editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  859    let pane = workspace
  860        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  861        .unwrap();
  862
  863    _ = workspace.update(cx, |_v, window, cx| {
  864        cx.new(|cx| {
  865            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  866            let mut editor = build_editor(buffer, window, cx);
  867            let handle = cx.entity();
  868            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  869
  870            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  871                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  872            }
  873
  874            // Move the cursor a small distance.
  875            // Nothing is added to the navigation history.
  876            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  877                s.select_display_ranges([
  878                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  879                ])
  880            });
  881            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  882                s.select_display_ranges([
  883                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  884                ])
  885            });
  886            assert!(pop_history(&mut editor, cx).is_none());
  887
  888            // Move the cursor a large distance.
  889            // The history can jump back to the previous position.
  890            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  891                s.select_display_ranges([
  892                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  893                ])
  894            });
  895            let nav_entry = pop_history(&mut editor, cx).unwrap();
  896            editor.navigate(nav_entry.data.unwrap(), window, cx);
  897            assert_eq!(nav_entry.item.id(), cx.entity_id());
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  903            );
  904            assert!(pop_history(&mut editor, cx).is_none());
  905
  906            // Move the cursor a small distance via the mouse.
  907            // Nothing is added to the navigation history.
  908            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  909            editor.end_selection(window, cx);
  910            assert_eq!(
  911                editor
  912                    .selections
  913                    .display_ranges(&editor.display_snapshot(cx)),
  914                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  915            );
  916            assert!(pop_history(&mut editor, cx).is_none());
  917
  918            // Move the cursor a large distance via the mouse.
  919            // The history can jump back to the previous position.
  920            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  921            editor.end_selection(window, cx);
  922            assert_eq!(
  923                editor
  924                    .selections
  925                    .display_ranges(&editor.display_snapshot(cx)),
  926                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  927            );
  928            let nav_entry = pop_history(&mut editor, cx).unwrap();
  929            editor.navigate(nav_entry.data.unwrap(), window, cx);
  930            assert_eq!(nav_entry.item.id(), cx.entity_id());
  931            assert_eq!(
  932                editor
  933                    .selections
  934                    .display_ranges(&editor.display_snapshot(cx)),
  935                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  936            );
  937            assert!(pop_history(&mut editor, cx).is_none());
  938
  939            // Set scroll position to check later
  940            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  941            let original_scroll_position = editor.scroll_manager.anchor();
  942
  943            // Jump to the end of the document and adjust scroll
  944            editor.move_to_end(&MoveToEnd, window, cx);
  945            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  946            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  947
  948            let nav_entry = pop_history(&mut editor, cx).unwrap();
  949            editor.navigate(nav_entry.data.unwrap(), window, cx);
  950            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  951
  952            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  953            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  954            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  955            let invalid_point = Point::new(9999, 0);
  956            editor.navigate(
  957                Box::new(NavigationData {
  958                    cursor_anchor: invalid_anchor,
  959                    cursor_position: invalid_point,
  960                    scroll_anchor: ScrollAnchor {
  961                        anchor: invalid_anchor,
  962                        offset: Default::default(),
  963                    },
  964                    scroll_top_row: invalid_point.row,
  965                }),
  966                window,
  967                cx,
  968            );
  969            assert_eq!(
  970                editor
  971                    .selections
  972                    .display_ranges(&editor.display_snapshot(cx)),
  973                &[editor.max_point(cx)..editor.max_point(cx)]
  974            );
  975            assert_eq!(
  976                editor.scroll_position(cx),
  977                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  978            );
  979
  980            editor
  981        })
  982    });
  983}
  984
  985#[gpui::test]
  986fn test_cancel(cx: &mut TestAppContext) {
  987    init_test(cx, |_| {});
  988
  989    let editor = cx.add_window(|window, cx| {
  990        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  991        build_editor(buffer, window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  996        editor.update_selection(
  997            DisplayPoint::new(DisplayRow(1), 1),
  998            0,
  999            gpui::Point::<f32>::default(),
 1000            window,
 1001            cx,
 1002        );
 1003        editor.end_selection(window, cx);
 1004
 1005        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1006        editor.update_selection(
 1007            DisplayPoint::new(DisplayRow(0), 3),
 1008            0,
 1009            gpui::Point::<f32>::default(),
 1010            window,
 1011            cx,
 1012        );
 1013        editor.end_selection(window, cx);
 1014        assert_eq!(
 1015            display_ranges(editor, cx),
 1016            [
 1017                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1018                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1019            ]
 1020        );
 1021    });
 1022
 1023    _ = editor.update(cx, |editor, window, cx| {
 1024        editor.cancel(&Cancel, window, cx);
 1025        assert_eq!(
 1026            display_ranges(editor, cx),
 1027            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1028        );
 1029    });
 1030
 1031    _ = editor.update(cx, |editor, window, cx| {
 1032        editor.cancel(&Cancel, window, cx);
 1033        assert_eq!(
 1034            display_ranges(editor, cx),
 1035            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                impl Foo {
 1048                    // Hello!
 1049
 1050                    fn a() {
 1051                        1
 1052                    }
 1053
 1054                    fn b() {
 1055                        2
 1056                    }
 1057
 1058                    fn c() {
 1059                        3
 1060                    }
 1061                }
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer, window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                impl Foo {
 1080                    // Hello!
 1081
 1082                    fn a() {
 1083                        1
 1084                    }
 1085
 1086                    fn b() {⋯
 1087                    }
 1088
 1089                    fn c() {⋯
 1090                    }
 1091                }
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                impl Foo {⋯
 1101                }
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                impl Foo {
 1111                    // Hello!
 1112
 1113                    fn a() {
 1114                        1
 1115                    }
 1116
 1117                    fn b() {⋯
 1118                    }
 1119
 1120                    fn c() {⋯
 1121                    }
 1122                }
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151                    def c():
 1152                        print(3)
 1153            "
 1154            .unindent(),
 1155            cx,
 1156        );
 1157        build_editor(buffer, window, cx)
 1158    });
 1159
 1160    _ = editor.update(cx, |editor, window, cx| {
 1161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1162            s.select_display_ranges([
 1163                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1164            ]);
 1165        });
 1166        editor.fold(&Fold, window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():⋯
 1177
 1178                    def c():⋯
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:⋯
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.unfold_lines(&UnfoldLines, window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:
 1197                    # Hello!
 1198
 1199                    def a():
 1200                        print(1)
 1201
 1202                    def b():⋯
 1203
 1204                    def c():⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            editor.buffer.read(cx).read(cx).text()
 1213        );
 1214    });
 1215}
 1216
 1217#[gpui::test]
 1218fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1219    init_test(cx, |_| {});
 1220
 1221    let editor = cx.add_window(|window, cx| {
 1222        let buffer = MultiBuffer::build_simple(
 1223            &"
 1224                class Foo:
 1225                    # Hello!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234                    def c():
 1235                        print(3)
 1236
 1237
 1238            "
 1239            .unindent(),
 1240            cx,
 1241        );
 1242        build_editor(buffer, window, cx)
 1243    });
 1244
 1245    _ = editor.update(cx, |editor, window, cx| {
 1246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1247            s.select_display_ranges([
 1248                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1249            ]);
 1250        });
 1251        editor.fold(&Fold, window, cx);
 1252        assert_eq!(
 1253            editor.display_text(cx),
 1254            "
 1255                class Foo:
 1256                    # Hello!
 1257
 1258                    def a():
 1259                        print(1)
 1260
 1261                    def b():⋯
 1262
 1263
 1264                    def c():⋯
 1265
 1266
 1267            "
 1268            .unindent(),
 1269        );
 1270
 1271        editor.fold(&Fold, window, cx);
 1272        assert_eq!(
 1273            editor.display_text(cx),
 1274            "
 1275                class Foo:⋯
 1276
 1277
 1278            "
 1279            .unindent(),
 1280        );
 1281
 1282        editor.unfold_lines(&UnfoldLines, window, cx);
 1283        assert_eq!(
 1284            editor.display_text(cx),
 1285            "
 1286                class Foo:
 1287                    # Hello!
 1288
 1289                    def a():
 1290                        print(1)
 1291
 1292                    def b():⋯
 1293
 1294
 1295                    def c():⋯
 1296
 1297
 1298            "
 1299            .unindent(),
 1300        );
 1301
 1302        editor.unfold_lines(&UnfoldLines, window, cx);
 1303        assert_eq!(
 1304            editor.display_text(cx),
 1305            editor.buffer.read(cx).read(cx).text()
 1306        );
 1307    });
 1308}
 1309
 1310#[gpui::test]
 1311fn test_fold_at_level(cx: &mut TestAppContext) {
 1312    init_test(cx, |_| {});
 1313
 1314    let editor = cx.add_window(|window, cx| {
 1315        let buffer = MultiBuffer::build_simple(
 1316            &"
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339            cx,
 1340        );
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:
 1350                    # Hello!
 1351
 1352                    def a():⋯
 1353
 1354                    def b():⋯
 1355
 1356
 1357                class Bar:
 1358                    # World!
 1359
 1360                    def a():⋯
 1361
 1362                    def b():⋯
 1363
 1364
 1365            "
 1366            .unindent(),
 1367        );
 1368
 1369        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1370        assert_eq!(
 1371            editor.display_text(cx),
 1372            "
 1373                class Foo:⋯
 1374
 1375
 1376                class Bar:⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.unfold_all(&UnfoldAll, window, cx);
 1384        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1385        assert_eq!(
 1386            editor.display_text(cx),
 1387            "
 1388                class Foo:
 1389                    # Hello!
 1390
 1391                    def a():
 1392                        print(1)
 1393
 1394                    def b():
 1395                        print(2)
 1396
 1397
 1398                class Bar:
 1399                    # World!
 1400
 1401                    def a():
 1402                        print(1)
 1403
 1404                    def b():
 1405                        print(2)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410        );
 1411
 1412        assert_eq!(
 1413            editor.display_text(cx),
 1414            editor.buffer.read(cx).read(cx).text()
 1415        );
 1416        let (_, positions) = marked_text_ranges(
 1417            &"
 1418                       class Foo:
 1419                           # Hello!
 1420
 1421                           def a():
 1422                              print(1)
 1423
 1424                           def b():
 1425                               p«riˇ»nt(2)
 1426
 1427
 1428                       class Bar:
 1429                           # World!
 1430
 1431                           def a():
 1432                               «ˇprint(1)
 1433
 1434                           def b():
 1435                               print(2)»
 1436
 1437
 1438                   "
 1439            .unindent(),
 1440            true,
 1441        );
 1442
 1443        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1444            s.select_ranges(
 1445                positions
 1446                    .iter()
 1447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1448            )
 1449        });
 1450
 1451        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1452        assert_eq!(
 1453            editor.display_text(cx),
 1454            "
 1455                class Foo:
 1456                    # Hello!
 1457
 1458                    def a():⋯
 1459
 1460                    def b():
 1461                        print(2)
 1462
 1463
 1464                class Bar:
 1465                    # World!
 1466
 1467                    def a():
 1468                        print(1)
 1469
 1470                    def b():
 1471                        print(2)
 1472
 1473
 1474            "
 1475            .unindent(),
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481fn test_move_cursor(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1485    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1486
 1487    buffer.update(cx, |buffer, cx| {
 1488        buffer.edit(
 1489            vec![
 1490                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1491                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1492            ],
 1493            None,
 1494            cx,
 1495        );
 1496    });
 1497    _ = editor.update(cx, |editor, window, cx| {
 1498        assert_eq!(
 1499            display_ranges(editor, cx),
 1500            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            display_ranges(editor, cx),
 1506            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1507        );
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            display_ranges(editor, cx),
 1512            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1513        );
 1514
 1515        editor.move_left(&MoveLeft, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1525        );
 1526
 1527        editor.move_to_end(&MoveToEnd, window, cx);
 1528        assert_eq!(
 1529            display_ranges(editor, cx),
 1530            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1531        );
 1532
 1533        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1534        assert_eq!(
 1535            display_ranges(editor, cx),
 1536            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1537        );
 1538
 1539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1542            ]);
 1543        });
 1544        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1548        );
 1549
 1550        editor.select_to_end(&SelectToEnd, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1554        );
 1555    });
 1556}
 1557
 1558#[gpui::test]
 1559fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1560    init_test(cx, |_| {});
 1561
 1562    let editor = cx.add_window(|window, cx| {
 1563        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1564        build_editor(buffer, window, cx)
 1565    });
 1566
 1567    assert_eq!('🟥'.len_utf8(), 4);
 1568    assert_eq!('α'.len_utf8(), 2);
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.fold_creases(
 1572            vec![
 1573                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1576            ],
 1577            true,
 1578            window,
 1579            cx,
 1580        );
 1581        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1582
 1583        editor.move_right(&MoveRight, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1585        editor.move_right(&MoveRight, window, cx);
 1586        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1587        editor.move_right(&MoveRight, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1589
 1590        editor.move_down(&MoveDown, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1594        editor.move_left(&MoveLeft, window, cx);
 1595        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1596        editor.move_left(&MoveLeft, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1598
 1599        editor.move_down(&MoveDown, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1605        editor.move_right(&MoveRight, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1610        editor.move_down(&MoveDown, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1614
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1617        editor.move_left(&MoveLeft, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1621    });
 1622}
 1623
 1624#[gpui::test]
 1625fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1626    init_test(cx, |_| {});
 1627
 1628    let editor = cx.add_window(|window, cx| {
 1629        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1630        build_editor(buffer, window, cx)
 1631    });
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1634            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1635        });
 1636
 1637        // moving above start of document should move selection to start of document,
 1638        // but the next move down should still be at the original goal_x
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1650
 1651        editor.move_down(&MoveDown, window, cx);
 1652        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1653
 1654        // moving past end of document should not change goal_x
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_up(&MoveUp, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1669    });
 1670}
 1671
 1672#[gpui::test]
 1673fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1674    init_test(cx, |_| {});
 1675    let move_to_beg = MoveToBeginningOfLine {
 1676        stop_at_soft_wraps: true,
 1677        stop_at_indent: true,
 1678    };
 1679
 1680    let delete_to_beg = DeleteToBeginningOfLine {
 1681        stop_at_indent: false,
 1682    };
 1683
 1684    let move_to_end = MoveToEndOfLine {
 1685        stop_at_soft_wraps: true,
 1686    };
 1687
 1688    let editor = cx.add_window(|window, cx| {
 1689        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1690        build_editor(buffer, window, cx)
 1691    });
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1694            s.select_display_ranges([
 1695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1696                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1697            ]);
 1698        });
 1699    });
 1700
 1701    _ = editor.update(cx, |editor, window, cx| {
 1702        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1703        assert_eq!(
 1704            display_ranges(editor, cx),
 1705            &[
 1706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1708            ]
 1709        );
 1710    });
 1711
 1712    _ = editor.update(cx, |editor, window, cx| {
 1713        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1714        assert_eq!(
 1715            display_ranges(editor, cx),
 1716            &[
 1717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1719            ]
 1720        );
 1721    });
 1722
 1723    _ = editor.update(cx, |editor, window, cx| {
 1724        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1725        assert_eq!(
 1726            display_ranges(editor, cx),
 1727            &[
 1728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1729                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1730            ]
 1731        );
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.move_to_end_of_line(&move_to_end, window, cx);
 1736        assert_eq!(
 1737            display_ranges(editor, cx),
 1738            &[
 1739                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1740                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1741            ]
 1742        );
 1743    });
 1744
 1745    // Moving to the end of line again is a no-op.
 1746    _ = editor.update(cx, |editor, window, cx| {
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            display_ranges(editor, cx),
 1750            &[
 1751                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1752                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1753            ]
 1754        );
 1755    });
 1756
 1757    _ = editor.update(cx, |editor, window, cx| {
 1758        editor.move_left(&MoveLeft, window, cx);
 1759        editor.select_to_beginning_of_line(
 1760            &SelectToBeginningOfLine {
 1761                stop_at_soft_wraps: true,
 1762                stop_at_indent: true,
 1763            },
 1764            window,
 1765            cx,
 1766        );
 1767        assert_eq!(
 1768            display_ranges(editor, cx),
 1769            &[
 1770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1771                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1772            ]
 1773        );
 1774    });
 1775
 1776    _ = editor.update(cx, |editor, window, cx| {
 1777        editor.select_to_beginning_of_line(
 1778            &SelectToBeginningOfLine {
 1779                stop_at_soft_wraps: true,
 1780                stop_at_indent: true,
 1781            },
 1782            window,
 1783            cx,
 1784        );
 1785        assert_eq!(
 1786            display_ranges(editor, cx),
 1787            &[
 1788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1789                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1790            ]
 1791        );
 1792    });
 1793
 1794    _ = editor.update(cx, |editor, window, cx| {
 1795        editor.select_to_beginning_of_line(
 1796            &SelectToBeginningOfLine {
 1797                stop_at_soft_wraps: true,
 1798                stop_at_indent: true,
 1799            },
 1800            window,
 1801            cx,
 1802        );
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.select_to_end_of_line(
 1814            &SelectToEndOfLine {
 1815                stop_at_soft_wraps: true,
 1816            },
 1817            window,
 1818            cx,
 1819        );
 1820        assert_eq!(
 1821            display_ranges(editor, cx),
 1822            &[
 1823                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1831        assert_eq!(editor.display_text(cx), "ab\n  de");
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]
 1838        );
 1839    });
 1840
 1841    _ = editor.update(cx, |editor, window, cx| {
 1842        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1843        assert_eq!(editor.display_text(cx), "\n");
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851    });
 1852}
 1853
 1854#[gpui::test]
 1855fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1856    init_test(cx, |_| {});
 1857    let move_to_beg = MoveToBeginningOfLine {
 1858        stop_at_soft_wraps: false,
 1859        stop_at_indent: false,
 1860    };
 1861
 1862    let move_to_end = MoveToEndOfLine {
 1863        stop_at_soft_wraps: false,
 1864    };
 1865
 1866    let editor = cx.add_window(|window, cx| {
 1867        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1868        build_editor(buffer, window, cx)
 1869    });
 1870
 1871    _ = editor.update(cx, |editor, window, cx| {
 1872        editor.set_wrap_width(Some(140.0.into()), cx);
 1873
 1874        // We expect the following lines after wrapping
 1875        // ```
 1876        // thequickbrownfox
 1877        // jumpedoverthelazydo
 1878        // gs
 1879        // ```
 1880        // The final `gs` was soft-wrapped onto a new line.
 1881        assert_eq!(
 1882            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1883            editor.display_text(cx),
 1884        );
 1885
 1886        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1887        // Start the cursor at the `k` on the first line
 1888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1889            s.select_display_ranges([
 1890                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1891            ]);
 1892        });
 1893
 1894        // Moving to the beginning of the line should put us at the beginning of the line.
 1895        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1896        assert_eq!(
 1897            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1898            display_ranges(editor, cx)
 1899        );
 1900
 1901        // Moving to the end of the line should put us at the end of the line.
 1902        editor.move_to_end_of_line(&move_to_end, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1905            display_ranges(editor, cx)
 1906        );
 1907
 1908        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1909        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1911            s.select_display_ranges([
 1912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1913            ]);
 1914        });
 1915
 1916        // Moving to the beginning of the line should put us at the start of the second line of
 1917        // display text, i.e., the `j`.
 1918        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Moving to the beginning of the line again should be a no-op.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            display_ranges(editor, cx)
 1929        );
 1930
 1931        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1932        // next display line.
 1933        editor.move_to_end_of_line(&move_to_end, window, cx);
 1934        assert_eq!(
 1935            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1936            display_ranges(editor, cx)
 1937        );
 1938
 1939        // Moving to the end of the line again should be a no-op.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            display_ranges(editor, cx)
 1944        );
 1945    });
 1946}
 1947
 1948#[gpui::test]
 1949fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1950    init_test(cx, |_| {});
 1951
 1952    let move_to_beg = MoveToBeginningOfLine {
 1953        stop_at_soft_wraps: true,
 1954        stop_at_indent: true,
 1955    };
 1956
 1957    let select_to_beg = SelectToBeginningOfLine {
 1958        stop_at_soft_wraps: true,
 1959        stop_at_indent: true,
 1960    };
 1961
 1962    let delete_to_beg = DeleteToBeginningOfLine {
 1963        stop_at_indent: true,
 1964    };
 1965
 1966    let move_to_end = MoveToEndOfLine {
 1967        stop_at_soft_wraps: false,
 1968    };
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1977            s.select_display_ranges([
 1978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1979                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1984        // and the second cursor at the first non-whitespace character in the line.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            display_ranges(editor, cx),
 1988            &[
 1989                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1990                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1991            ]
 1992        );
 1993
 1994        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1995        // and should move the second cursor to the beginning of the line.
 1996        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1997        assert_eq!(
 1998            display_ranges(editor, cx),
 1999            &[
 2000                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2001                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2002            ]
 2003        );
 2004
 2005        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2006        // and should move the second cursor back to the first non-whitespace character in the line.
 2007        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2008        assert_eq!(
 2009            display_ranges(editor, cx),
 2010            &[
 2011                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2012                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2013            ]
 2014        );
 2015
 2016        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2017        // and to the first non-whitespace character in the line for the second cursor.
 2018        editor.move_to_end_of_line(&move_to_end, window, cx);
 2019        editor.move_left(&MoveLeft, window, cx);
 2020        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2021        assert_eq!(
 2022            display_ranges(editor, cx),
 2023            &[
 2024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2025                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2026            ]
 2027        );
 2028
 2029        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2030        // and should select to the beginning of the line for the second cursor.
 2031        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2032        assert_eq!(
 2033            display_ranges(editor, cx),
 2034            &[
 2035                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2036                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2037            ]
 2038        );
 2039
 2040        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2041        // and should delete to the first non-whitespace character in the line for the second cursor.
 2042        editor.move_to_end_of_line(&move_to_end, window, cx);
 2043        editor.move_left(&MoveLeft, window, cx);
 2044        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2045        assert_eq!(editor.text(cx), "c\n  f");
 2046    });
 2047}
 2048
 2049#[gpui::test]
 2050fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2051    init_test(cx, |_| {});
 2052
 2053    let move_to_beg = MoveToBeginningOfLine {
 2054        stop_at_soft_wraps: true,
 2055        stop_at_indent: true,
 2056    };
 2057
 2058    let editor = cx.add_window(|window, cx| {
 2059        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2060        build_editor(buffer, window, cx)
 2061    });
 2062
 2063    _ = editor.update(cx, |editor, window, cx| {
 2064        // test cursor between line_start and indent_start
 2065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2066            s.select_display_ranges([
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2068            ]);
 2069        });
 2070
 2071        // cursor should move to line_start
 2072        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2073        assert_eq!(
 2074            display_ranges(editor, cx),
 2075            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2076        );
 2077
 2078        // cursor should move to indent_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            display_ranges(editor, cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2083        );
 2084
 2085        // cursor should move to back to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091    });
 2092}
 2093
 2094#[gpui::test]
 2095fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2096    init_test(cx, |_| {});
 2097
 2098    let editor = cx.add_window(|window, cx| {
 2099        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2100        build_editor(buffer, window, cx)
 2101    });
 2102    _ = editor.update(cx, |editor, window, cx| {
 2103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2104            s.select_display_ranges([
 2105                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2106                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2107            ])
 2108        });
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_right(&MoveRight, window, cx);
 2134        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2135        assert_selection_ranges(
 2136            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2137            editor,
 2138            cx,
 2139        );
 2140
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154    });
 2155}
 2156
 2157#[gpui::test]
 2158fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2159    init_test(cx, |_| {});
 2160
 2161    let editor = cx.add_window(|window, cx| {
 2162        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2163        build_editor(buffer, window, cx)
 2164    });
 2165
 2166    _ = editor.update(cx, |editor, window, cx| {
 2167        editor.set_wrap_width(Some(140.0.into()), cx);
 2168        assert_eq!(
 2169            editor.display_text(cx),
 2170            "use one::{\n    two::three::\n    four::five\n};"
 2171        );
 2172
 2173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2174            s.select_display_ranges([
 2175                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2176            ]);
 2177        });
 2178
 2179        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2180        assert_eq!(
 2181            display_ranges(editor, cx),
 2182            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2183        );
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            display_ranges(editor, cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            display_ranges(editor, cx),
 2194            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            display_ranges(editor, cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2201        );
 2202
 2203        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            display_ranges(editor, cx),
 2212            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2213        );
 2214    });
 2215}
 2216
 2217#[gpui::test]
 2218async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2219    init_test(cx, |_| {});
 2220    let mut cx = EditorTestContext::new(cx).await;
 2221
 2222    let line_height = cx.update_editor(|editor, window, cx| {
 2223        editor
 2224            .style(cx)
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.update_editor(|editor, window, cx| {
 2338        editor
 2339            .style(cx)
 2340            .text
 2341            .line_height_in_pixels(window.rem_size())
 2342    });
 2343    let window = cx.window;
 2344    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2345
 2346    cx.set_state(
 2347        r#"ˇone
 2348        two
 2349        three
 2350        four
 2351        five
 2352        six
 2353        seven
 2354        eight
 2355        nine
 2356        ten
 2357        "#,
 2358    );
 2359
 2360    cx.update_editor(|editor, window, cx| {
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 0.)
 2364        );
 2365        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2366        assert_eq!(
 2367            editor.snapshot(window, cx).scroll_position(),
 2368            gpui::Point::new(0., 3.)
 2369        );
 2370        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2371        assert_eq!(
 2372            editor.snapshot(window, cx).scroll_position(),
 2373            gpui::Point::new(0., 6.)
 2374        );
 2375        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2376        assert_eq!(
 2377            editor.snapshot(window, cx).scroll_position(),
 2378            gpui::Point::new(0., 3.)
 2379        );
 2380
 2381        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2382        assert_eq!(
 2383            editor.snapshot(window, cx).scroll_position(),
 2384            gpui::Point::new(0., 1.)
 2385        );
 2386        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2387        assert_eq!(
 2388            editor.snapshot(window, cx).scroll_position(),
 2389            gpui::Point::new(0., 3.)
 2390        );
 2391    });
 2392}
 2393
 2394#[gpui::test]
 2395async fn test_autoscroll(cx: &mut TestAppContext) {
 2396    init_test(cx, |_| {});
 2397    let mut cx = EditorTestContext::new(cx).await;
 2398
 2399    let line_height = cx.update_editor(|editor, window, cx| {
 2400        editor.set_vertical_scroll_margin(2, cx);
 2401        editor
 2402            .style(cx)
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.update_editor(|editor, window, cx| {
 2482        editor
 2483            .style(cx)
 2484            .text
 2485            .line_height_in_pixels(window.rem_size())
 2486    });
 2487    let window = cx.window;
 2488    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2489    cx.set_state(
 2490        &r#"
 2491        ˇone
 2492        two
 2493        threeˇ
 2494        four
 2495        five
 2496        six
 2497        seven
 2498        eight
 2499        nine
 2500        ten
 2501        "#
 2502        .unindent(),
 2503    );
 2504
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.move_page_down(&MovePageDown::default(), window, cx)
 2507    });
 2508    cx.assert_editor_state(
 2509        &r#"
 2510        one
 2511        two
 2512        three
 2513        ˇfour
 2514        five
 2515        sixˇ
 2516        seven
 2517        eight
 2518        nine
 2519        ten
 2520        "#
 2521        .unindent(),
 2522    );
 2523
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.move_page_down(&MovePageDown::default(), window, cx)
 2526    });
 2527    cx.assert_editor_state(
 2528        &r#"
 2529        one
 2530        two
 2531        three
 2532        four
 2533        five
 2534        six
 2535        ˇseven
 2536        eight
 2537        nineˇ
 2538        ten
 2539        "#
 2540        .unindent(),
 2541    );
 2542
 2543    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2561    cx.assert_editor_state(
 2562        &r#"
 2563        ˇone
 2564        two
 2565        threeˇ
 2566        four
 2567        five
 2568        six
 2569        seven
 2570        eight
 2571        nine
 2572        ten
 2573        "#
 2574        .unindent(),
 2575    );
 2576
 2577    // Test select collapsing
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582    });
 2583    cx.assert_editor_state(
 2584        &r#"
 2585        one
 2586        two
 2587        three
 2588        four
 2589        five
 2590        six
 2591        seven
 2592        eight
 2593        nine
 2594        ˇten
 2595        ˇ"#
 2596        .unindent(),
 2597    );
 2598}
 2599
 2600#[gpui::test]
 2601async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2602    init_test(cx, |_| {});
 2603    let mut cx = EditorTestContext::new(cx).await;
 2604    cx.set_state("one «two threeˇ» four");
 2605    cx.update_editor(|editor, window, cx| {
 2606        editor.delete_to_beginning_of_line(
 2607            &DeleteToBeginningOfLine {
 2608                stop_at_indent: false,
 2609            },
 2610            window,
 2611            cx,
 2612        );
 2613        assert_eq!(editor.text(cx), " four");
 2614    });
 2615}
 2616
 2617#[gpui::test]
 2618async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2619    init_test(cx, |_| {});
 2620
 2621    let mut cx = EditorTestContext::new(cx).await;
 2622
 2623    // For an empty selection, the preceding word fragment is deleted.
 2624    // For non-empty selections, only selected characters are deleted.
 2625    cx.set_state("onˇe two t«hreˇ»e four");
 2626    cx.update_editor(|editor, window, cx| {
 2627        editor.delete_to_previous_word_start(
 2628            &DeleteToPreviousWordStart {
 2629                ignore_newlines: false,
 2630                ignore_brackets: false,
 2631            },
 2632            window,
 2633            cx,
 2634        );
 2635    });
 2636    cx.assert_editor_state("ˇe two tˇe four");
 2637
 2638    cx.set_state("e tˇwo te «fˇ»our");
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.delete_to_next_word_end(
 2641            &DeleteToNextWordEnd {
 2642                ignore_newlines: false,
 2643                ignore_brackets: false,
 2644            },
 2645            window,
 2646            cx,
 2647        );
 2648    });
 2649    cx.assert_editor_state("e tˇ te ˇour");
 2650}
 2651
 2652#[gpui::test]
 2653async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2654    init_test(cx, |_| {});
 2655
 2656    let mut cx = EditorTestContext::new(cx).await;
 2657
 2658    cx.set_state("here is some text    ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: false,
 2663                ignore_brackets: true,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2670    cx.assert_editor_state("here is some textˇwith a space");
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: false,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    cx.assert_editor_state("here is some textˇwith a space");
 2684
 2685    cx.set_state("here is some textˇ    with a space");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_next_word_end(
 2688            &DeleteToNextWordEnd {
 2689                ignore_newlines: false,
 2690                ignore_brackets: true,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    // Same happens in the other direction.
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some textˇwith a space");
 2711
 2712    cx.set_state("here is some textˇ    with a space");
 2713    cx.update_editor(|editor, window, cx| {
 2714        editor.delete_to_next_word_end(
 2715            &DeleteToNextWordEnd {
 2716                ignore_newlines: true,
 2717                ignore_brackets: false,
 2718            },
 2719            window,
 2720            cx,
 2721        );
 2722    });
 2723    cx.assert_editor_state("here is some textˇwith a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_previous_word_start(
 2726            &DeleteToPreviousWordStart {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some ˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    // Single whitespaces are removed with the word behind them.
 2746    cx.assert_editor_state("here is ˇwith a space");
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    cx.assert_editor_state("here ˇwith a space");
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    cx.assert_editor_state("ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_next_word_end(
 2782            &DeleteToNextWordEnd {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    // Same happens in the other direction.
 2791    cx.assert_editor_state("ˇ a space");
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_next_word_end(
 2794            &DeleteToNextWordEnd {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state("ˇ space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_previous_word_start(
 2827            &DeleteToPreviousWordStart {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836}
 2837
 2838#[gpui::test]
 2839async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2840    init_test(cx, |_| {});
 2841
 2842    let language = Arc::new(
 2843        Language::new(
 2844            LanguageConfig {
 2845                brackets: BracketPairConfig {
 2846                    pairs: vec![
 2847                        BracketPair {
 2848                            start: "\"".to_string(),
 2849                            end: "\"".to_string(),
 2850                            close: true,
 2851                            surround: true,
 2852                            newline: false,
 2853                        },
 2854                        BracketPair {
 2855                            start: "(".to_string(),
 2856                            end: ")".to_string(),
 2857                            close: true,
 2858                            surround: true,
 2859                            newline: true,
 2860                        },
 2861                    ],
 2862                    ..BracketPairConfig::default()
 2863                },
 2864                ..LanguageConfig::default()
 2865            },
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_brackets_query(
 2869            r#"
 2870                ("(" @open ")" @close)
 2871                ("\"" @open "\"" @close)
 2872            "#,
 2873        )
 2874        .unwrap(),
 2875    );
 2876
 2877    let mut cx = EditorTestContext::new(cx).await;
 2878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2879
 2880    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    // Deletion stops before brackets if asked to not ignore them.
 2892    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    // Deletion has to remove a single bracket and then stop again.
 2904    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_previous_word_start(
 2920            &DeleteToPreviousWordStart {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2953    cx.assert_editor_state(r#"ˇ");"#);
 2954
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_next_word_end(
 2957            &DeleteToNextWordEnd {
 2958                ignore_newlines: true,
 2959                ignore_brackets: false,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"ˇ"#);
 2966
 2967    cx.update_editor(|editor, window, cx| {
 2968        editor.delete_to_next_word_end(
 2969            &DeleteToNextWordEnd {
 2970                ignore_newlines: true,
 2971                ignore_brackets: false,
 2972            },
 2973            window,
 2974            cx,
 2975        );
 2976    });
 2977    cx.assert_editor_state(r#"ˇ"#);
 2978
 2979    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2980    cx.update_editor(|editor, window, cx| {
 2981        editor.delete_to_previous_word_start(
 2982            &DeleteToPreviousWordStart {
 2983                ignore_newlines: true,
 2984                ignore_brackets: true,
 2985            },
 2986            window,
 2987            cx,
 2988        );
 2989    });
 2990    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2991}
 2992
 2993#[gpui::test]
 2994fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2999        build_editor(buffer, window, cx)
 3000    });
 3001    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3002        ignore_newlines: false,
 3003        ignore_brackets: false,
 3004    };
 3005    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3006        ignore_newlines: true,
 3007        ignore_brackets: false,
 3008    };
 3009
 3010    _ = editor.update(cx, |editor, window, cx| {
 3011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3012            s.select_display_ranges([
 3013                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3014            ])
 3015        });
 3016        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3017        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3018        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3020        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3021        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3022        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3023        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3024        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3025        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3028    });
 3029}
 3030
 3031#[gpui::test]
 3032fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3033    init_test(cx, |_| {});
 3034
 3035    let editor = cx.add_window(|window, cx| {
 3036        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3037        build_editor(buffer, window, cx)
 3038    });
 3039    let del_to_next_word_end = DeleteToNextWordEnd {
 3040        ignore_newlines: false,
 3041        ignore_brackets: false,
 3042    };
 3043    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3044        ignore_newlines: true,
 3045        ignore_brackets: false,
 3046    };
 3047
 3048    _ = editor.update(cx, |editor, window, cx| {
 3049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3050            s.select_display_ranges([
 3051                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3052            ])
 3053        });
 3054        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3055        assert_eq!(
 3056            editor.buffer.read(cx).read(cx).text(),
 3057            "one\n   two\nthree\n   four"
 3058        );
 3059        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3060        assert_eq!(
 3061            editor.buffer.read(cx).read(cx).text(),
 3062            "\n   two\nthree\n   four"
 3063        );
 3064        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3065        assert_eq!(
 3066            editor.buffer.read(cx).read(cx).text(),
 3067            "two\nthree\n   four"
 3068        );
 3069        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3070        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3071        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3072        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3073        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3074        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3075        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3076        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3077    });
 3078}
 3079
 3080#[gpui::test]
 3081fn test_newline(cx: &mut TestAppContext) {
 3082    init_test(cx, |_| {});
 3083
 3084    let editor = cx.add_window(|window, cx| {
 3085        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3086        build_editor(buffer, window, cx)
 3087    });
 3088
 3089    _ = editor.update(cx, |editor, window, cx| {
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_display_ranges([
 3092                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3093                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3094                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3095            ])
 3096        });
 3097
 3098        editor.newline(&Newline, window, cx);
 3099        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3100    });
 3101}
 3102
 3103#[gpui::test]
 3104async fn test_newline_yaml(cx: &mut TestAppContext) {
 3105    init_test(cx, |_| {});
 3106
 3107    let mut cx = EditorTestContext::new(cx).await;
 3108    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3109    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3110
 3111    // Object (between 2 fields)
 3112    cx.set_state(indoc! {"
 3113    test:ˇ
 3114    hello: bye"});
 3115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117    test:
 3118        ˇ
 3119    hello: bye"});
 3120
 3121    // Object (first and single line)
 3122    cx.set_state(indoc! {"
 3123    test:ˇ"});
 3124    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126    test:
 3127        ˇ"});
 3128
 3129    // Array with objects (after first element)
 3130    cx.set_state(indoc! {"
 3131    test:
 3132        - foo: barˇ"});
 3133    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3134    cx.assert_editor_state(indoc! {"
 3135    test:
 3136        - foo: bar
 3137        ˇ"});
 3138
 3139    // Array with objects and comment
 3140    cx.set_state(indoc! {"
 3141    test:
 3142        - foo: bar
 3143        - bar: # testˇ"});
 3144    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3145    cx.assert_editor_state(indoc! {"
 3146    test:
 3147        - foo: bar
 3148        - bar: # test
 3149            ˇ"});
 3150
 3151    // Array with objects (after second element)
 3152    cx.set_state(indoc! {"
 3153    test:
 3154        - foo: bar
 3155        - bar: fooˇ"});
 3156    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3157    cx.assert_editor_state(indoc! {"
 3158    test:
 3159        - foo: bar
 3160        - bar: foo
 3161        ˇ"});
 3162
 3163    // Array with strings (after first element)
 3164    cx.set_state(indoc! {"
 3165    test:
 3166        - fooˇ"});
 3167    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168    cx.assert_editor_state(indoc! {"
 3169    test:
 3170        - foo
 3171        ˇ"});
 3172}
 3173
 3174#[gpui::test]
 3175fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3176    init_test(cx, |_| {});
 3177
 3178    let editor = cx.add_window(|window, cx| {
 3179        let buffer = MultiBuffer::build_simple(
 3180            "
 3181                a
 3182                b(
 3183                    X
 3184                )
 3185                c(
 3186                    X
 3187                )
 3188            "
 3189            .unindent()
 3190            .as_str(),
 3191            cx,
 3192        );
 3193        let mut editor = build_editor(buffer, window, cx);
 3194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3195            s.select_ranges([
 3196                Point::new(2, 4)..Point::new(2, 5),
 3197                Point::new(5, 4)..Point::new(5, 5),
 3198            ])
 3199        });
 3200        editor
 3201    });
 3202
 3203    _ = editor.update(cx, |editor, window, cx| {
 3204        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3205        editor.buffer.update(cx, |buffer, cx| {
 3206            buffer.edit(
 3207                [
 3208                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3209                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3210                ],
 3211                None,
 3212                cx,
 3213            );
 3214            assert_eq!(
 3215                buffer.read(cx).text(),
 3216                "
 3217                    a
 3218                    b()
 3219                    c()
 3220                "
 3221                .unindent()
 3222            );
 3223        });
 3224        assert_eq!(
 3225            editor.selections.ranges(&editor.display_snapshot(cx)),
 3226            &[
 3227                Point::new(1, 2)..Point::new(1, 2),
 3228                Point::new(2, 2)..Point::new(2, 2),
 3229            ],
 3230        );
 3231
 3232        editor.newline(&Newline, window, cx);
 3233        assert_eq!(
 3234            editor.text(cx),
 3235            "
 3236                a
 3237                b(
 3238                )
 3239                c(
 3240                )
 3241            "
 3242            .unindent()
 3243        );
 3244
 3245        // The selections are moved after the inserted newlines
 3246        assert_eq!(
 3247            editor.selections.ranges(&editor.display_snapshot(cx)),
 3248            &[
 3249                Point::new(2, 0)..Point::new(2, 0),
 3250                Point::new(4, 0)..Point::new(4, 0),
 3251            ],
 3252        );
 3253    });
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_newline_above(cx: &mut TestAppContext) {
 3258    init_test(cx, |settings| {
 3259        settings.defaults.tab_size = NonZeroU32::new(4)
 3260    });
 3261
 3262    let language = Arc::new(
 3263        Language::new(
 3264            LanguageConfig::default(),
 3265            Some(tree_sitter_rust::LANGUAGE.into()),
 3266        )
 3267        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3268        .unwrap(),
 3269    );
 3270
 3271    let mut cx = EditorTestContext::new(cx).await;
 3272    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3273    cx.set_state(indoc! {"
 3274        const a: ˇA = (
 3275 3276                «const_functionˇ»(ˇ),
 3277                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3278 3279        ˇ);ˇ
 3280    "});
 3281
 3282    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3283    cx.assert_editor_state(indoc! {"
 3284        ˇ
 3285        const a: A = (
 3286            ˇ
 3287            (
 3288                ˇ
 3289                ˇ
 3290                const_function(),
 3291                ˇ
 3292                ˇ
 3293                ˇ
 3294                ˇ
 3295                something_else,
 3296                ˇ
 3297            )
 3298            ˇ
 3299            ˇ
 3300        );
 3301    "});
 3302}
 3303
 3304#[gpui::test]
 3305async fn test_newline_below(cx: &mut TestAppContext) {
 3306    init_test(cx, |settings| {
 3307        settings.defaults.tab_size = NonZeroU32::new(4)
 3308    });
 3309
 3310    let language = Arc::new(
 3311        Language::new(
 3312            LanguageConfig::default(),
 3313            Some(tree_sitter_rust::LANGUAGE.into()),
 3314        )
 3315        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3316        .unwrap(),
 3317    );
 3318
 3319    let mut cx = EditorTestContext::new(cx).await;
 3320    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3321    cx.set_state(indoc! {"
 3322        const a: ˇA = (
 3323 3324                «const_functionˇ»(ˇ),
 3325                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3326 3327        ˇ);ˇ
 3328    "});
 3329
 3330    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3331    cx.assert_editor_state(indoc! {"
 3332        const a: A = (
 3333            ˇ
 3334            (
 3335                ˇ
 3336                const_function(),
 3337                ˇ
 3338                ˇ
 3339                something_else,
 3340                ˇ
 3341                ˇ
 3342                ˇ
 3343                ˇ
 3344            )
 3345            ˇ
 3346        );
 3347        ˇ
 3348        ˇ
 3349    "});
 3350}
 3351
 3352#[gpui::test]
 3353async fn test_newline_comments(cx: &mut TestAppContext) {
 3354    init_test(cx, |settings| {
 3355        settings.defaults.tab_size = NonZeroU32::new(4)
 3356    });
 3357
 3358    let language = Arc::new(Language::new(
 3359        LanguageConfig {
 3360            line_comments: vec!["// ".into()],
 3361            ..LanguageConfig::default()
 3362        },
 3363        None,
 3364    ));
 3365    {
 3366        let mut cx = EditorTestContext::new(cx).await;
 3367        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3368        cx.set_state(indoc! {"
 3369        // Fooˇ
 3370    "});
 3371
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        // Foo
 3375        // ˇ
 3376    "});
 3377        // Ensure that we add comment prefix when existing line contains space
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(
 3380            indoc! {"
 3381        // Foo
 3382        //s
 3383        // ˇ
 3384    "}
 3385            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3386            .as_str(),
 3387        );
 3388        // Ensure that we add comment prefix when existing line does not contain space
 3389        cx.set_state(indoc! {"
 3390        // Foo
 3391        //ˇ
 3392    "});
 3393        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3394        cx.assert_editor_state(indoc! {"
 3395        // Foo
 3396        //
 3397        // ˇ
 3398    "});
 3399        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3400        cx.set_state(indoc! {"
 3401        ˇ// Foo
 3402    "});
 3403        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3404        cx.assert_editor_state(indoc! {"
 3405
 3406        ˇ// Foo
 3407    "});
 3408    }
 3409    // Ensure that comment continuations can be disabled.
 3410    update_test_language_settings(cx, |settings| {
 3411        settings.defaults.extend_comment_on_newline = Some(false);
 3412    });
 3413    let mut cx = EditorTestContext::new(cx).await;
 3414    cx.set_state(indoc! {"
 3415        // Fooˇ
 3416    "});
 3417    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        // Foo
 3420        ˇ
 3421    "});
 3422}
 3423
 3424#[gpui::test]
 3425async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3426    init_test(cx, |settings| {
 3427        settings.defaults.tab_size = NonZeroU32::new(4)
 3428    });
 3429
 3430    let language = Arc::new(Language::new(
 3431        LanguageConfig {
 3432            line_comments: vec!["// ".into(), "/// ".into()],
 3433            ..LanguageConfig::default()
 3434        },
 3435        None,
 3436    ));
 3437    {
 3438        let mut cx = EditorTestContext::new(cx).await;
 3439        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3440        cx.set_state(indoc! {"
 3441        //ˇ
 3442    "});
 3443        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3444        cx.assert_editor_state(indoc! {"
 3445        //
 3446        // ˇ
 3447    "});
 3448
 3449        cx.set_state(indoc! {"
 3450        ///ˇ
 3451    "});
 3452        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3453        cx.assert_editor_state(indoc! {"
 3454        ///
 3455        /// ˇ
 3456    "});
 3457    }
 3458}
 3459
 3460#[gpui::test]
 3461async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3462    init_test(cx, |settings| {
 3463        settings.defaults.tab_size = NonZeroU32::new(4)
 3464    });
 3465
 3466    let language = Arc::new(
 3467        Language::new(
 3468            LanguageConfig {
 3469                documentation_comment: Some(language::BlockCommentConfig {
 3470                    start: "/**".into(),
 3471                    end: "*/".into(),
 3472                    prefix: "* ".into(),
 3473                    tab_size: 1,
 3474                }),
 3475
 3476                ..LanguageConfig::default()
 3477            },
 3478            Some(tree_sitter_rust::LANGUAGE.into()),
 3479        )
 3480        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3481        .unwrap(),
 3482    );
 3483
 3484    {
 3485        let mut cx = EditorTestContext::new(cx).await;
 3486        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3487        cx.set_state(indoc! {"
 3488        /**ˇ
 3489    "});
 3490
 3491        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492        cx.assert_editor_state(indoc! {"
 3493        /**
 3494         * ˇ
 3495    "});
 3496        // Ensure that if cursor is before the comment start,
 3497        // we do not actually insert a comment prefix.
 3498        cx.set_state(indoc! {"
 3499        ˇ/**
 3500    "});
 3501        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3502        cx.assert_editor_state(indoc! {"
 3503
 3504        ˇ/**
 3505    "});
 3506        // Ensure that if cursor is between it doesn't add comment prefix.
 3507        cx.set_state(indoc! {"
 3508        /*ˇ*
 3509    "});
 3510        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3511        cx.assert_editor_state(indoc! {"
 3512        /*
 3513        ˇ*
 3514    "});
 3515        // Ensure that if suffix exists on same line after cursor it adds new line.
 3516        cx.set_state(indoc! {"
 3517        /**ˇ*/
 3518    "});
 3519        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3520        cx.assert_editor_state(indoc! {"
 3521        /**
 3522         * ˇ
 3523         */
 3524    "});
 3525        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3526        cx.set_state(indoc! {"
 3527        /**ˇ */
 3528    "});
 3529        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3530        cx.assert_editor_state(indoc! {"
 3531        /**
 3532         * ˇ
 3533         */
 3534    "});
 3535        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3536        cx.set_state(indoc! {"
 3537        /** ˇ*/
 3538    "});
 3539        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3540        cx.assert_editor_state(
 3541            indoc! {"
 3542        /**s
 3543         * ˇ
 3544         */
 3545    "}
 3546            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3547            .as_str(),
 3548        );
 3549        // Ensure that delimiter space is preserved when newline on already
 3550        // spaced delimiter.
 3551        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3552        cx.assert_editor_state(
 3553            indoc! {"
 3554        /**s
 3555         *s
 3556         * ˇ
 3557         */
 3558    "}
 3559            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3560            .as_str(),
 3561        );
 3562        // Ensure that delimiter space is preserved when space is not
 3563        // on existing delimiter.
 3564        cx.set_state(indoc! {"
 3565        /**
 3566 3567         */
 3568    "});
 3569        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3570        cx.assert_editor_state(indoc! {"
 3571        /**
 3572         *
 3573         * ˇ
 3574         */
 3575    "});
 3576        // Ensure that if suffix exists on same line after cursor it
 3577        // doesn't add extra new line if prefix is not on same line.
 3578        cx.set_state(indoc! {"
 3579        /**
 3580        ˇ*/
 3581    "});
 3582        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3583        cx.assert_editor_state(indoc! {"
 3584        /**
 3585
 3586        ˇ*/
 3587    "});
 3588        // Ensure that it detects suffix after existing prefix.
 3589        cx.set_state(indoc! {"
 3590        /**ˇ/
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        /**
 3595        ˇ/
 3596    "});
 3597        // Ensure that if suffix exists on same line before
 3598        // cursor it does not add comment prefix.
 3599        cx.set_state(indoc! {"
 3600        /** */ˇ
 3601    "});
 3602        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3603        cx.assert_editor_state(indoc! {"
 3604        /** */
 3605        ˇ
 3606    "});
 3607        // Ensure that if suffix exists on same line before
 3608        // cursor it does not add comment prefix.
 3609        cx.set_state(indoc! {"
 3610        /**
 3611         *
 3612         */ˇ
 3613    "});
 3614        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3615        cx.assert_editor_state(indoc! {"
 3616        /**
 3617         *
 3618         */
 3619         ˇ
 3620    "});
 3621
 3622        // Ensure that inline comment followed by code
 3623        // doesn't add comment prefix on newline
 3624        cx.set_state(indoc! {"
 3625        /** */ textˇ
 3626    "});
 3627        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3628        cx.assert_editor_state(indoc! {"
 3629        /** */ text
 3630        ˇ
 3631    "});
 3632
 3633        // Ensure that text after comment end tag
 3634        // doesn't add comment prefix on newline
 3635        cx.set_state(indoc! {"
 3636        /**
 3637         *
 3638         */ˇtext
 3639    "});
 3640        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3641        cx.assert_editor_state(indoc! {"
 3642        /**
 3643         *
 3644         */
 3645         ˇtext
 3646    "});
 3647
 3648        // Ensure if not comment block it doesn't
 3649        // add comment prefix on newline
 3650        cx.set_state(indoc! {"
 3651        * textˇ
 3652    "});
 3653        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3654        cx.assert_editor_state(indoc! {"
 3655        * text
 3656        ˇ
 3657    "});
 3658    }
 3659    // Ensure that comment continuations can be disabled.
 3660    update_test_language_settings(cx, |settings| {
 3661        settings.defaults.extend_comment_on_newline = Some(false);
 3662    });
 3663    let mut cx = EditorTestContext::new(cx).await;
 3664    cx.set_state(indoc! {"
 3665        /**ˇ
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        /**
 3670        ˇ
 3671    "});
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3676    init_test(cx, |settings| {
 3677        settings.defaults.tab_size = NonZeroU32::new(4)
 3678    });
 3679
 3680    let lua_language = Arc::new(Language::new(
 3681        LanguageConfig {
 3682            line_comments: vec!["--".into()],
 3683            block_comment: Some(language::BlockCommentConfig {
 3684                start: "--[[".into(),
 3685                prefix: "".into(),
 3686                end: "]]".into(),
 3687                tab_size: 0,
 3688            }),
 3689            ..LanguageConfig::default()
 3690        },
 3691        None,
 3692    ));
 3693
 3694    let mut cx = EditorTestContext::new(cx).await;
 3695    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3696
 3697    // Line with line comment should extend
 3698    cx.set_state(indoc! {"
 3699        --ˇ
 3700    "});
 3701    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3702    cx.assert_editor_state(indoc! {"
 3703        --
 3704        --ˇ
 3705    "});
 3706
 3707    // Line with block comment that matches line comment should not extend
 3708    cx.set_state(indoc! {"
 3709        --[[ˇ
 3710    "});
 3711    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3712    cx.assert_editor_state(indoc! {"
 3713        --[[
 3714        ˇ
 3715    "});
 3716}
 3717
 3718#[gpui::test]
 3719fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3720    init_test(cx, |_| {});
 3721
 3722    let editor = cx.add_window(|window, cx| {
 3723        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3724        let mut editor = build_editor(buffer, window, cx);
 3725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3726            s.select_ranges([
 3727                MultiBufferOffset(3)..MultiBufferOffset(4),
 3728                MultiBufferOffset(11)..MultiBufferOffset(12),
 3729                MultiBufferOffset(19)..MultiBufferOffset(20),
 3730            ])
 3731        });
 3732        editor
 3733    });
 3734
 3735    _ = editor.update(cx, |editor, window, cx| {
 3736        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3737        editor.buffer.update(cx, |buffer, cx| {
 3738            buffer.edit(
 3739                [
 3740                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3741                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3742                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3743                ],
 3744                None,
 3745                cx,
 3746            );
 3747            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3748        });
 3749        assert_eq!(
 3750            editor.selections.ranges(&editor.display_snapshot(cx)),
 3751            &[
 3752                MultiBufferOffset(2)..MultiBufferOffset(2),
 3753                MultiBufferOffset(7)..MultiBufferOffset(7),
 3754                MultiBufferOffset(12)..MultiBufferOffset(12)
 3755            ],
 3756        );
 3757
 3758        editor.insert("Z", window, cx);
 3759        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3760
 3761        // The selections are moved after the inserted characters
 3762        assert_eq!(
 3763            editor.selections.ranges(&editor.display_snapshot(cx)),
 3764            &[
 3765                MultiBufferOffset(3)..MultiBufferOffset(3),
 3766                MultiBufferOffset(9)..MultiBufferOffset(9),
 3767                MultiBufferOffset(15)..MultiBufferOffset(15)
 3768            ],
 3769        );
 3770    });
 3771}
 3772
 3773#[gpui::test]
 3774async fn test_tab(cx: &mut TestAppContext) {
 3775    init_test(cx, |settings| {
 3776        settings.defaults.tab_size = NonZeroU32::new(3)
 3777    });
 3778
 3779    let mut cx = EditorTestContext::new(cx).await;
 3780    cx.set_state(indoc! {"
 3781        ˇabˇc
 3782        ˇ🏀ˇ🏀ˇefg
 3783 3784    "});
 3785    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3786    cx.assert_editor_state(indoc! {"
 3787           ˇab ˇc
 3788           ˇ🏀  ˇ🏀  ˇefg
 3789        d  ˇ
 3790    "});
 3791
 3792    cx.set_state(indoc! {"
 3793        a
 3794        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3795    "});
 3796    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3797    cx.assert_editor_state(indoc! {"
 3798        a
 3799           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3800    "});
 3801}
 3802
 3803#[gpui::test]
 3804async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3805    init_test(cx, |_| {});
 3806
 3807    let mut cx = EditorTestContext::new(cx).await;
 3808    let language = Arc::new(
 3809        Language::new(
 3810            LanguageConfig::default(),
 3811            Some(tree_sitter_rust::LANGUAGE.into()),
 3812        )
 3813        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3814        .unwrap(),
 3815    );
 3816    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3817
 3818    // test when all cursors are not at suggested indent
 3819    // then simply move to their suggested indent location
 3820    cx.set_state(indoc! {"
 3821        const a: B = (
 3822            c(
 3823        ˇ
 3824        ˇ    )
 3825        );
 3826    "});
 3827    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3828    cx.assert_editor_state(indoc! {"
 3829        const a: B = (
 3830            c(
 3831                ˇ
 3832            ˇ)
 3833        );
 3834    "});
 3835
 3836    // test cursor already at suggested indent not moving when
 3837    // other cursors are yet to reach their suggested indents
 3838    cx.set_state(indoc! {"
 3839        ˇ
 3840        const a: B = (
 3841            c(
 3842                d(
 3843        ˇ
 3844                )
 3845        ˇ
 3846        ˇ    )
 3847        );
 3848    "});
 3849    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        ˇ
 3852        const a: B = (
 3853            c(
 3854                d(
 3855                    ˇ
 3856                )
 3857                ˇ
 3858            ˇ)
 3859        );
 3860    "});
 3861    // test when all cursors are at suggested indent then tab is inserted
 3862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3863    cx.assert_editor_state(indoc! {"
 3864            ˇ
 3865        const a: B = (
 3866            c(
 3867                d(
 3868                        ˇ
 3869                )
 3870                    ˇ
 3871                ˇ)
 3872        );
 3873    "});
 3874
 3875    // test when current indent is less than suggested indent,
 3876    // we adjust line to match suggested indent and move cursor to it
 3877    //
 3878    // when no other cursor is at word boundary, all of them should move
 3879    cx.set_state(indoc! {"
 3880        const a: B = (
 3881            c(
 3882                d(
 3883        ˇ
 3884        ˇ   )
 3885        ˇ   )
 3886        );
 3887    "});
 3888    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3889    cx.assert_editor_state(indoc! {"
 3890        const a: B = (
 3891            c(
 3892                d(
 3893                    ˇ
 3894                ˇ)
 3895            ˇ)
 3896        );
 3897    "});
 3898
 3899    // test when current indent is less than suggested indent,
 3900    // we adjust line to match suggested indent and move cursor to it
 3901    //
 3902    // when some other cursor is at word boundary, it should not move
 3903    cx.set_state(indoc! {"
 3904        const a: B = (
 3905            c(
 3906                d(
 3907        ˇ
 3908        ˇ   )
 3909           ˇ)
 3910        );
 3911    "});
 3912    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3913    cx.assert_editor_state(indoc! {"
 3914        const a: B = (
 3915            c(
 3916                d(
 3917                    ˇ
 3918                ˇ)
 3919            ˇ)
 3920        );
 3921    "});
 3922
 3923    // test when current indent is more than suggested indent,
 3924    // we just move cursor to current indent instead of suggested indent
 3925    //
 3926    // when no other cursor is at word boundary, all of them should move
 3927    cx.set_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930                d(
 3931        ˇ
 3932        ˇ                )
 3933        ˇ   )
 3934        );
 3935    "});
 3936    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3937    cx.assert_editor_state(indoc! {"
 3938        const a: B = (
 3939            c(
 3940                d(
 3941                    ˇ
 3942                        ˇ)
 3943            ˇ)
 3944        );
 3945    "});
 3946    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3947    cx.assert_editor_state(indoc! {"
 3948        const a: B = (
 3949            c(
 3950                d(
 3951                        ˇ
 3952                            ˇ)
 3953                ˇ)
 3954        );
 3955    "});
 3956
 3957    // test when current indent is more than suggested indent,
 3958    // we just move cursor to current indent instead of suggested indent
 3959    //
 3960    // when some other cursor is at word boundary, it doesn't move
 3961    cx.set_state(indoc! {"
 3962        const a: B = (
 3963            c(
 3964                d(
 3965        ˇ
 3966        ˇ                )
 3967            ˇ)
 3968        );
 3969    "});
 3970    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3971    cx.assert_editor_state(indoc! {"
 3972        const a: B = (
 3973            c(
 3974                d(
 3975                    ˇ
 3976                        ˇ)
 3977            ˇ)
 3978        );
 3979    "});
 3980
 3981    // handle auto-indent when there are multiple cursors on the same line
 3982    cx.set_state(indoc! {"
 3983        const a: B = (
 3984            c(
 3985        ˇ    ˇ
 3986        ˇ    )
 3987        );
 3988    "});
 3989    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3990    cx.assert_editor_state(indoc! {"
 3991        const a: B = (
 3992            c(
 3993                ˇ
 3994            ˇ)
 3995        );
 3996    "});
 3997}
 3998
 3999#[gpui::test]
 4000async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4001    init_test(cx, |settings| {
 4002        settings.defaults.tab_size = NonZeroU32::new(3)
 4003    });
 4004
 4005    let mut cx = EditorTestContext::new(cx).await;
 4006    cx.set_state(indoc! {"
 4007         ˇ
 4008        \t ˇ
 4009        \t  ˇ
 4010        \t   ˇ
 4011         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4012    "});
 4013
 4014    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4015    cx.assert_editor_state(indoc! {"
 4016           ˇ
 4017        \t   ˇ
 4018        \t   ˇ
 4019        \t      ˇ
 4020         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4021    "});
 4022}
 4023
 4024#[gpui::test]
 4025async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4026    init_test(cx, |settings| {
 4027        settings.defaults.tab_size = NonZeroU32::new(4)
 4028    });
 4029
 4030    let language = Arc::new(
 4031        Language::new(
 4032            LanguageConfig::default(),
 4033            Some(tree_sitter_rust::LANGUAGE.into()),
 4034        )
 4035        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4036        .unwrap(),
 4037    );
 4038
 4039    let mut cx = EditorTestContext::new(cx).await;
 4040    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4041    cx.set_state(indoc! {"
 4042        fn a() {
 4043            if b {
 4044        \t ˇc
 4045            }
 4046        }
 4047    "});
 4048
 4049    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4050    cx.assert_editor_state(indoc! {"
 4051        fn a() {
 4052            if b {
 4053                ˇc
 4054            }
 4055        }
 4056    "});
 4057}
 4058
 4059#[gpui::test]
 4060async fn test_indent_outdent(cx: &mut TestAppContext) {
 4061    init_test(cx, |settings| {
 4062        settings.defaults.tab_size = NonZeroU32::new(4);
 4063    });
 4064
 4065    let mut cx = EditorTestContext::new(cx).await;
 4066
 4067    cx.set_state(indoc! {"
 4068          «oneˇ» «twoˇ»
 4069        three
 4070         four
 4071    "});
 4072    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4073    cx.assert_editor_state(indoc! {"
 4074            «oneˇ» «twoˇ»
 4075        three
 4076         four
 4077    "});
 4078
 4079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4080    cx.assert_editor_state(indoc! {"
 4081        «oneˇ» «twoˇ»
 4082        three
 4083         four
 4084    "});
 4085
 4086    // select across line ending
 4087    cx.set_state(indoc! {"
 4088        one two
 4089        t«hree
 4090        ˇ» four
 4091    "});
 4092    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4093    cx.assert_editor_state(indoc! {"
 4094        one two
 4095            t«hree
 4096        ˇ» four
 4097    "});
 4098
 4099    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4100    cx.assert_editor_state(indoc! {"
 4101        one two
 4102        t«hree
 4103        ˇ» four
 4104    "});
 4105
 4106    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4107    cx.set_state(indoc! {"
 4108        one two
 4109        ˇthree
 4110            four
 4111    "});
 4112    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4113    cx.assert_editor_state(indoc! {"
 4114        one two
 4115            ˇthree
 4116            four
 4117    "});
 4118
 4119    cx.set_state(indoc! {"
 4120        one two
 4121        ˇ    three
 4122            four
 4123    "});
 4124    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4125    cx.assert_editor_state(indoc! {"
 4126        one two
 4127        ˇthree
 4128            four
 4129    "});
 4130}
 4131
 4132#[gpui::test]
 4133async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4134    // This is a regression test for issue #33761
 4135    init_test(cx, |_| {});
 4136
 4137    let mut cx = EditorTestContext::new(cx).await;
 4138    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4140
 4141    cx.set_state(
 4142        r#"ˇ#     ingress:
 4143ˇ#         api:
 4144ˇ#             enabled: false
 4145ˇ#             pathType: Prefix
 4146ˇ#           console:
 4147ˇ#               enabled: false
 4148ˇ#               pathType: Prefix
 4149"#,
 4150    );
 4151
 4152    // Press tab to indent all lines
 4153    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4154
 4155    cx.assert_editor_state(
 4156        r#"    ˇ#     ingress:
 4157    ˇ#         api:
 4158    ˇ#             enabled: false
 4159    ˇ#             pathType: Prefix
 4160    ˇ#           console:
 4161    ˇ#               enabled: false
 4162    ˇ#               pathType: Prefix
 4163"#,
 4164    );
 4165}
 4166
 4167#[gpui::test]
 4168async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4169    // This is a test to make sure our fix for issue #33761 didn't break anything
 4170    init_test(cx, |_| {});
 4171
 4172    let mut cx = EditorTestContext::new(cx).await;
 4173    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4175
 4176    cx.set_state(
 4177        r#"ˇingress:
 4178ˇ  api:
 4179ˇ    enabled: false
 4180ˇ    pathType: Prefix
 4181"#,
 4182    );
 4183
 4184    // Press tab to indent all lines
 4185    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4186
 4187    cx.assert_editor_state(
 4188        r#"ˇingress:
 4189    ˇapi:
 4190        ˇenabled: false
 4191        ˇpathType: Prefix
 4192"#,
 4193    );
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4198    init_test(cx, |settings| {
 4199        settings.defaults.hard_tabs = Some(true);
 4200    });
 4201
 4202    let mut cx = EditorTestContext::new(cx).await;
 4203
 4204    // select two ranges on one line
 4205    cx.set_state(indoc! {"
 4206        «oneˇ» «twoˇ»
 4207        three
 4208        four
 4209    "});
 4210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4211    cx.assert_editor_state(indoc! {"
 4212        \t«oneˇ» «twoˇ»
 4213        three
 4214        four
 4215    "});
 4216    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4217    cx.assert_editor_state(indoc! {"
 4218        \t\t«oneˇ» «twoˇ»
 4219        three
 4220        four
 4221    "});
 4222    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4223    cx.assert_editor_state(indoc! {"
 4224        \t«oneˇ» «twoˇ»
 4225        three
 4226        four
 4227    "});
 4228    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4229    cx.assert_editor_state(indoc! {"
 4230        «oneˇ» «twoˇ»
 4231        three
 4232        four
 4233    "});
 4234
 4235    // select across a line ending
 4236    cx.set_state(indoc! {"
 4237        one two
 4238        t«hree
 4239        ˇ»four
 4240    "});
 4241    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4242    cx.assert_editor_state(indoc! {"
 4243        one two
 4244        \tt«hree
 4245        ˇ»four
 4246    "});
 4247    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4248    cx.assert_editor_state(indoc! {"
 4249        one two
 4250        \t\tt«hree
 4251        ˇ»four
 4252    "});
 4253    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4254    cx.assert_editor_state(indoc! {"
 4255        one two
 4256        \tt«hree
 4257        ˇ»four
 4258    "});
 4259    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4260    cx.assert_editor_state(indoc! {"
 4261        one two
 4262        t«hree
 4263        ˇ»four
 4264    "});
 4265
 4266    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4267    cx.set_state(indoc! {"
 4268        one two
 4269        ˇthree
 4270        four
 4271    "});
 4272    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4273    cx.assert_editor_state(indoc! {"
 4274        one two
 4275        ˇthree
 4276        four
 4277    "});
 4278    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4279    cx.assert_editor_state(indoc! {"
 4280        one two
 4281        \tˇthree
 4282        four
 4283    "});
 4284    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4285    cx.assert_editor_state(indoc! {"
 4286        one two
 4287        ˇthree
 4288        four
 4289    "});
 4290}
 4291
 4292#[gpui::test]
 4293fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4294    init_test(cx, |settings| {
 4295        settings.languages.0.extend([
 4296            (
 4297                "TOML".into(),
 4298                LanguageSettingsContent {
 4299                    tab_size: NonZeroU32::new(2),
 4300                    ..Default::default()
 4301                },
 4302            ),
 4303            (
 4304                "Rust".into(),
 4305                LanguageSettingsContent {
 4306                    tab_size: NonZeroU32::new(4),
 4307                    ..Default::default()
 4308                },
 4309            ),
 4310        ]);
 4311    });
 4312
 4313    let toml_language = Arc::new(Language::new(
 4314        LanguageConfig {
 4315            name: "TOML".into(),
 4316            ..Default::default()
 4317        },
 4318        None,
 4319    ));
 4320    let rust_language = Arc::new(Language::new(
 4321        LanguageConfig {
 4322            name: "Rust".into(),
 4323            ..Default::default()
 4324        },
 4325        None,
 4326    ));
 4327
 4328    let toml_buffer =
 4329        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4330    let rust_buffer =
 4331        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4332    let multibuffer = cx.new(|cx| {
 4333        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4334        multibuffer.push_excerpts(
 4335            toml_buffer.clone(),
 4336            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4337            cx,
 4338        );
 4339        multibuffer.push_excerpts(
 4340            rust_buffer.clone(),
 4341            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4342            cx,
 4343        );
 4344        multibuffer
 4345    });
 4346
 4347    cx.add_window(|window, cx| {
 4348        let mut editor = build_editor(multibuffer, window, cx);
 4349
 4350        assert_eq!(
 4351            editor.text(cx),
 4352            indoc! {"
 4353                a = 1
 4354                b = 2
 4355
 4356                const c: usize = 3;
 4357            "}
 4358        );
 4359
 4360        select_ranges(
 4361            &mut editor,
 4362            indoc! {"
 4363                «aˇ» = 1
 4364                b = 2
 4365
 4366                «const c:ˇ» usize = 3;
 4367            "},
 4368            window,
 4369            cx,
 4370        );
 4371
 4372        editor.tab(&Tab, window, cx);
 4373        assert_text_with_selections(
 4374            &mut editor,
 4375            indoc! {"
 4376                  «aˇ» = 1
 4377                b = 2
 4378
 4379                    «const c:ˇ» usize = 3;
 4380            "},
 4381            cx,
 4382        );
 4383        editor.backtab(&Backtab, window, cx);
 4384        assert_text_with_selections(
 4385            &mut editor,
 4386            indoc! {"
 4387                «aˇ» = 1
 4388                b = 2
 4389
 4390                «const c:ˇ» usize = 3;
 4391            "},
 4392            cx,
 4393        );
 4394
 4395        editor
 4396    });
 4397}
 4398
 4399#[gpui::test]
 4400async fn test_backspace(cx: &mut TestAppContext) {
 4401    init_test(cx, |_| {});
 4402
 4403    let mut cx = EditorTestContext::new(cx).await;
 4404
 4405    // Basic backspace
 4406    cx.set_state(indoc! {"
 4407        onˇe two three
 4408        fou«rˇ» five six
 4409        seven «ˇeight nine
 4410        »ten
 4411    "});
 4412    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4413    cx.assert_editor_state(indoc! {"
 4414        oˇe two three
 4415        fouˇ five six
 4416        seven ˇten
 4417    "});
 4418
 4419    // Test backspace inside and around indents
 4420    cx.set_state(indoc! {"
 4421        zero
 4422            ˇone
 4423                ˇtwo
 4424            ˇ ˇ ˇ  three
 4425        ˇ  ˇ  four
 4426    "});
 4427    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4428    cx.assert_editor_state(indoc! {"
 4429        zero
 4430        ˇone
 4431            ˇtwo
 4432        ˇ  threeˇ  four
 4433    "});
 4434}
 4435
 4436#[gpui::test]
 4437async fn test_delete(cx: &mut TestAppContext) {
 4438    init_test(cx, |_| {});
 4439
 4440    let mut cx = EditorTestContext::new(cx).await;
 4441    cx.set_state(indoc! {"
 4442        onˇe two three
 4443        fou«rˇ» five six
 4444        seven «ˇeight nine
 4445        »ten
 4446    "});
 4447    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4448    cx.assert_editor_state(indoc! {"
 4449        onˇ two three
 4450        fouˇ five six
 4451        seven ˇten
 4452    "});
 4453}
 4454
 4455#[gpui::test]
 4456fn test_delete_line(cx: &mut TestAppContext) {
 4457    init_test(cx, |_| {});
 4458
 4459    let editor = cx.add_window(|window, cx| {
 4460        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4461        build_editor(buffer, window, cx)
 4462    });
 4463    _ = editor.update(cx, |editor, window, cx| {
 4464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4465            s.select_display_ranges([
 4466                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4467                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4468                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4469            ])
 4470        });
 4471        editor.delete_line(&DeleteLine, window, cx);
 4472        assert_eq!(editor.display_text(cx), "ghi");
 4473        assert_eq!(
 4474            display_ranges(editor, cx),
 4475            vec![
 4476                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4477                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4478            ]
 4479        );
 4480    });
 4481
 4482    let editor = cx.add_window(|window, cx| {
 4483        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4484        build_editor(buffer, window, cx)
 4485    });
 4486    _ = editor.update(cx, |editor, window, cx| {
 4487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4488            s.select_display_ranges([
 4489                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4490            ])
 4491        });
 4492        editor.delete_line(&DeleteLine, window, cx);
 4493        assert_eq!(editor.display_text(cx), "ghi\n");
 4494        assert_eq!(
 4495            display_ranges(editor, cx),
 4496            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4497        );
 4498    });
 4499
 4500    let editor = cx.add_window(|window, cx| {
 4501        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4502        build_editor(buffer, window, cx)
 4503    });
 4504    _ = editor.update(cx, |editor, window, cx| {
 4505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4506            s.select_display_ranges([
 4507                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4508            ])
 4509        });
 4510        editor.delete_line(&DeleteLine, window, cx);
 4511        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4512        assert_eq!(
 4513            display_ranges(editor, cx),
 4514            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4515        );
 4516    });
 4517}
 4518
 4519#[gpui::test]
 4520fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4521    init_test(cx, |_| {});
 4522
 4523    cx.add_window(|window, cx| {
 4524        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4525        let mut editor = build_editor(buffer.clone(), window, cx);
 4526        let buffer = buffer.read(cx).as_singleton().unwrap();
 4527
 4528        assert_eq!(
 4529            editor
 4530                .selections
 4531                .ranges::<Point>(&editor.display_snapshot(cx)),
 4532            &[Point::new(0, 0)..Point::new(0, 0)]
 4533        );
 4534
 4535        // When on single line, replace newline at end by space
 4536        editor.join_lines(&JoinLines, window, cx);
 4537        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4538        assert_eq!(
 4539            editor
 4540                .selections
 4541                .ranges::<Point>(&editor.display_snapshot(cx)),
 4542            &[Point::new(0, 3)..Point::new(0, 3)]
 4543        );
 4544
 4545        // When multiple lines are selected, remove newlines that are spanned by the selection
 4546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4547            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4548        });
 4549        editor.join_lines(&JoinLines, window, cx);
 4550        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4551        assert_eq!(
 4552            editor
 4553                .selections
 4554                .ranges::<Point>(&editor.display_snapshot(cx)),
 4555            &[Point::new(0, 11)..Point::new(0, 11)]
 4556        );
 4557
 4558        // Undo should be transactional
 4559        editor.undo(&Undo, window, cx);
 4560        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4561        assert_eq!(
 4562            editor
 4563                .selections
 4564                .ranges::<Point>(&editor.display_snapshot(cx)),
 4565            &[Point::new(0, 5)..Point::new(2, 2)]
 4566        );
 4567
 4568        // When joining an empty line don't insert a space
 4569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4570            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4571        });
 4572        editor.join_lines(&JoinLines, window, cx);
 4573        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4574        assert_eq!(
 4575            editor
 4576                .selections
 4577                .ranges::<Point>(&editor.display_snapshot(cx)),
 4578            [Point::new(2, 3)..Point::new(2, 3)]
 4579        );
 4580
 4581        // We can remove trailing newlines
 4582        editor.join_lines(&JoinLines, window, cx);
 4583        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4584        assert_eq!(
 4585            editor
 4586                .selections
 4587                .ranges::<Point>(&editor.display_snapshot(cx)),
 4588            [Point::new(2, 3)..Point::new(2, 3)]
 4589        );
 4590
 4591        // We don't blow up on the last line
 4592        editor.join_lines(&JoinLines, window, cx);
 4593        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4594        assert_eq!(
 4595            editor
 4596                .selections
 4597                .ranges::<Point>(&editor.display_snapshot(cx)),
 4598            [Point::new(2, 3)..Point::new(2, 3)]
 4599        );
 4600
 4601        // reset to test indentation
 4602        editor.buffer.update(cx, |buffer, cx| {
 4603            buffer.edit(
 4604                [
 4605                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4606                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4607                ],
 4608                None,
 4609                cx,
 4610            )
 4611        });
 4612
 4613        // We remove any leading spaces
 4614        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4616            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4617        });
 4618        editor.join_lines(&JoinLines, window, cx);
 4619        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4620
 4621        // We don't insert a space for a line containing only spaces
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4624
 4625        // We ignore any leading tabs
 4626        editor.join_lines(&JoinLines, window, cx);
 4627        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4628
 4629        editor
 4630    });
 4631}
 4632
 4633#[gpui::test]
 4634fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4635    init_test(cx, |_| {});
 4636
 4637    cx.add_window(|window, cx| {
 4638        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4639        let mut editor = build_editor(buffer.clone(), window, cx);
 4640        let buffer = buffer.read(cx).as_singleton().unwrap();
 4641
 4642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4643            s.select_ranges([
 4644                Point::new(0, 2)..Point::new(1, 1),
 4645                Point::new(1, 2)..Point::new(1, 2),
 4646                Point::new(3, 1)..Point::new(3, 2),
 4647            ])
 4648        });
 4649
 4650        editor.join_lines(&JoinLines, window, cx);
 4651        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4652
 4653        assert_eq!(
 4654            editor
 4655                .selections
 4656                .ranges::<Point>(&editor.display_snapshot(cx)),
 4657            [
 4658                Point::new(0, 7)..Point::new(0, 7),
 4659                Point::new(1, 3)..Point::new(1, 3)
 4660            ]
 4661        );
 4662        editor
 4663    });
 4664}
 4665
 4666#[gpui::test]
 4667async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4668    init_test(cx, |_| {});
 4669
 4670    let mut cx = EditorTestContext::new(cx).await;
 4671
 4672    let diff_base = r#"
 4673        Line 0
 4674        Line 1
 4675        Line 2
 4676        Line 3
 4677        "#
 4678    .unindent();
 4679
 4680    cx.set_state(
 4681        &r#"
 4682        ˇLine 0
 4683        Line 1
 4684        Line 2
 4685        Line 3
 4686        "#
 4687        .unindent(),
 4688    );
 4689
 4690    cx.set_head_text(&diff_base);
 4691    executor.run_until_parked();
 4692
 4693    // Join lines
 4694    cx.update_editor(|editor, window, cx| {
 4695        editor.join_lines(&JoinLines, window, cx);
 4696    });
 4697    executor.run_until_parked();
 4698
 4699    cx.assert_editor_state(
 4700        &r#"
 4701        Line 0ˇ Line 1
 4702        Line 2
 4703        Line 3
 4704        "#
 4705        .unindent(),
 4706    );
 4707    // Join again
 4708    cx.update_editor(|editor, window, cx| {
 4709        editor.join_lines(&JoinLines, window, cx);
 4710    });
 4711    executor.run_until_parked();
 4712
 4713    cx.assert_editor_state(
 4714        &r#"
 4715        Line 0 Line 1ˇ Line 2
 4716        Line 3
 4717        "#
 4718        .unindent(),
 4719    );
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_custom_newlines_cause_no_false_positive_diffs(
 4724    executor: BackgroundExecutor,
 4725    cx: &mut TestAppContext,
 4726) {
 4727    init_test(cx, |_| {});
 4728    let mut cx = EditorTestContext::new(cx).await;
 4729    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4730    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4731    executor.run_until_parked();
 4732
 4733    cx.update_editor(|editor, window, cx| {
 4734        let snapshot = editor.snapshot(window, cx);
 4735        assert_eq!(
 4736            snapshot
 4737                .buffer_snapshot()
 4738                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4739                .collect::<Vec<_>>(),
 4740            Vec::new(),
 4741            "Should not have any diffs for files with custom newlines"
 4742        );
 4743    });
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    // Test sort_lines_case_insensitive()
 4753    cx.set_state(indoc! {"
 4754        «z
 4755        y
 4756        x
 4757        Z
 4758        Y
 4759        Xˇ»
 4760    "});
 4761    cx.update_editor(|e, window, cx| {
 4762        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4763    });
 4764    cx.assert_editor_state(indoc! {"
 4765        «x
 4766        X
 4767        y
 4768        Y
 4769        z
 4770        Zˇ»
 4771    "});
 4772
 4773    // Test sort_lines_by_length()
 4774    //
 4775    // Demonstrates:
 4776    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4777    // - sort is stable
 4778    cx.set_state(indoc! {"
 4779        «123
 4780        æ
 4781        12
 4782 4783        1
 4784        æˇ»
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «æ
 4789 4790        1
 4791        æ
 4792        12
 4793        123ˇ»
 4794    "});
 4795
 4796    // Test reverse_lines()
 4797    cx.set_state(indoc! {"
 4798        «5
 4799        4
 4800        3
 4801        2
 4802        1ˇ»
 4803    "});
 4804    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4805    cx.assert_editor_state(indoc! {"
 4806        «1
 4807        2
 4808        3
 4809        4
 4810        5ˇ»
 4811    "});
 4812
 4813    // Skip testing shuffle_line()
 4814
 4815    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4816    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4817
 4818    // Don't manipulate when cursor is on single line, but expand the selection
 4819    cx.set_state(indoc! {"
 4820        ddˇdd
 4821        ccc
 4822        bb
 4823        a
 4824    "});
 4825    cx.update_editor(|e, window, cx| {
 4826        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4827    });
 4828    cx.assert_editor_state(indoc! {"
 4829        «ddddˇ»
 4830        ccc
 4831        bb
 4832        a
 4833    "});
 4834
 4835    // Basic manipulate case
 4836    // Start selection moves to column 0
 4837    // End of selection shrinks to fit shorter line
 4838    cx.set_state(indoc! {"
 4839        dd«d
 4840        ccc
 4841        bb
 4842        aaaaaˇ»
 4843    "});
 4844    cx.update_editor(|e, window, cx| {
 4845        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4846    });
 4847    cx.assert_editor_state(indoc! {"
 4848        «aaaaa
 4849        bb
 4850        ccc
 4851        dddˇ»
 4852    "});
 4853
 4854    // Manipulate case with newlines
 4855    cx.set_state(indoc! {"
 4856        dd«d
 4857        ccc
 4858
 4859        bb
 4860        aaaaa
 4861
 4862        ˇ»
 4863    "});
 4864    cx.update_editor(|e, window, cx| {
 4865        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4866    });
 4867    cx.assert_editor_state(indoc! {"
 4868        «
 4869
 4870        aaaaa
 4871        bb
 4872        ccc
 4873        dddˇ»
 4874
 4875    "});
 4876
 4877    // Adding new line
 4878    cx.set_state(indoc! {"
 4879        aa«a
 4880        bbˇ»b
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «aaa
 4887        bbb
 4888        added_lineˇ»
 4889    "});
 4890
 4891    // Removing line
 4892    cx.set_state(indoc! {"
 4893        aa«a
 4894        bbbˇ»
 4895    "});
 4896    cx.update_editor(|e, window, cx| {
 4897        e.manipulate_immutable_lines(window, cx, |lines| {
 4898            lines.pop();
 4899        })
 4900    });
 4901    cx.assert_editor_state(indoc! {"
 4902        «aaaˇ»
 4903    "});
 4904
 4905    // Removing all lines
 4906    cx.set_state(indoc! {"
 4907        aa«a
 4908        bbbˇ»
 4909    "});
 4910    cx.update_editor(|e, window, cx| {
 4911        e.manipulate_immutable_lines(window, cx, |lines| {
 4912            lines.drain(..);
 4913        })
 4914    });
 4915    cx.assert_editor_state(indoc! {"
 4916        ˇ
 4917    "});
 4918}
 4919
 4920#[gpui::test]
 4921async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4922    init_test(cx, |_| {});
 4923
 4924    let mut cx = EditorTestContext::new(cx).await;
 4925
 4926    // Consider continuous selection as single selection
 4927    cx.set_state(indoc! {"
 4928        Aaa«aa
 4929        cˇ»c«c
 4930        bb
 4931        aaaˇ»aa
 4932    "});
 4933    cx.update_editor(|e, window, cx| {
 4934        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4935    });
 4936    cx.assert_editor_state(indoc! {"
 4937        «Aaaaa
 4938        ccc
 4939        bb
 4940        aaaaaˇ»
 4941    "});
 4942
 4943    cx.set_state(indoc! {"
 4944        Aaa«aa
 4945        cˇ»c«c
 4946        bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4951    });
 4952    cx.assert_editor_state(indoc! {"
 4953        «Aaaaa
 4954        ccc
 4955        bbˇ»
 4956    "});
 4957
 4958    // Consider non continuous selection as distinct dedup operations
 4959    cx.set_state(indoc! {"
 4960        «aaaaa
 4961        bb
 4962        aaaaa
 4963        aaaaaˇ»
 4964
 4965        aaa«aaˇ»
 4966    "});
 4967    cx.update_editor(|e, window, cx| {
 4968        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4969    });
 4970    cx.assert_editor_state(indoc! {"
 4971        «aaaaa
 4972        bbˇ»
 4973
 4974        «aaaaaˇ»
 4975    "});
 4976}
 4977
 4978#[gpui::test]
 4979async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4980    init_test(cx, |_| {});
 4981
 4982    let mut cx = EditorTestContext::new(cx).await;
 4983
 4984    cx.set_state(indoc! {"
 4985        «Aaa
 4986        aAa
 4987        Aaaˇ»
 4988    "});
 4989    cx.update_editor(|e, window, cx| {
 4990        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4991    });
 4992    cx.assert_editor_state(indoc! {"
 4993        «Aaa
 4994        aAaˇ»
 4995    "});
 4996
 4997    cx.set_state(indoc! {"
 4998        «Aaa
 4999        aAa
 5000        aaAˇ»
 5001    "});
 5002    cx.update_editor(|e, window, cx| {
 5003        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5004    });
 5005    cx.assert_editor_state(indoc! {"
 5006        «Aaaˇ»
 5007    "});
 5008}
 5009
 5010#[gpui::test]
 5011async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5012    init_test(cx, |_| {});
 5013
 5014    let mut cx = EditorTestContext::new(cx).await;
 5015
 5016    let js_language = Arc::new(Language::new(
 5017        LanguageConfig {
 5018            name: "JavaScript".into(),
 5019            wrap_characters: Some(language::WrapCharactersConfig {
 5020                start_prefix: "<".into(),
 5021                start_suffix: ">".into(),
 5022                end_prefix: "</".into(),
 5023                end_suffix: ">".into(),
 5024            }),
 5025            ..LanguageConfig::default()
 5026        },
 5027        None,
 5028    ));
 5029
 5030    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5031
 5032    cx.set_state(indoc! {"
 5033        «testˇ»
 5034    "});
 5035    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5036    cx.assert_editor_state(indoc! {"
 5037        <«ˇ»>test</«ˇ»>
 5038    "});
 5039
 5040    cx.set_state(indoc! {"
 5041        «test
 5042         testˇ»
 5043    "});
 5044    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5045    cx.assert_editor_state(indoc! {"
 5046        <«ˇ»>test
 5047         test</«ˇ»>
 5048    "});
 5049
 5050    cx.set_state(indoc! {"
 5051        teˇst
 5052    "});
 5053    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5054    cx.assert_editor_state(indoc! {"
 5055        te<«ˇ»></«ˇ»>st
 5056    "});
 5057}
 5058
 5059#[gpui::test]
 5060async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5061    init_test(cx, |_| {});
 5062
 5063    let mut cx = EditorTestContext::new(cx).await;
 5064
 5065    let js_language = Arc::new(Language::new(
 5066        LanguageConfig {
 5067            name: "JavaScript".into(),
 5068            wrap_characters: Some(language::WrapCharactersConfig {
 5069                start_prefix: "<".into(),
 5070                start_suffix: ">".into(),
 5071                end_prefix: "</".into(),
 5072                end_suffix: ">".into(),
 5073            }),
 5074            ..LanguageConfig::default()
 5075        },
 5076        None,
 5077    ));
 5078
 5079    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5080
 5081    cx.set_state(indoc! {"
 5082        «testˇ»
 5083        «testˇ» «testˇ»
 5084        «testˇ»
 5085    "});
 5086    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5087    cx.assert_editor_state(indoc! {"
 5088        <«ˇ»>test</«ˇ»>
 5089        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5090        <«ˇ»>test</«ˇ»>
 5091    "});
 5092
 5093    cx.set_state(indoc! {"
 5094        «test
 5095         testˇ»
 5096        «test
 5097         testˇ»
 5098    "});
 5099    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5100    cx.assert_editor_state(indoc! {"
 5101        <«ˇ»>test
 5102         test</«ˇ»>
 5103        <«ˇ»>test
 5104         test</«ˇ»>
 5105    "});
 5106}
 5107
 5108#[gpui::test]
 5109async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5110    init_test(cx, |_| {});
 5111
 5112    let mut cx = EditorTestContext::new(cx).await;
 5113
 5114    let plaintext_language = Arc::new(Language::new(
 5115        LanguageConfig {
 5116            name: "Plain Text".into(),
 5117            ..LanguageConfig::default()
 5118        },
 5119        None,
 5120    ));
 5121
 5122    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5123
 5124    cx.set_state(indoc! {"
 5125        «testˇ»
 5126    "});
 5127    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5128    cx.assert_editor_state(indoc! {"
 5129      «testˇ»
 5130    "});
 5131}
 5132
 5133#[gpui::test]
 5134async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5135    init_test(cx, |_| {});
 5136
 5137    let mut cx = EditorTestContext::new(cx).await;
 5138
 5139    // Manipulate with multiple selections on a single line
 5140    cx.set_state(indoc! {"
 5141        dd«dd
 5142        cˇ»c«c
 5143        bb
 5144        aaaˇ»aa
 5145    "});
 5146    cx.update_editor(|e, window, cx| {
 5147        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5148    });
 5149    cx.assert_editor_state(indoc! {"
 5150        «aaaaa
 5151        bb
 5152        ccc
 5153        ddddˇ»
 5154    "});
 5155
 5156    // Manipulate with multiple disjoin selections
 5157    cx.set_state(indoc! {"
 5158 5159        4
 5160        3
 5161        2
 5162        1ˇ»
 5163
 5164        dd«dd
 5165        ccc
 5166        bb
 5167        aaaˇ»aa
 5168    "});
 5169    cx.update_editor(|e, window, cx| {
 5170        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5171    });
 5172    cx.assert_editor_state(indoc! {"
 5173        «1
 5174        2
 5175        3
 5176        4
 5177        5ˇ»
 5178
 5179        «aaaaa
 5180        bb
 5181        ccc
 5182        ddddˇ»
 5183    "});
 5184
 5185    // Adding lines on each selection
 5186    cx.set_state(indoc! {"
 5187 5188        1ˇ»
 5189
 5190        bb«bb
 5191        aaaˇ»aa
 5192    "});
 5193    cx.update_editor(|e, window, cx| {
 5194        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5195    });
 5196    cx.assert_editor_state(indoc! {"
 5197        «2
 5198        1
 5199        added lineˇ»
 5200
 5201        «bbbb
 5202        aaaaa
 5203        added lineˇ»
 5204    "});
 5205
 5206    // Removing lines on each selection
 5207    cx.set_state(indoc! {"
 5208 5209        1ˇ»
 5210
 5211        bb«bb
 5212        aaaˇ»aa
 5213    "});
 5214    cx.update_editor(|e, window, cx| {
 5215        e.manipulate_immutable_lines(window, cx, |lines| {
 5216            lines.pop();
 5217        })
 5218    });
 5219    cx.assert_editor_state(indoc! {"
 5220        «2ˇ»
 5221
 5222        «bbbbˇ»
 5223    "});
 5224}
 5225
 5226#[gpui::test]
 5227async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5228    init_test(cx, |settings| {
 5229        settings.defaults.tab_size = NonZeroU32::new(3)
 5230    });
 5231
 5232    let mut cx = EditorTestContext::new(cx).await;
 5233
 5234    // MULTI SELECTION
 5235    // Ln.1 "«" tests empty lines
 5236    // Ln.9 tests just leading whitespace
 5237    cx.set_state(indoc! {"
 5238        «
 5239        abc                 // No indentationˇ»
 5240        «\tabc              // 1 tabˇ»
 5241        \t\tabc «      ˇ»   // 2 tabs
 5242        \t ab«c             // Tab followed by space
 5243         \tabc              // Space followed by tab (3 spaces should be the result)
 5244        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5245           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5246        \t
 5247        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5248    "});
 5249    cx.update_editor(|e, window, cx| {
 5250        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5251    });
 5252    cx.assert_editor_state(
 5253        indoc! {"
 5254            «
 5255            abc                 // No indentation
 5256               abc              // 1 tab
 5257                  abc          // 2 tabs
 5258                abc             // Tab followed by space
 5259               abc              // Space followed by tab (3 spaces should be the result)
 5260                           abc   // Mixed indentation (tab conversion depends on the column)
 5261               abc         // Already space indented
 5262               ·
 5263               abc\tdef          // Only the leading tab is manipulatedˇ»
 5264        "}
 5265        .replace("·", "")
 5266        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5267    );
 5268
 5269    // Test on just a few lines, the others should remain unchanged
 5270    // Only lines (3, 5, 10, 11) should change
 5271    cx.set_state(
 5272        indoc! {"
 5273            ·
 5274            abc                 // No indentation
 5275            \tabcˇ               // 1 tab
 5276            \t\tabc             // 2 tabs
 5277            \t abcˇ              // Tab followed by space
 5278             \tabc              // Space followed by tab (3 spaces should be the result)
 5279            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5280               abc              // Already space indented
 5281            «\t
 5282            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5283        "}
 5284        .replace("·", "")
 5285        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5286    );
 5287    cx.update_editor(|e, window, cx| {
 5288        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5289    });
 5290    cx.assert_editor_state(
 5291        indoc! {"
 5292            ·
 5293            abc                 // No indentation
 5294            «   abc               // 1 tabˇ»
 5295            \t\tabc             // 2 tabs
 5296            «    abc              // Tab followed by spaceˇ»
 5297             \tabc              // Space followed by tab (3 spaces should be the result)
 5298            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5299               abc              // Already space indented
 5300            «   ·
 5301               abc\tdef          // Only the leading tab is manipulatedˇ»
 5302        "}
 5303        .replace("·", "")
 5304        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5305    );
 5306
 5307    // SINGLE SELECTION
 5308    // Ln.1 "«" tests empty lines
 5309    // Ln.9 tests just leading whitespace
 5310    cx.set_state(indoc! {"
 5311        «
 5312        abc                 // No indentation
 5313        \tabc               // 1 tab
 5314        \t\tabc             // 2 tabs
 5315        \t abc              // Tab followed by space
 5316         \tabc              // Space followed by tab (3 spaces should be the result)
 5317        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5318           abc              // Already space indented
 5319        \t
 5320        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5321    "});
 5322    cx.update_editor(|e, window, cx| {
 5323        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5324    });
 5325    cx.assert_editor_state(
 5326        indoc! {"
 5327            «
 5328            abc                 // No indentation
 5329               abc               // 1 tab
 5330                  abc             // 2 tabs
 5331                abc              // Tab followed by space
 5332               abc              // Space followed by tab (3 spaces should be the result)
 5333                           abc   // Mixed indentation (tab conversion depends on the column)
 5334               abc              // Already space indented
 5335               ·
 5336               abc\tdef          // Only the leading tab is manipulatedˇ»
 5337        "}
 5338        .replace("·", "")
 5339        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5340    );
 5341}
 5342
 5343#[gpui::test]
 5344async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5345    init_test(cx, |settings| {
 5346        settings.defaults.tab_size = NonZeroU32::new(3)
 5347    });
 5348
 5349    let mut cx = EditorTestContext::new(cx).await;
 5350
 5351    // MULTI SELECTION
 5352    // Ln.1 "«" tests empty lines
 5353    // Ln.11 tests just leading whitespace
 5354    cx.set_state(indoc! {"
 5355        «
 5356        abˇ»ˇc                 // No indentation
 5357         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5358          abc  «             // 2 spaces (< 3 so dont convert)
 5359           abc              // 3 spaces (convert)
 5360             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5361        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5362        «\t abc              // Tab followed by space
 5363         \tabc              // Space followed by tab (should be consumed due to tab)
 5364        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5365           \tˇ»  «\t
 5366           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5367    "});
 5368    cx.update_editor(|e, window, cx| {
 5369        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5370    });
 5371    cx.assert_editor_state(indoc! {"
 5372        «
 5373        abc                 // No indentation
 5374         abc                // 1 space (< 3 so dont convert)
 5375          abc               // 2 spaces (< 3 so dont convert)
 5376        \tabc              // 3 spaces (convert)
 5377        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5378        \t\t\tabc           // Already tab indented
 5379        \t abc              // Tab followed by space
 5380        \tabc              // Space followed by tab (should be consumed due to tab)
 5381        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5382        \t\t\t
 5383        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5384    "});
 5385
 5386    // Test on just a few lines, the other should remain unchanged
 5387    // Only lines (4, 8, 11, 12) should change
 5388    cx.set_state(
 5389        indoc! {"
 5390            ·
 5391            abc                 // No indentation
 5392             abc                // 1 space (< 3 so dont convert)
 5393              abc               // 2 spaces (< 3 so dont convert)
 5394            «   abc              // 3 spaces (convert)ˇ»
 5395                 abc            // 5 spaces (1 tab + 2 spaces)
 5396            \t\t\tabc           // Already tab indented
 5397            \t abc              // Tab followed by space
 5398             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5399               \t\t  \tabc      // Mixed indentation
 5400            \t \t  \t   \tabc   // Mixed indentation
 5401               \t  \tˇ
 5402            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5403        "}
 5404        .replace("·", "")
 5405        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5406    );
 5407    cx.update_editor(|e, window, cx| {
 5408        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5409    });
 5410    cx.assert_editor_state(
 5411        indoc! {"
 5412            ·
 5413            abc                 // No indentation
 5414             abc                // 1 space (< 3 so dont convert)
 5415              abc               // 2 spaces (< 3 so dont convert)
 5416            «\tabc              // 3 spaces (convert)ˇ»
 5417                 abc            // 5 spaces (1 tab + 2 spaces)
 5418            \t\t\tabc           // Already tab indented
 5419            \t abc              // Tab followed by space
 5420            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5421               \t\t  \tabc      // Mixed indentation
 5422            \t \t  \t   \tabc   // Mixed indentation
 5423            «\t\t\t
 5424            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5425        "}
 5426        .replace("·", "")
 5427        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5428    );
 5429
 5430    // SINGLE SELECTION
 5431    // Ln.1 "«" tests empty lines
 5432    // Ln.11 tests just leading whitespace
 5433    cx.set_state(indoc! {"
 5434        «
 5435        abc                 // No indentation
 5436         abc                // 1 space (< 3 so dont convert)
 5437          abc               // 2 spaces (< 3 so dont convert)
 5438           abc              // 3 spaces (convert)
 5439             abc            // 5 spaces (1 tab + 2 spaces)
 5440        \t\t\tabc           // Already tab indented
 5441        \t abc              // Tab followed by space
 5442         \tabc              // Space followed by tab (should be consumed due to tab)
 5443        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5444           \t  \t
 5445           abc   \t         // Only the leading spaces should be convertedˇ»
 5446    "});
 5447    cx.update_editor(|e, window, cx| {
 5448        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5449    });
 5450    cx.assert_editor_state(indoc! {"
 5451        «
 5452        abc                 // No indentation
 5453         abc                // 1 space (< 3 so dont convert)
 5454          abc               // 2 spaces (< 3 so dont convert)
 5455        \tabc              // 3 spaces (convert)
 5456        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5457        \t\t\tabc           // Already tab indented
 5458        \t abc              // Tab followed by space
 5459        \tabc              // Space followed by tab (should be consumed due to tab)
 5460        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5461        \t\t\t
 5462        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5463    "});
 5464}
 5465
 5466#[gpui::test]
 5467async fn test_toggle_case(cx: &mut TestAppContext) {
 5468    init_test(cx, |_| {});
 5469
 5470    let mut cx = EditorTestContext::new(cx).await;
 5471
 5472    // If all lower case -> upper case
 5473    cx.set_state(indoc! {"
 5474        «hello worldˇ»
 5475    "});
 5476    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5477    cx.assert_editor_state(indoc! {"
 5478        «HELLO WORLDˇ»
 5479    "});
 5480
 5481    // If all upper case -> lower case
 5482    cx.set_state(indoc! {"
 5483        «HELLO WORLDˇ»
 5484    "});
 5485    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5486    cx.assert_editor_state(indoc! {"
 5487        «hello worldˇ»
 5488    "});
 5489
 5490    // If any upper case characters are identified -> lower case
 5491    // This matches JetBrains IDEs
 5492    cx.set_state(indoc! {"
 5493        «hEllo worldˇ»
 5494    "});
 5495    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5496    cx.assert_editor_state(indoc! {"
 5497        «hello worldˇ»
 5498    "});
 5499}
 5500
 5501#[gpui::test]
 5502async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5503    init_test(cx, |_| {});
 5504
 5505    let mut cx = EditorTestContext::new(cx).await;
 5506
 5507    cx.set_state(indoc! {"
 5508        «implement-windows-supportˇ»
 5509    "});
 5510    cx.update_editor(|e, window, cx| {
 5511        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5512    });
 5513    cx.assert_editor_state(indoc! {"
 5514        «Implement windows supportˇ»
 5515    "});
 5516}
 5517
 5518#[gpui::test]
 5519async fn test_manipulate_text(cx: &mut TestAppContext) {
 5520    init_test(cx, |_| {});
 5521
 5522    let mut cx = EditorTestContext::new(cx).await;
 5523
 5524    // Test convert_to_upper_case()
 5525    cx.set_state(indoc! {"
 5526        «hello worldˇ»
 5527    "});
 5528    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5529    cx.assert_editor_state(indoc! {"
 5530        «HELLO WORLDˇ»
 5531    "});
 5532
 5533    // Test convert_to_lower_case()
 5534    cx.set_state(indoc! {"
 5535        «HELLO WORLDˇ»
 5536    "});
 5537    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5538    cx.assert_editor_state(indoc! {"
 5539        «hello worldˇ»
 5540    "});
 5541
 5542    // Test multiple line, single selection case
 5543    cx.set_state(indoc! {"
 5544        «The quick brown
 5545        fox jumps over
 5546        the lazy dogˇ»
 5547    "});
 5548    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5549    cx.assert_editor_state(indoc! {"
 5550        «The Quick Brown
 5551        Fox Jumps Over
 5552        The Lazy Dogˇ»
 5553    "});
 5554
 5555    // Test multiple line, single selection case
 5556    cx.set_state(indoc! {"
 5557        «The quick brown
 5558        fox jumps over
 5559        the lazy dogˇ»
 5560    "});
 5561    cx.update_editor(|e, window, cx| {
 5562        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5563    });
 5564    cx.assert_editor_state(indoc! {"
 5565        «TheQuickBrown
 5566        FoxJumpsOver
 5567        TheLazyDogˇ»
 5568    "});
 5569
 5570    // From here on out, test more complex cases of manipulate_text()
 5571
 5572    // Test no selection case - should affect words cursors are in
 5573    // Cursor at beginning, middle, and end of word
 5574    cx.set_state(indoc! {"
 5575        ˇhello big beauˇtiful worldˇ
 5576    "});
 5577    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5578    cx.assert_editor_state(indoc! {"
 5579        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5580    "});
 5581
 5582    // Test multiple selections on a single line and across multiple lines
 5583    cx.set_state(indoc! {"
 5584        «Theˇ» quick «brown
 5585        foxˇ» jumps «overˇ»
 5586        the «lazyˇ» dog
 5587    "});
 5588    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5589    cx.assert_editor_state(indoc! {"
 5590        «THEˇ» quick «BROWN
 5591        FOXˇ» jumps «OVERˇ»
 5592        the «LAZYˇ» dog
 5593    "});
 5594
 5595    // Test case where text length grows
 5596    cx.set_state(indoc! {"
 5597        «tschüߡ»
 5598    "});
 5599    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5600    cx.assert_editor_state(indoc! {"
 5601        «TSCHÜSSˇ»
 5602    "});
 5603
 5604    // Test to make sure we don't crash when text shrinks
 5605    cx.set_state(indoc! {"
 5606        aaa_bbbˇ
 5607    "});
 5608    cx.update_editor(|e, window, cx| {
 5609        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5610    });
 5611    cx.assert_editor_state(indoc! {"
 5612        «aaaBbbˇ»
 5613    "});
 5614
 5615    // Test to make sure we all aware of the fact that each word can grow and shrink
 5616    // Final selections should be aware of this fact
 5617    cx.set_state(indoc! {"
 5618        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5619    "});
 5620    cx.update_editor(|e, window, cx| {
 5621        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5622    });
 5623    cx.assert_editor_state(indoc! {"
 5624        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5625    "});
 5626
 5627    cx.set_state(indoc! {"
 5628        «hElLo, WoRld!ˇ»
 5629    "});
 5630    cx.update_editor(|e, window, cx| {
 5631        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5632    });
 5633    cx.assert_editor_state(indoc! {"
 5634        «HeLlO, wOrLD!ˇ»
 5635    "});
 5636
 5637    // Test selections with `line_mode() = true`.
 5638    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5639    cx.set_state(indoc! {"
 5640        «The quick brown
 5641        fox jumps over
 5642        tˇ»he lazy dog
 5643    "});
 5644    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5645    cx.assert_editor_state(indoc! {"
 5646        «THE QUICK BROWN
 5647        FOX JUMPS OVER
 5648        THE LAZY DOGˇ»
 5649    "});
 5650}
 5651
 5652#[gpui::test]
 5653fn test_duplicate_line(cx: &mut TestAppContext) {
 5654    init_test(cx, |_| {});
 5655
 5656    let editor = cx.add_window(|window, cx| {
 5657        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5658        build_editor(buffer, window, cx)
 5659    });
 5660    _ = editor.update(cx, |editor, window, cx| {
 5661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5662            s.select_display_ranges([
 5663                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5664                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5665                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5666                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5667            ])
 5668        });
 5669        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5670        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5671        assert_eq!(
 5672            display_ranges(editor, cx),
 5673            vec![
 5674                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5675                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5676                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5677                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5678            ]
 5679        );
 5680    });
 5681
 5682    let editor = cx.add_window(|window, cx| {
 5683        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5684        build_editor(buffer, window, cx)
 5685    });
 5686    _ = editor.update(cx, |editor, window, cx| {
 5687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5688            s.select_display_ranges([
 5689                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5690                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5691            ])
 5692        });
 5693        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5694        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5695        assert_eq!(
 5696            display_ranges(editor, cx),
 5697            vec![
 5698                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5699                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5700            ]
 5701        );
 5702    });
 5703
 5704    // With `duplicate_line_up` the selections move to the duplicated lines,
 5705    // which are inserted above the original lines
 5706    let editor = cx.add_window(|window, cx| {
 5707        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5708        build_editor(buffer, window, cx)
 5709    });
 5710    _ = editor.update(cx, |editor, window, cx| {
 5711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5712            s.select_display_ranges([
 5713                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5714                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5715                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5716                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5717            ])
 5718        });
 5719        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5720        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5721        assert_eq!(
 5722            display_ranges(editor, cx),
 5723            vec![
 5724                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5725                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5726                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5727                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5728            ]
 5729        );
 5730    });
 5731
 5732    let editor = cx.add_window(|window, cx| {
 5733        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5734        build_editor(buffer, window, cx)
 5735    });
 5736    _ = editor.update(cx, |editor, window, cx| {
 5737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5738            s.select_display_ranges([
 5739                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5740                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5741            ])
 5742        });
 5743        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5744        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5745        assert_eq!(
 5746            display_ranges(editor, cx),
 5747            vec![
 5748                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5749                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5750            ]
 5751        );
 5752    });
 5753
 5754    let editor = cx.add_window(|window, cx| {
 5755        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5756        build_editor(buffer, window, cx)
 5757    });
 5758    _ = editor.update(cx, |editor, window, cx| {
 5759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5760            s.select_display_ranges([
 5761                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5762                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5763            ])
 5764        });
 5765        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5766        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5767        assert_eq!(
 5768            display_ranges(editor, cx),
 5769            vec![
 5770                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5771                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5772            ]
 5773        );
 5774    });
 5775}
 5776
 5777#[gpui::test]
 5778async fn test_rotate_selections(cx: &mut TestAppContext) {
 5779    init_test(cx, |_| {});
 5780
 5781    let mut cx = EditorTestContext::new(cx).await;
 5782
 5783    // Rotate text selections (horizontal)
 5784    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5785    cx.update_editor(|e, window, cx| {
 5786        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5787    });
 5788    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5789    cx.update_editor(|e, window, cx| {
 5790        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5791    });
 5792    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5793
 5794    // Rotate text selections (vertical)
 5795    cx.set_state(indoc! {"
 5796        x=«1ˇ»
 5797        y=«2ˇ»
 5798        z=«3ˇ»
 5799    "});
 5800    cx.update_editor(|e, window, cx| {
 5801        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5802    });
 5803    cx.assert_editor_state(indoc! {"
 5804        x=«3ˇ»
 5805        y=«1ˇ»
 5806        z=«2ˇ»
 5807    "});
 5808    cx.update_editor(|e, window, cx| {
 5809        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5810    });
 5811    cx.assert_editor_state(indoc! {"
 5812        x=«1ˇ»
 5813        y=«2ˇ»
 5814        z=«3ˇ»
 5815    "});
 5816
 5817    // Rotate text selections (vertical, different lengths)
 5818    cx.set_state(indoc! {"
 5819        x=\"«ˇ»\"
 5820        y=\"«aˇ»\"
 5821        z=\"«aaˇ»\"
 5822    "});
 5823    cx.update_editor(|e, window, cx| {
 5824        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5825    });
 5826    cx.assert_editor_state(indoc! {"
 5827        x=\"«aaˇ»\"
 5828        y=\"«ˇ»\"
 5829        z=\"«aˇ»\"
 5830    "});
 5831    cx.update_editor(|e, window, cx| {
 5832        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5833    });
 5834    cx.assert_editor_state(indoc! {"
 5835        x=\"«ˇ»\"
 5836        y=\"«aˇ»\"
 5837        z=\"«aaˇ»\"
 5838    "});
 5839
 5840    // Rotate whole lines (cursor positions preserved)
 5841    cx.set_state(indoc! {"
 5842        ˇline123
 5843        liˇne23
 5844        line3ˇ
 5845    "});
 5846    cx.update_editor(|e, window, cx| {
 5847        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5848    });
 5849    cx.assert_editor_state(indoc! {"
 5850        line3ˇ
 5851        ˇline123
 5852        liˇne23
 5853    "});
 5854    cx.update_editor(|e, window, cx| {
 5855        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5856    });
 5857    cx.assert_editor_state(indoc! {"
 5858        ˇline123
 5859        liˇne23
 5860        line3ˇ
 5861    "});
 5862
 5863    // Rotate whole lines, multiple cursors per line (positions preserved)
 5864    cx.set_state(indoc! {"
 5865        ˇliˇne123
 5866        ˇline23
 5867        ˇline3
 5868    "});
 5869    cx.update_editor(|e, window, cx| {
 5870        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5871    });
 5872    cx.assert_editor_state(indoc! {"
 5873        ˇline3
 5874        ˇliˇne123
 5875        ˇline23
 5876    "});
 5877    cx.update_editor(|e, window, cx| {
 5878        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5879    });
 5880    cx.assert_editor_state(indoc! {"
 5881        ˇliˇne123
 5882        ˇline23
 5883        ˇline3
 5884    "});
 5885}
 5886
 5887#[gpui::test]
 5888fn test_move_line_up_down(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890
 5891    let editor = cx.add_window(|window, cx| {
 5892        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5893        build_editor(buffer, window, cx)
 5894    });
 5895    _ = editor.update(cx, |editor, window, cx| {
 5896        editor.fold_creases(
 5897            vec![
 5898                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5899                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5900                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5901            ],
 5902            true,
 5903            window,
 5904            cx,
 5905        );
 5906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5907            s.select_display_ranges([
 5908                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5909                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5910                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5911                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5912            ])
 5913        });
 5914        assert_eq!(
 5915            editor.display_text(cx),
 5916            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5917        );
 5918
 5919        editor.move_line_up(&MoveLineUp, window, cx);
 5920        assert_eq!(
 5921            editor.display_text(cx),
 5922            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5923        );
 5924        assert_eq!(
 5925            display_ranges(editor, cx),
 5926            vec![
 5927                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5928                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5929                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5930                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5931            ]
 5932        );
 5933    });
 5934
 5935    _ = editor.update(cx, |editor, window, cx| {
 5936        editor.move_line_down(&MoveLineDown, window, cx);
 5937        assert_eq!(
 5938            editor.display_text(cx),
 5939            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5940        );
 5941        assert_eq!(
 5942            display_ranges(editor, cx),
 5943            vec![
 5944                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5945                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5946                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5947                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5948            ]
 5949        );
 5950    });
 5951
 5952    _ = editor.update(cx, |editor, window, cx| {
 5953        editor.move_line_down(&MoveLineDown, window, cx);
 5954        assert_eq!(
 5955            editor.display_text(cx),
 5956            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5957        );
 5958        assert_eq!(
 5959            display_ranges(editor, cx),
 5960            vec![
 5961                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5962                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5963                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5964                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5965            ]
 5966        );
 5967    });
 5968
 5969    _ = editor.update(cx, |editor, window, cx| {
 5970        editor.move_line_up(&MoveLineUp, window, cx);
 5971        assert_eq!(
 5972            editor.display_text(cx),
 5973            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5974        );
 5975        assert_eq!(
 5976            display_ranges(editor, cx),
 5977            vec![
 5978                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5979                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5980                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5981                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5982            ]
 5983        );
 5984    });
 5985}
 5986
 5987#[gpui::test]
 5988fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990    let editor = cx.add_window(|window, cx| {
 5991        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5992        build_editor(buffer, window, cx)
 5993    });
 5994    _ = editor.update(cx, |editor, window, cx| {
 5995        editor.fold_creases(
 5996            vec![Crease::simple(
 5997                Point::new(6, 4)..Point::new(7, 4),
 5998                FoldPlaceholder::test(),
 5999            )],
 6000            true,
 6001            window,
 6002            cx,
 6003        );
 6004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6005            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6006        });
 6007        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6008        editor.move_line_up(&MoveLineUp, window, cx);
 6009        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6010        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6011    });
 6012}
 6013
 6014#[gpui::test]
 6015fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6016    init_test(cx, |_| {});
 6017
 6018    let editor = cx.add_window(|window, cx| {
 6019        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6020        build_editor(buffer, window, cx)
 6021    });
 6022    _ = editor.update(cx, |editor, window, cx| {
 6023        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6024        editor.insert_blocks(
 6025            [BlockProperties {
 6026                style: BlockStyle::Fixed,
 6027                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6028                height: Some(1),
 6029                render: Arc::new(|_| div().into_any()),
 6030                priority: 0,
 6031            }],
 6032            Some(Autoscroll::fit()),
 6033            cx,
 6034        );
 6035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6036            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6037        });
 6038        editor.move_line_down(&MoveLineDown, window, cx);
 6039    });
 6040}
 6041
 6042#[gpui::test]
 6043async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6044    init_test(cx, |_| {});
 6045
 6046    let mut cx = EditorTestContext::new(cx).await;
 6047    cx.set_state(
 6048        &"
 6049            ˇzero
 6050            one
 6051            two
 6052            three
 6053            four
 6054            five
 6055        "
 6056        .unindent(),
 6057    );
 6058
 6059    // Create a four-line block that replaces three lines of text.
 6060    cx.update_editor(|editor, window, cx| {
 6061        let snapshot = editor.snapshot(window, cx);
 6062        let snapshot = &snapshot.buffer_snapshot();
 6063        let placement = BlockPlacement::Replace(
 6064            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6065        );
 6066        editor.insert_blocks(
 6067            [BlockProperties {
 6068                placement,
 6069                height: Some(4),
 6070                style: BlockStyle::Sticky,
 6071                render: Arc::new(|_| gpui::div().into_any_element()),
 6072                priority: 0,
 6073            }],
 6074            None,
 6075            cx,
 6076        );
 6077    });
 6078
 6079    // Move down so that the cursor touches the block.
 6080    cx.update_editor(|editor, window, cx| {
 6081        editor.move_down(&Default::default(), window, cx);
 6082    });
 6083    cx.assert_editor_state(
 6084        &"
 6085            zero
 6086            «one
 6087            two
 6088            threeˇ»
 6089            four
 6090            five
 6091        "
 6092        .unindent(),
 6093    );
 6094
 6095    // Move down past the block.
 6096    cx.update_editor(|editor, window, cx| {
 6097        editor.move_down(&Default::default(), window, cx);
 6098    });
 6099    cx.assert_editor_state(
 6100        &"
 6101            zero
 6102            one
 6103            two
 6104            three
 6105            ˇfour
 6106            five
 6107        "
 6108        .unindent(),
 6109    );
 6110}
 6111
 6112#[gpui::test]
 6113fn test_transpose(cx: &mut TestAppContext) {
 6114    init_test(cx, |_| {});
 6115
 6116    _ = cx.add_window(|window, cx| {
 6117        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6118        editor.set_style(EditorStyle::default(), window, cx);
 6119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6120            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6121        });
 6122        editor.transpose(&Default::default(), window, cx);
 6123        assert_eq!(editor.text(cx), "bac");
 6124        assert_eq!(
 6125            editor.selections.ranges(&editor.display_snapshot(cx)),
 6126            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6127        );
 6128
 6129        editor.transpose(&Default::default(), window, cx);
 6130        assert_eq!(editor.text(cx), "bca");
 6131        assert_eq!(
 6132            editor.selections.ranges(&editor.display_snapshot(cx)),
 6133            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6134        );
 6135
 6136        editor.transpose(&Default::default(), window, cx);
 6137        assert_eq!(editor.text(cx), "bac");
 6138        assert_eq!(
 6139            editor.selections.ranges(&editor.display_snapshot(cx)),
 6140            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6141        );
 6142
 6143        editor
 6144    });
 6145
 6146    _ = cx.add_window(|window, cx| {
 6147        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6148        editor.set_style(EditorStyle::default(), window, cx);
 6149        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6150            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6151        });
 6152        editor.transpose(&Default::default(), window, cx);
 6153        assert_eq!(editor.text(cx), "acb\nde");
 6154        assert_eq!(
 6155            editor.selections.ranges(&editor.display_snapshot(cx)),
 6156            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6157        );
 6158
 6159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6160            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6161        });
 6162        editor.transpose(&Default::default(), window, cx);
 6163        assert_eq!(editor.text(cx), "acbd\ne");
 6164        assert_eq!(
 6165            editor.selections.ranges(&editor.display_snapshot(cx)),
 6166            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6167        );
 6168
 6169        editor.transpose(&Default::default(), window, cx);
 6170        assert_eq!(editor.text(cx), "acbde\n");
 6171        assert_eq!(
 6172            editor.selections.ranges(&editor.display_snapshot(cx)),
 6173            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6174        );
 6175
 6176        editor.transpose(&Default::default(), window, cx);
 6177        assert_eq!(editor.text(cx), "acbd\ne");
 6178        assert_eq!(
 6179            editor.selections.ranges(&editor.display_snapshot(cx)),
 6180            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6181        );
 6182
 6183        editor
 6184    });
 6185
 6186    _ = cx.add_window(|window, cx| {
 6187        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6188        editor.set_style(EditorStyle::default(), window, cx);
 6189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6190            s.select_ranges([
 6191                MultiBufferOffset(1)..MultiBufferOffset(1),
 6192                MultiBufferOffset(2)..MultiBufferOffset(2),
 6193                MultiBufferOffset(4)..MultiBufferOffset(4),
 6194            ])
 6195        });
 6196        editor.transpose(&Default::default(), window, cx);
 6197        assert_eq!(editor.text(cx), "bacd\ne");
 6198        assert_eq!(
 6199            editor.selections.ranges(&editor.display_snapshot(cx)),
 6200            [
 6201                MultiBufferOffset(2)..MultiBufferOffset(2),
 6202                MultiBufferOffset(3)..MultiBufferOffset(3),
 6203                MultiBufferOffset(5)..MultiBufferOffset(5)
 6204            ]
 6205        );
 6206
 6207        editor.transpose(&Default::default(), window, cx);
 6208        assert_eq!(editor.text(cx), "bcade\n");
 6209        assert_eq!(
 6210            editor.selections.ranges(&editor.display_snapshot(cx)),
 6211            [
 6212                MultiBufferOffset(3)..MultiBufferOffset(3),
 6213                MultiBufferOffset(4)..MultiBufferOffset(4),
 6214                MultiBufferOffset(6)..MultiBufferOffset(6)
 6215            ]
 6216        );
 6217
 6218        editor.transpose(&Default::default(), window, cx);
 6219        assert_eq!(editor.text(cx), "bcda\ne");
 6220        assert_eq!(
 6221            editor.selections.ranges(&editor.display_snapshot(cx)),
 6222            [
 6223                MultiBufferOffset(4)..MultiBufferOffset(4),
 6224                MultiBufferOffset(6)..MultiBufferOffset(6)
 6225            ]
 6226        );
 6227
 6228        editor.transpose(&Default::default(), window, cx);
 6229        assert_eq!(editor.text(cx), "bcade\n");
 6230        assert_eq!(
 6231            editor.selections.ranges(&editor.display_snapshot(cx)),
 6232            [
 6233                MultiBufferOffset(4)..MultiBufferOffset(4),
 6234                MultiBufferOffset(6)..MultiBufferOffset(6)
 6235            ]
 6236        );
 6237
 6238        editor.transpose(&Default::default(), window, cx);
 6239        assert_eq!(editor.text(cx), "bcaed\n");
 6240        assert_eq!(
 6241            editor.selections.ranges(&editor.display_snapshot(cx)),
 6242            [
 6243                MultiBufferOffset(5)..MultiBufferOffset(5),
 6244                MultiBufferOffset(6)..MultiBufferOffset(6)
 6245            ]
 6246        );
 6247
 6248        editor
 6249    });
 6250
 6251    _ = cx.add_window(|window, cx| {
 6252        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6253        editor.set_style(EditorStyle::default(), window, cx);
 6254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6255            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6256        });
 6257        editor.transpose(&Default::default(), window, cx);
 6258        assert_eq!(editor.text(cx), "🏀🍐✋");
 6259        assert_eq!(
 6260            editor.selections.ranges(&editor.display_snapshot(cx)),
 6261            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6262        );
 6263
 6264        editor.transpose(&Default::default(), window, cx);
 6265        assert_eq!(editor.text(cx), "🏀✋🍐");
 6266        assert_eq!(
 6267            editor.selections.ranges(&editor.display_snapshot(cx)),
 6268            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6269        );
 6270
 6271        editor.transpose(&Default::default(), window, cx);
 6272        assert_eq!(editor.text(cx), "🏀🍐✋");
 6273        assert_eq!(
 6274            editor.selections.ranges(&editor.display_snapshot(cx)),
 6275            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6276        );
 6277
 6278        editor
 6279    });
 6280}
 6281
 6282#[gpui::test]
 6283async fn test_rewrap(cx: &mut TestAppContext) {
 6284    init_test(cx, |settings| {
 6285        settings.languages.0.extend([
 6286            (
 6287                "Markdown".into(),
 6288                LanguageSettingsContent {
 6289                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6290                    preferred_line_length: Some(40),
 6291                    ..Default::default()
 6292                },
 6293            ),
 6294            (
 6295                "Plain Text".into(),
 6296                LanguageSettingsContent {
 6297                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6298                    preferred_line_length: Some(40),
 6299                    ..Default::default()
 6300                },
 6301            ),
 6302            (
 6303                "C++".into(),
 6304                LanguageSettingsContent {
 6305                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6306                    preferred_line_length: Some(40),
 6307                    ..Default::default()
 6308                },
 6309            ),
 6310            (
 6311                "Python".into(),
 6312                LanguageSettingsContent {
 6313                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6314                    preferred_line_length: Some(40),
 6315                    ..Default::default()
 6316                },
 6317            ),
 6318            (
 6319                "Rust".into(),
 6320                LanguageSettingsContent {
 6321                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6322                    preferred_line_length: Some(40),
 6323                    ..Default::default()
 6324                },
 6325            ),
 6326        ])
 6327    });
 6328
 6329    let mut cx = EditorTestContext::new(cx).await;
 6330
 6331    let cpp_language = Arc::new(Language::new(
 6332        LanguageConfig {
 6333            name: "C++".into(),
 6334            line_comments: vec!["// ".into()],
 6335            ..LanguageConfig::default()
 6336        },
 6337        None,
 6338    ));
 6339    let python_language = Arc::new(Language::new(
 6340        LanguageConfig {
 6341            name: "Python".into(),
 6342            line_comments: vec!["# ".into()],
 6343            ..LanguageConfig::default()
 6344        },
 6345        None,
 6346    ));
 6347    let markdown_language = Arc::new(Language::new(
 6348        LanguageConfig {
 6349            name: "Markdown".into(),
 6350            rewrap_prefixes: vec![
 6351                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6352                regex::Regex::new("[-*+]\\s+").unwrap(),
 6353            ],
 6354            ..LanguageConfig::default()
 6355        },
 6356        None,
 6357    ));
 6358    let rust_language = Arc::new(
 6359        Language::new(
 6360            LanguageConfig {
 6361                name: "Rust".into(),
 6362                line_comments: vec!["// ".into(), "/// ".into()],
 6363                ..LanguageConfig::default()
 6364            },
 6365            Some(tree_sitter_rust::LANGUAGE.into()),
 6366        )
 6367        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6368        .unwrap(),
 6369    );
 6370
 6371    let plaintext_language = Arc::new(Language::new(
 6372        LanguageConfig {
 6373            name: "Plain Text".into(),
 6374            ..LanguageConfig::default()
 6375        },
 6376        None,
 6377    ));
 6378
 6379    // Test basic rewrapping of a long line with a cursor
 6380    assert_rewrap(
 6381        indoc! {"
 6382            // ˇThis is a long comment that needs to be wrapped.
 6383        "},
 6384        indoc! {"
 6385            // ˇThis is a long comment that needs to
 6386            // be wrapped.
 6387        "},
 6388        cpp_language.clone(),
 6389        &mut cx,
 6390    );
 6391
 6392    // Test rewrapping a full selection
 6393    assert_rewrap(
 6394        indoc! {"
 6395            «// This selected long comment needs to be wrapped.ˇ»"
 6396        },
 6397        indoc! {"
 6398            «// This selected long comment needs to
 6399            // be wrapped.ˇ»"
 6400        },
 6401        cpp_language.clone(),
 6402        &mut cx,
 6403    );
 6404
 6405    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6406    assert_rewrap(
 6407        indoc! {"
 6408            // ˇThis is the first line.
 6409            // Thisˇ is the second line.
 6410            // This is the thirdˇ line, all part of one paragraph.
 6411         "},
 6412        indoc! {"
 6413            // ˇThis is the first line. Thisˇ is the
 6414            // second line. This is the thirdˇ line,
 6415            // all part of one paragraph.
 6416         "},
 6417        cpp_language.clone(),
 6418        &mut cx,
 6419    );
 6420
 6421    // Test multiple cursors in different paragraphs trigger separate rewraps
 6422    assert_rewrap(
 6423        indoc! {"
 6424            // ˇThis is the first paragraph, first line.
 6425            // ˇThis is the first paragraph, second line.
 6426
 6427            // ˇThis is the second paragraph, first line.
 6428            // ˇThis is the second paragraph, second line.
 6429        "},
 6430        indoc! {"
 6431            // ˇThis is the first paragraph, first
 6432            // line. ˇThis is the first paragraph,
 6433            // second line.
 6434
 6435            // ˇThis is the second paragraph, first
 6436            // line. ˇThis is the second paragraph,
 6437            // second line.
 6438        "},
 6439        cpp_language.clone(),
 6440        &mut cx,
 6441    );
 6442
 6443    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6444    assert_rewrap(
 6445        indoc! {"
 6446            «// A regular long long comment to be wrapped.
 6447            /// A documentation long comment to be wrapped.ˇ»
 6448          "},
 6449        indoc! {"
 6450            «// A regular long long comment to be
 6451            // wrapped.
 6452            /// A documentation long comment to be
 6453            /// wrapped.ˇ»
 6454          "},
 6455        rust_language.clone(),
 6456        &mut cx,
 6457    );
 6458
 6459    // Test that change in indentation level trigger seperate rewraps
 6460    assert_rewrap(
 6461        indoc! {"
 6462            fn foo() {
 6463                «// This is a long comment at the base indent.
 6464                    // This is a long comment at the next indent.ˇ»
 6465            }
 6466        "},
 6467        indoc! {"
 6468            fn foo() {
 6469                «// This is a long comment at the
 6470                // base indent.
 6471                    // This is a long comment at the
 6472                    // next indent.ˇ»
 6473            }
 6474        "},
 6475        rust_language.clone(),
 6476        &mut cx,
 6477    );
 6478
 6479    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6480    assert_rewrap(
 6481        indoc! {"
 6482            # ˇThis is a long comment using a pound sign.
 6483        "},
 6484        indoc! {"
 6485            # ˇThis is a long comment using a pound
 6486            # sign.
 6487        "},
 6488        python_language,
 6489        &mut cx,
 6490    );
 6491
 6492    // Test rewrapping only affects comments, not code even when selected
 6493    assert_rewrap(
 6494        indoc! {"
 6495            «/// This doc comment is long and should be wrapped.
 6496            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6497        "},
 6498        indoc! {"
 6499            «/// This doc comment is long and should
 6500            /// be wrapped.
 6501            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6502        "},
 6503        rust_language.clone(),
 6504        &mut cx,
 6505    );
 6506
 6507    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6508    assert_rewrap(
 6509        indoc! {"
 6510            # Header
 6511
 6512            A long long long line of markdown text to wrap.ˇ
 6513         "},
 6514        indoc! {"
 6515            # Header
 6516
 6517            A long long long line of markdown text
 6518            to wrap.ˇ
 6519         "},
 6520        markdown_language.clone(),
 6521        &mut cx,
 6522    );
 6523
 6524    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6525    assert_rewrap(
 6526        indoc! {"
 6527            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6528            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6529            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6530        "},
 6531        indoc! {"
 6532            «1. This is a numbered list item that is
 6533               very long and needs to be wrapped
 6534               properly.
 6535            2. This is a numbered list item that is
 6536               very long and needs to be wrapped
 6537               properly.
 6538            - This is an unordered list item that is
 6539              also very long and should not merge
 6540              with the numbered item.ˇ»
 6541        "},
 6542        markdown_language.clone(),
 6543        &mut cx,
 6544    );
 6545
 6546    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6547    assert_rewrap(
 6548        indoc! {"
 6549            «1. This is a numbered list item that is
 6550            very long and needs to be wrapped
 6551            properly.
 6552            2. This is a numbered list item that is
 6553            very long and needs to be wrapped
 6554            properly.
 6555            - This is an unordered list item that is
 6556            also very long and should not merge with
 6557            the numbered item.ˇ»
 6558        "},
 6559        indoc! {"
 6560            «1. This is a numbered list item that is
 6561               very long and needs to be wrapped
 6562               properly.
 6563            2. This is a numbered list item that is
 6564               very long and needs to be wrapped
 6565               properly.
 6566            - This is an unordered list item that is
 6567              also very long and should not merge
 6568              with the numbered item.ˇ»
 6569        "},
 6570        markdown_language.clone(),
 6571        &mut cx,
 6572    );
 6573
 6574    // Test that rewrapping maintain indents even when they already exists.
 6575    assert_rewrap(
 6576        indoc! {"
 6577            «1. This is a numbered list
 6578               item that is very long and needs to be wrapped properly.
 6579            2. This is a numbered list
 6580               item that is very long and needs to be wrapped properly.
 6581            - This is an unordered list item that is also very long and
 6582              should not merge with the numbered item.ˇ»
 6583        "},
 6584        indoc! {"
 6585            «1. This is a numbered list item that is
 6586               very long and needs to be wrapped
 6587               properly.
 6588            2. This is a numbered list item that is
 6589               very long and needs to be wrapped
 6590               properly.
 6591            - This is an unordered list item that is
 6592              also very long and should not merge
 6593              with the numbered item.ˇ»
 6594        "},
 6595        markdown_language,
 6596        &mut cx,
 6597    );
 6598
 6599    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6600    assert_rewrap(
 6601        indoc! {"
 6602            ˇThis is a very long line of plain text that will be wrapped.
 6603        "},
 6604        indoc! {"
 6605            ˇThis is a very long line of plain text
 6606            that will be wrapped.
 6607        "},
 6608        plaintext_language.clone(),
 6609        &mut cx,
 6610    );
 6611
 6612    // Test that non-commented code acts as a paragraph boundary within a selection
 6613    assert_rewrap(
 6614        indoc! {"
 6615               «// This is the first long comment block to be wrapped.
 6616               fn my_func(a: u32);
 6617               // This is the second long comment block to be wrapped.ˇ»
 6618           "},
 6619        indoc! {"
 6620               «// This is the first long comment block
 6621               // to be wrapped.
 6622               fn my_func(a: u32);
 6623               // This is the second long comment block
 6624               // to be wrapped.ˇ»
 6625           "},
 6626        rust_language,
 6627        &mut cx,
 6628    );
 6629
 6630    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6631    assert_rewrap(
 6632        indoc! {"
 6633            «ˇThis is a very long line that will be wrapped.
 6634
 6635            This is another paragraph in the same selection.»
 6636
 6637            «\tThis is a very long indented line that will be wrapped.ˇ»
 6638         "},
 6639        indoc! {"
 6640            «ˇThis is a very long line that will be
 6641            wrapped.
 6642
 6643            This is another paragraph in the same
 6644            selection.»
 6645
 6646            «\tThis is a very long indented line
 6647            \tthat will be wrapped.ˇ»
 6648         "},
 6649        plaintext_language,
 6650        &mut cx,
 6651    );
 6652
 6653    // Test that an empty comment line acts as a paragraph boundary
 6654    assert_rewrap(
 6655        indoc! {"
 6656            // ˇThis is a long comment that will be wrapped.
 6657            //
 6658            // And this is another long comment that will also be wrapped.ˇ
 6659         "},
 6660        indoc! {"
 6661            // ˇThis is a long comment that will be
 6662            // wrapped.
 6663            //
 6664            // And this is another long comment that
 6665            // will also be wrapped.ˇ
 6666         "},
 6667        cpp_language,
 6668        &mut cx,
 6669    );
 6670
 6671    #[track_caller]
 6672    fn assert_rewrap(
 6673        unwrapped_text: &str,
 6674        wrapped_text: &str,
 6675        language: Arc<Language>,
 6676        cx: &mut EditorTestContext,
 6677    ) {
 6678        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6679        cx.set_state(unwrapped_text);
 6680        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6681        cx.assert_editor_state(wrapped_text);
 6682    }
 6683}
 6684
 6685#[gpui::test]
 6686async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6687    init_test(cx, |settings| {
 6688        settings.languages.0.extend([(
 6689            "Rust".into(),
 6690            LanguageSettingsContent {
 6691                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6692                preferred_line_length: Some(40),
 6693                ..Default::default()
 6694            },
 6695        )])
 6696    });
 6697
 6698    let mut cx = EditorTestContext::new(cx).await;
 6699
 6700    let rust_lang = Arc::new(
 6701        Language::new(
 6702            LanguageConfig {
 6703                name: "Rust".into(),
 6704                line_comments: vec!["// ".into()],
 6705                block_comment: Some(BlockCommentConfig {
 6706                    start: "/*".into(),
 6707                    end: "*/".into(),
 6708                    prefix: "* ".into(),
 6709                    tab_size: 1,
 6710                }),
 6711                documentation_comment: Some(BlockCommentConfig {
 6712                    start: "/**".into(),
 6713                    end: "*/".into(),
 6714                    prefix: "* ".into(),
 6715                    tab_size: 1,
 6716                }),
 6717
 6718                ..LanguageConfig::default()
 6719            },
 6720            Some(tree_sitter_rust::LANGUAGE.into()),
 6721        )
 6722        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6723        .unwrap(),
 6724    );
 6725
 6726    // regular block comment
 6727    assert_rewrap(
 6728        indoc! {"
 6729            /*
 6730             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6731             */
 6732            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6733        "},
 6734        indoc! {"
 6735            /*
 6736             *ˇ Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */
 6739            /*
 6740             *ˇ Lorem ipsum dolor sit amet,
 6741             * consectetur adipiscing elit.
 6742             */
 6743        "},
 6744        rust_lang.clone(),
 6745        &mut cx,
 6746    );
 6747
 6748    // indent is respected
 6749    assert_rewrap(
 6750        indoc! {"
 6751            {}
 6752                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6753        "},
 6754        indoc! {"
 6755            {}
 6756                /*
 6757                 *ˇ Lorem ipsum dolor sit amet,
 6758                 * consectetur adipiscing elit.
 6759                 */
 6760        "},
 6761        rust_lang.clone(),
 6762        &mut cx,
 6763    );
 6764
 6765    // short block comments with inline delimiters
 6766    assert_rewrap(
 6767        indoc! {"
 6768            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6769            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6770             */
 6771            /*
 6772             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6773        "},
 6774        indoc! {"
 6775            /*
 6776             *ˇ Lorem ipsum dolor sit amet,
 6777             * consectetur adipiscing elit.
 6778             */
 6779            /*
 6780             *ˇ Lorem ipsum dolor sit amet,
 6781             * consectetur adipiscing elit.
 6782             */
 6783            /*
 6784             *ˇ Lorem ipsum dolor sit amet,
 6785             * consectetur adipiscing elit.
 6786             */
 6787        "},
 6788        rust_lang.clone(),
 6789        &mut cx,
 6790    );
 6791
 6792    // multiline block comment with inline start/end delimiters
 6793    assert_rewrap(
 6794        indoc! {"
 6795            /*ˇ Lorem ipsum dolor sit amet,
 6796             * consectetur adipiscing elit. */
 6797        "},
 6798        indoc! {"
 6799            /*
 6800             *ˇ Lorem ipsum dolor sit amet,
 6801             * consectetur adipiscing elit.
 6802             */
 6803        "},
 6804        rust_lang.clone(),
 6805        &mut cx,
 6806    );
 6807
 6808    // block comment rewrap still respects paragraph bounds
 6809    assert_rewrap(
 6810        indoc! {"
 6811            /*
 6812             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6813             *
 6814             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6815             */
 6816        "},
 6817        indoc! {"
 6818            /*
 6819             *ˇ Lorem ipsum dolor sit amet,
 6820             * consectetur adipiscing elit.
 6821             *
 6822             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6823             */
 6824        "},
 6825        rust_lang.clone(),
 6826        &mut cx,
 6827    );
 6828
 6829    // documentation comments
 6830    assert_rewrap(
 6831        indoc! {"
 6832            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6833            /**
 6834             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6835             */
 6836        "},
 6837        indoc! {"
 6838            /**
 6839             *ˇ Lorem ipsum dolor sit amet,
 6840             * consectetur adipiscing elit.
 6841             */
 6842            /**
 6843             *ˇ Lorem ipsum dolor sit amet,
 6844             * consectetur adipiscing elit.
 6845             */
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // different, adjacent comments
 6852    assert_rewrap(
 6853        indoc! {"
 6854            /**
 6855             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6856             */
 6857            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6858            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6859        "},
 6860        indoc! {"
 6861            /**
 6862             *ˇ Lorem ipsum dolor sit amet,
 6863             * consectetur adipiscing elit.
 6864             */
 6865            /*
 6866             *ˇ Lorem ipsum dolor sit amet,
 6867             * consectetur adipiscing elit.
 6868             */
 6869            //ˇ Lorem ipsum dolor sit amet,
 6870            // consectetur adipiscing elit.
 6871        "},
 6872        rust_lang.clone(),
 6873        &mut cx,
 6874    );
 6875
 6876    // selection w/ single short block comment
 6877    assert_rewrap(
 6878        indoc! {"
 6879            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6880        "},
 6881        indoc! {"
 6882            «/*
 6883             * Lorem ipsum dolor sit amet,
 6884             * consectetur adipiscing elit.
 6885             */ˇ»
 6886        "},
 6887        rust_lang.clone(),
 6888        &mut cx,
 6889    );
 6890
 6891    // rewrapping a single comment w/ abutting comments
 6892    assert_rewrap(
 6893        indoc! {"
 6894            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6895            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6896        "},
 6897        indoc! {"
 6898            /*
 6899             * ˇLorem ipsum dolor sit amet,
 6900             * consectetur adipiscing elit.
 6901             */
 6902            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6903        "},
 6904        rust_lang.clone(),
 6905        &mut cx,
 6906    );
 6907
 6908    // selection w/ non-abutting short block comments
 6909    assert_rewrap(
 6910        indoc! {"
 6911            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6912
 6913            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6914        "},
 6915        indoc! {"
 6916            «/*
 6917             * Lorem ipsum dolor sit amet,
 6918             * consectetur adipiscing elit.
 6919             */
 6920
 6921            /*
 6922             * Lorem ipsum dolor sit amet,
 6923             * consectetur adipiscing elit.
 6924             */ˇ»
 6925        "},
 6926        rust_lang.clone(),
 6927        &mut cx,
 6928    );
 6929
 6930    // selection of multiline block comments
 6931    assert_rewrap(
 6932        indoc! {"
 6933            «/* Lorem ipsum dolor sit amet,
 6934             * consectetur adipiscing elit. */ˇ»
 6935        "},
 6936        indoc! {"
 6937            «/*
 6938             * Lorem ipsum dolor sit amet,
 6939             * consectetur adipiscing elit.
 6940             */ˇ»
 6941        "},
 6942        rust_lang.clone(),
 6943        &mut cx,
 6944    );
 6945
 6946    // partial selection of multiline block comments
 6947    assert_rewrap(
 6948        indoc! {"
 6949            «/* Lorem ipsum dolor sit amet,ˇ»
 6950             * consectetur adipiscing elit. */
 6951            /* Lorem ipsum dolor sit amet,
 6952             «* consectetur adipiscing elit. */ˇ»
 6953        "},
 6954        indoc! {"
 6955            «/*
 6956             * Lorem ipsum dolor sit amet,ˇ»
 6957             * consectetur adipiscing elit. */
 6958            /* Lorem ipsum dolor sit amet,
 6959             «* consectetur adipiscing elit.
 6960             */ˇ»
 6961        "},
 6962        rust_lang.clone(),
 6963        &mut cx,
 6964    );
 6965
 6966    // selection w/ abutting short block comments
 6967    // TODO: should not be combined; should rewrap as 2 comments
 6968    assert_rewrap(
 6969        indoc! {"
 6970            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6971            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6972        "},
 6973        // desired behavior:
 6974        // indoc! {"
 6975        //     «/*
 6976        //      * Lorem ipsum dolor sit amet,
 6977        //      * consectetur adipiscing elit.
 6978        //      */
 6979        //     /*
 6980        //      * Lorem ipsum dolor sit amet,
 6981        //      * consectetur adipiscing elit.
 6982        //      */ˇ»
 6983        // "},
 6984        // actual behaviour:
 6985        indoc! {"
 6986            «/*
 6987             * Lorem ipsum dolor sit amet,
 6988             * consectetur adipiscing elit. Lorem
 6989             * ipsum dolor sit amet, consectetur
 6990             * adipiscing elit.
 6991             */ˇ»
 6992        "},
 6993        rust_lang.clone(),
 6994        &mut cx,
 6995    );
 6996
 6997    // TODO: same as above, but with delimiters on separate line
 6998    // assert_rewrap(
 6999    //     indoc! {"
 7000    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7001    //          */
 7002    //         /*
 7003    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7004    //     "},
 7005    //     // desired:
 7006    //     // indoc! {"
 7007    //     //     «/*
 7008    //     //      * Lorem ipsum dolor sit amet,
 7009    //     //      * consectetur adipiscing elit.
 7010    //     //      */
 7011    //     //     /*
 7012    //     //      * Lorem ipsum dolor sit amet,
 7013    //     //      * consectetur adipiscing elit.
 7014    //     //      */ˇ»
 7015    //     // "},
 7016    //     // actual: (but with trailing w/s on the empty lines)
 7017    //     indoc! {"
 7018    //         «/*
 7019    //          * Lorem ipsum dolor sit amet,
 7020    //          * consectetur adipiscing elit.
 7021    //          *
 7022    //          */
 7023    //         /*
 7024    //          *
 7025    //          * Lorem ipsum dolor sit amet,
 7026    //          * consectetur adipiscing elit.
 7027    //          */ˇ»
 7028    //     "},
 7029    //     rust_lang.clone(),
 7030    //     &mut cx,
 7031    // );
 7032
 7033    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7034    assert_rewrap(
 7035        indoc! {"
 7036            /*
 7037             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7038             */
 7039            /*
 7040             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7041            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7042        "},
 7043        // desired:
 7044        // indoc! {"
 7045        //     /*
 7046        //      *ˇ Lorem ipsum dolor sit amet,
 7047        //      * consectetur adipiscing elit.
 7048        //      */
 7049        //     /*
 7050        //      *ˇ Lorem ipsum dolor sit amet,
 7051        //      * consectetur adipiscing elit.
 7052        //      */
 7053        //     /*
 7054        //      *ˇ Lorem ipsum dolor sit amet
 7055        //      */ /* consectetur adipiscing elit. */
 7056        // "},
 7057        // actual:
 7058        indoc! {"
 7059            /*
 7060             //ˇ Lorem ipsum dolor sit amet,
 7061             // consectetur adipiscing elit.
 7062             */
 7063            /*
 7064             * //ˇ Lorem ipsum dolor sit amet,
 7065             * consectetur adipiscing elit.
 7066             */
 7067            /*
 7068             *ˇ Lorem ipsum dolor sit amet */ /*
 7069             * consectetur adipiscing elit.
 7070             */
 7071        "},
 7072        rust_lang,
 7073        &mut cx,
 7074    );
 7075
 7076    #[track_caller]
 7077    fn assert_rewrap(
 7078        unwrapped_text: &str,
 7079        wrapped_text: &str,
 7080        language: Arc<Language>,
 7081        cx: &mut EditorTestContext,
 7082    ) {
 7083        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7084        cx.set_state(unwrapped_text);
 7085        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7086        cx.assert_editor_state(wrapped_text);
 7087    }
 7088}
 7089
 7090#[gpui::test]
 7091async fn test_hard_wrap(cx: &mut TestAppContext) {
 7092    init_test(cx, |_| {});
 7093    let mut cx = EditorTestContext::new(cx).await;
 7094
 7095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7096    cx.update_editor(|editor, _, cx| {
 7097        editor.set_hard_wrap(Some(14), cx);
 7098    });
 7099
 7100    cx.set_state(indoc!(
 7101        "
 7102        one two three ˇ
 7103        "
 7104    ));
 7105    cx.simulate_input("four");
 7106    cx.run_until_parked();
 7107
 7108    cx.assert_editor_state(indoc!(
 7109        "
 7110        one two three
 7111        fourˇ
 7112        "
 7113    ));
 7114
 7115    cx.update_editor(|editor, window, cx| {
 7116        editor.newline(&Default::default(), window, cx);
 7117    });
 7118    cx.run_until_parked();
 7119    cx.assert_editor_state(indoc!(
 7120        "
 7121        one two three
 7122        four
 7123        ˇ
 7124        "
 7125    ));
 7126
 7127    cx.simulate_input("five");
 7128    cx.run_until_parked();
 7129    cx.assert_editor_state(indoc!(
 7130        "
 7131        one two three
 7132        four
 7133        fiveˇ
 7134        "
 7135    ));
 7136
 7137    cx.update_editor(|editor, window, cx| {
 7138        editor.newline(&Default::default(), window, cx);
 7139    });
 7140    cx.run_until_parked();
 7141    cx.simulate_input("# ");
 7142    cx.run_until_parked();
 7143    cx.assert_editor_state(indoc!(
 7144        "
 7145        one two three
 7146        four
 7147        five
 7148        # ˇ
 7149        "
 7150    ));
 7151
 7152    cx.update_editor(|editor, window, cx| {
 7153        editor.newline(&Default::default(), window, cx);
 7154    });
 7155    cx.run_until_parked();
 7156    cx.assert_editor_state(indoc!(
 7157        "
 7158        one two three
 7159        four
 7160        five
 7161        #\x20
 7162 7163        "
 7164    ));
 7165
 7166    cx.simulate_input(" 6");
 7167    cx.run_until_parked();
 7168    cx.assert_editor_state(indoc!(
 7169        "
 7170        one two three
 7171        four
 7172        five
 7173        #
 7174        # 6ˇ
 7175        "
 7176    ));
 7177}
 7178
 7179#[gpui::test]
 7180async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7181    init_test(cx, |_| {});
 7182
 7183    let mut cx = EditorTestContext::new(cx).await;
 7184
 7185    cx.set_state(indoc! {"The quick brownˇ"});
 7186    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7187    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7188
 7189    cx.set_state(indoc! {"The emacs foxˇ"});
 7190    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7191    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7192
 7193    cx.set_state(indoc! {"
 7194        The quick« brownˇ»
 7195        fox jumps overˇ
 7196        the lazy dog"});
 7197    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7198    cx.assert_editor_state(indoc! {"
 7199        The quickˇ
 7200        ˇthe lazy dog"});
 7201
 7202    cx.set_state(indoc! {"
 7203        The quick« brownˇ»
 7204        fox jumps overˇ
 7205        the lazy dog"});
 7206    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7207    cx.assert_editor_state(indoc! {"
 7208        The quickˇ
 7209        fox jumps overˇthe lazy dog"});
 7210
 7211    cx.set_state(indoc! {"
 7212        The quick« brownˇ»
 7213        fox jumps overˇ
 7214        the lazy dog"});
 7215    cx.update_editor(|e, window, cx| {
 7216        e.cut_to_end_of_line(
 7217            &CutToEndOfLine {
 7218                stop_at_newlines: true,
 7219            },
 7220            window,
 7221            cx,
 7222        )
 7223    });
 7224    cx.assert_editor_state(indoc! {"
 7225        The quickˇ
 7226        fox jumps overˇ
 7227        the lazy dog"});
 7228
 7229    cx.set_state(indoc! {"
 7230        The quick« brownˇ»
 7231        fox jumps overˇ
 7232        the lazy dog"});
 7233    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7234    cx.assert_editor_state(indoc! {"
 7235        The quickˇ
 7236        fox jumps overˇthe lazy dog"});
 7237}
 7238
 7239#[gpui::test]
 7240async fn test_clipboard(cx: &mut TestAppContext) {
 7241    init_test(cx, |_| {});
 7242
 7243    let mut cx = EditorTestContext::new(cx).await;
 7244
 7245    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7246    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7247    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7248
 7249    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7250    cx.set_state("two ˇfour ˇsix ˇ");
 7251    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7252    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7253
 7254    // Paste again but with only two cursors. Since the number of cursors doesn't
 7255    // match the number of slices in the clipboard, the entire clipboard text
 7256    // is pasted at each cursor.
 7257    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7258    cx.update_editor(|e, window, cx| {
 7259        e.handle_input("( ", window, cx);
 7260        e.paste(&Paste, window, cx);
 7261        e.handle_input(") ", window, cx);
 7262    });
 7263    cx.assert_editor_state(
 7264        &([
 7265            "( one✅ ",
 7266            "three ",
 7267            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7268            "three ",
 7269            "five ) ˇ",
 7270        ]
 7271        .join("\n")),
 7272    );
 7273
 7274    // Cut with three selections, one of which is full-line.
 7275    cx.set_state(indoc! {"
 7276        1«2ˇ»3
 7277        4ˇ567
 7278        «8ˇ»9"});
 7279    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7280    cx.assert_editor_state(indoc! {"
 7281        1ˇ3
 7282        ˇ9"});
 7283
 7284    // Paste with three selections, noticing how the copied selection that was full-line
 7285    // gets inserted before the second cursor.
 7286    cx.set_state(indoc! {"
 7287        1ˇ3
 7288 7289        «oˇ»ne"});
 7290    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7291    cx.assert_editor_state(indoc! {"
 7292        12ˇ3
 7293        4567
 7294 7295        8ˇne"});
 7296
 7297    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7298    cx.set_state(indoc! {"
 7299        The quick brown
 7300        fox juˇmps over
 7301        the lazy dog"});
 7302    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7303    assert_eq!(
 7304        cx.read_from_clipboard()
 7305            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7306        Some("fox jumps over\n".to_string())
 7307    );
 7308
 7309    // Paste with three selections, noticing how the copied full-line selection is inserted
 7310    // before the empty selections but replaces the selection that is non-empty.
 7311    cx.set_state(indoc! {"
 7312        Tˇhe quick brown
 7313        «foˇ»x jumps over
 7314        tˇhe lazy dog"});
 7315    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7316    cx.assert_editor_state(indoc! {"
 7317        fox jumps over
 7318        Tˇhe quick brown
 7319        fox jumps over
 7320        ˇx jumps over
 7321        fox jumps over
 7322        tˇhe lazy dog"});
 7323}
 7324
 7325#[gpui::test]
 7326async fn test_copy_trim(cx: &mut TestAppContext) {
 7327    init_test(cx, |_| {});
 7328
 7329    let mut cx = EditorTestContext::new(cx).await;
 7330    cx.set_state(
 7331        r#"            «for selection in selections.iter() {
 7332            let mut start = selection.start;
 7333            let mut end = selection.end;
 7334            let is_entire_line = selection.is_empty();
 7335            if is_entire_line {
 7336                start = Point::new(start.row, 0);ˇ»
 7337                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7338            }
 7339        "#,
 7340    );
 7341    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7342    assert_eq!(
 7343        cx.read_from_clipboard()
 7344            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7345        Some(
 7346            "for selection in selections.iter() {
 7347            let mut start = selection.start;
 7348            let mut end = selection.end;
 7349            let is_entire_line = selection.is_empty();
 7350            if is_entire_line {
 7351                start = Point::new(start.row, 0);"
 7352                .to_string()
 7353        ),
 7354        "Regular copying preserves all indentation selected",
 7355    );
 7356    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7357    assert_eq!(
 7358        cx.read_from_clipboard()
 7359            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7360        Some(
 7361            "for selection in selections.iter() {
 7362let mut start = selection.start;
 7363let mut end = selection.end;
 7364let is_entire_line = selection.is_empty();
 7365if is_entire_line {
 7366    start = Point::new(start.row, 0);"
 7367                .to_string()
 7368        ),
 7369        "Copying with stripping should strip all leading whitespaces"
 7370    );
 7371
 7372    cx.set_state(
 7373        r#"       «     for selection in selections.iter() {
 7374            let mut start = selection.start;
 7375            let mut end = selection.end;
 7376            let is_entire_line = selection.is_empty();
 7377            if is_entire_line {
 7378                start = Point::new(start.row, 0);ˇ»
 7379                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7380            }
 7381        "#,
 7382    );
 7383    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7384    assert_eq!(
 7385        cx.read_from_clipboard()
 7386            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7387        Some(
 7388            "     for selection in selections.iter() {
 7389            let mut start = selection.start;
 7390            let mut end = selection.end;
 7391            let is_entire_line = selection.is_empty();
 7392            if is_entire_line {
 7393                start = Point::new(start.row, 0);"
 7394                .to_string()
 7395        ),
 7396        "Regular copying preserves all indentation selected",
 7397    );
 7398    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7399    assert_eq!(
 7400        cx.read_from_clipboard()
 7401            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7402        Some(
 7403            "for selection in selections.iter() {
 7404let mut start = selection.start;
 7405let mut end = selection.end;
 7406let is_entire_line = selection.is_empty();
 7407if is_entire_line {
 7408    start = Point::new(start.row, 0);"
 7409                .to_string()
 7410        ),
 7411        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7412    );
 7413
 7414    cx.set_state(
 7415        r#"       «ˇ     for selection in selections.iter() {
 7416            let mut start = selection.start;
 7417            let mut end = selection.end;
 7418            let is_entire_line = selection.is_empty();
 7419            if is_entire_line {
 7420                start = Point::new(start.row, 0);»
 7421                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7422            }
 7423        "#,
 7424    );
 7425    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7426    assert_eq!(
 7427        cx.read_from_clipboard()
 7428            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7429        Some(
 7430            "     for selection in selections.iter() {
 7431            let mut start = selection.start;
 7432            let mut end = selection.end;
 7433            let is_entire_line = selection.is_empty();
 7434            if is_entire_line {
 7435                start = Point::new(start.row, 0);"
 7436                .to_string()
 7437        ),
 7438        "Regular copying for reverse selection works the same",
 7439    );
 7440    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7441    assert_eq!(
 7442        cx.read_from_clipboard()
 7443            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7444        Some(
 7445            "for selection in selections.iter() {
 7446let mut start = selection.start;
 7447let mut end = selection.end;
 7448let is_entire_line = selection.is_empty();
 7449if is_entire_line {
 7450    start = Point::new(start.row, 0);"
 7451                .to_string()
 7452        ),
 7453        "Copying with stripping for reverse selection works the same"
 7454    );
 7455
 7456    cx.set_state(
 7457        r#"            for selection «in selections.iter() {
 7458            let mut start = selection.start;
 7459            let mut end = selection.end;
 7460            let is_entire_line = selection.is_empty();
 7461            if is_entire_line {
 7462                start = Point::new(start.row, 0);ˇ»
 7463                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7464            }
 7465        "#,
 7466    );
 7467    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7468    assert_eq!(
 7469        cx.read_from_clipboard()
 7470            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7471        Some(
 7472            "in selections.iter() {
 7473            let mut start = selection.start;
 7474            let mut end = selection.end;
 7475            let is_entire_line = selection.is_empty();
 7476            if is_entire_line {
 7477                start = Point::new(start.row, 0);"
 7478                .to_string()
 7479        ),
 7480        "When selecting past the indent, the copying works as usual",
 7481    );
 7482    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7483    assert_eq!(
 7484        cx.read_from_clipboard()
 7485            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7486        Some(
 7487            "in selections.iter() {
 7488            let mut start = selection.start;
 7489            let mut end = selection.end;
 7490            let is_entire_line = selection.is_empty();
 7491            if is_entire_line {
 7492                start = Point::new(start.row, 0);"
 7493                .to_string()
 7494        ),
 7495        "When selecting past the indent, nothing is trimmed"
 7496    );
 7497
 7498    cx.set_state(
 7499        r#"            «for selection in selections.iter() {
 7500            let mut start = selection.start;
 7501
 7502            let mut end = selection.end;
 7503            let is_entire_line = selection.is_empty();
 7504            if is_entire_line {
 7505                start = Point::new(start.row, 0);
 7506ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7507            }
 7508        "#,
 7509    );
 7510    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7511    assert_eq!(
 7512        cx.read_from_clipboard()
 7513            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7514        Some(
 7515            "for selection in selections.iter() {
 7516let mut start = selection.start;
 7517
 7518let mut end = selection.end;
 7519let is_entire_line = selection.is_empty();
 7520if is_entire_line {
 7521    start = Point::new(start.row, 0);
 7522"
 7523            .to_string()
 7524        ),
 7525        "Copying with stripping should ignore empty lines"
 7526    );
 7527}
 7528
 7529#[gpui::test]
 7530async fn test_paste_multiline(cx: &mut TestAppContext) {
 7531    init_test(cx, |_| {});
 7532
 7533    let mut cx = EditorTestContext::new(cx).await;
 7534    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7535
 7536    // Cut an indented block, without the leading whitespace.
 7537    cx.set_state(indoc! {"
 7538        const a: B = (
 7539            c(),
 7540            «d(
 7541                e,
 7542                f
 7543            )ˇ»
 7544        );
 7545    "});
 7546    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7547    cx.assert_editor_state(indoc! {"
 7548        const a: B = (
 7549            c(),
 7550            ˇ
 7551        );
 7552    "});
 7553
 7554    // Paste it at the same position.
 7555    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7556    cx.assert_editor_state(indoc! {"
 7557        const a: B = (
 7558            c(),
 7559            d(
 7560                e,
 7561                f
 7562 7563        );
 7564    "});
 7565
 7566    // Paste it at a line with a lower indent level.
 7567    cx.set_state(indoc! {"
 7568        ˇ
 7569        const a: B = (
 7570            c(),
 7571        );
 7572    "});
 7573    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7574    cx.assert_editor_state(indoc! {"
 7575        d(
 7576            e,
 7577            f
 7578 7579        const a: B = (
 7580            c(),
 7581        );
 7582    "});
 7583
 7584    // Cut an indented block, with the leading whitespace.
 7585    cx.set_state(indoc! {"
 7586        const a: B = (
 7587            c(),
 7588        «    d(
 7589                e,
 7590                f
 7591            )
 7592        ˇ»);
 7593    "});
 7594    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7595    cx.assert_editor_state(indoc! {"
 7596        const a: B = (
 7597            c(),
 7598        ˇ);
 7599    "});
 7600
 7601    // Paste it at the same position.
 7602    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7603    cx.assert_editor_state(indoc! {"
 7604        const a: B = (
 7605            c(),
 7606            d(
 7607                e,
 7608                f
 7609            )
 7610        ˇ);
 7611    "});
 7612
 7613    // Paste it at a line with a higher indent level.
 7614    cx.set_state(indoc! {"
 7615        const a: B = (
 7616            c(),
 7617            d(
 7618                e,
 7619 7620            )
 7621        );
 7622    "});
 7623    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7624    cx.assert_editor_state(indoc! {"
 7625        const a: B = (
 7626            c(),
 7627            d(
 7628                e,
 7629                f    d(
 7630                    e,
 7631                    f
 7632                )
 7633        ˇ
 7634            )
 7635        );
 7636    "});
 7637
 7638    // Copy an indented block, starting mid-line
 7639    cx.set_state(indoc! {"
 7640        const a: B = (
 7641            c(),
 7642            somethin«g(
 7643                e,
 7644                f
 7645            )ˇ»
 7646        );
 7647    "});
 7648    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7649
 7650    // Paste it on a line with a lower indent level
 7651    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7652    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7653    cx.assert_editor_state(indoc! {"
 7654        const a: B = (
 7655            c(),
 7656            something(
 7657                e,
 7658                f
 7659            )
 7660        );
 7661        g(
 7662            e,
 7663            f
 7664"});
 7665}
 7666
 7667#[gpui::test]
 7668async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7669    init_test(cx, |_| {});
 7670
 7671    cx.write_to_clipboard(ClipboardItem::new_string(
 7672        "    d(\n        e\n    );\n".into(),
 7673    ));
 7674
 7675    let mut cx = EditorTestContext::new(cx).await;
 7676    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7677
 7678    cx.set_state(indoc! {"
 7679        fn a() {
 7680            b();
 7681            if c() {
 7682                ˇ
 7683            }
 7684        }
 7685    "});
 7686
 7687    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7688    cx.assert_editor_state(indoc! {"
 7689        fn a() {
 7690            b();
 7691            if c() {
 7692                d(
 7693                    e
 7694                );
 7695        ˇ
 7696            }
 7697        }
 7698    "});
 7699
 7700    cx.set_state(indoc! {"
 7701        fn a() {
 7702            b();
 7703            ˇ
 7704        }
 7705    "});
 7706
 7707    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7708    cx.assert_editor_state(indoc! {"
 7709        fn a() {
 7710            b();
 7711            d(
 7712                e
 7713            );
 7714        ˇ
 7715        }
 7716    "});
 7717}
 7718
 7719#[gpui::test]
 7720fn test_select_all(cx: &mut TestAppContext) {
 7721    init_test(cx, |_| {});
 7722
 7723    let editor = cx.add_window(|window, cx| {
 7724        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7725        build_editor(buffer, window, cx)
 7726    });
 7727    _ = editor.update(cx, |editor, window, cx| {
 7728        editor.select_all(&SelectAll, window, cx);
 7729        assert_eq!(
 7730            display_ranges(editor, cx),
 7731            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7732        );
 7733    });
 7734}
 7735
 7736#[gpui::test]
 7737fn test_select_line(cx: &mut TestAppContext) {
 7738    init_test(cx, |_| {});
 7739
 7740    let editor = cx.add_window(|window, cx| {
 7741        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7742        build_editor(buffer, window, cx)
 7743    });
 7744    _ = editor.update(cx, |editor, window, cx| {
 7745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7746            s.select_display_ranges([
 7747                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7748                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7749                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7750                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7751            ])
 7752        });
 7753        editor.select_line(&SelectLine, window, cx);
 7754        // Adjacent line selections should NOT merge (only overlapping ones do)
 7755        assert_eq!(
 7756            display_ranges(editor, cx),
 7757            vec![
 7758                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7759                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7760                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7761            ]
 7762        );
 7763    });
 7764
 7765    _ = editor.update(cx, |editor, window, cx| {
 7766        editor.select_line(&SelectLine, window, cx);
 7767        assert_eq!(
 7768            display_ranges(editor, cx),
 7769            vec![
 7770                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7771                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7772            ]
 7773        );
 7774    });
 7775
 7776    _ = editor.update(cx, |editor, window, cx| {
 7777        editor.select_line(&SelectLine, window, cx);
 7778        // Adjacent but not overlapping, so they stay separate
 7779        assert_eq!(
 7780            display_ranges(editor, cx),
 7781            vec![
 7782                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7783                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7784            ]
 7785        );
 7786    });
 7787}
 7788
 7789#[gpui::test]
 7790async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7791    init_test(cx, |_| {});
 7792    let mut cx = EditorTestContext::new(cx).await;
 7793
 7794    #[track_caller]
 7795    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7796        cx.set_state(initial_state);
 7797        cx.update_editor(|e, window, cx| {
 7798            e.split_selection_into_lines(&Default::default(), window, cx)
 7799        });
 7800        cx.assert_editor_state(expected_state);
 7801    }
 7802
 7803    // Selection starts and ends at the middle of lines, left-to-right
 7804    test(
 7805        &mut cx,
 7806        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7807        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7808    );
 7809    // Same thing, right-to-left
 7810    test(
 7811        &mut cx,
 7812        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7813        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7814    );
 7815
 7816    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7817    test(
 7818        &mut cx,
 7819        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7820        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7821    );
 7822    // Same thing, right-to-left
 7823    test(
 7824        &mut cx,
 7825        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7826        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7827    );
 7828
 7829    // Whole buffer, left-to-right, last line ends with newline
 7830    test(
 7831        &mut cx,
 7832        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7833        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7834    );
 7835    // Same thing, right-to-left
 7836    test(
 7837        &mut cx,
 7838        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7839        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7840    );
 7841
 7842    // Starts at the end of a line, ends at the start of another
 7843    test(
 7844        &mut cx,
 7845        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7846        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7847    );
 7848}
 7849
 7850#[gpui::test]
 7851async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7852    init_test(cx, |_| {});
 7853
 7854    let editor = cx.add_window(|window, cx| {
 7855        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7856        build_editor(buffer, window, cx)
 7857    });
 7858
 7859    // setup
 7860    _ = editor.update(cx, |editor, window, cx| {
 7861        editor.fold_creases(
 7862            vec![
 7863                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7864                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7865                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7866            ],
 7867            true,
 7868            window,
 7869            cx,
 7870        );
 7871        assert_eq!(
 7872            editor.display_text(cx),
 7873            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7874        );
 7875    });
 7876
 7877    _ = editor.update(cx, |editor, window, cx| {
 7878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7879            s.select_display_ranges([
 7880                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7882                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7883                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7884            ])
 7885        });
 7886        editor.split_selection_into_lines(&Default::default(), window, cx);
 7887        assert_eq!(
 7888            editor.display_text(cx),
 7889            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7890        );
 7891    });
 7892    EditorTestContext::for_editor(editor, cx)
 7893        .await
 7894        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7895
 7896    _ = editor.update(cx, |editor, window, cx| {
 7897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7898            s.select_display_ranges([
 7899                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7900            ])
 7901        });
 7902        editor.split_selection_into_lines(&Default::default(), window, cx);
 7903        assert_eq!(
 7904            editor.display_text(cx),
 7905            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7906        );
 7907        assert_eq!(
 7908            display_ranges(editor, cx),
 7909            [
 7910                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7911                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7912                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7913                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7914                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7915                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7916                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7917            ]
 7918        );
 7919    });
 7920    EditorTestContext::for_editor(editor, cx)
 7921        .await
 7922        .assert_editor_state(
 7923            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7924        );
 7925}
 7926
 7927#[gpui::test]
 7928async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7929    init_test(cx, |_| {});
 7930
 7931    let mut cx = EditorTestContext::new(cx).await;
 7932
 7933    cx.set_state(indoc!(
 7934        r#"abc
 7935           defˇghi
 7936
 7937           jk
 7938           nlmo
 7939           "#
 7940    ));
 7941
 7942    cx.update_editor(|editor, window, cx| {
 7943        editor.add_selection_above(&Default::default(), window, cx);
 7944    });
 7945
 7946    cx.assert_editor_state(indoc!(
 7947        r#"abcˇ
 7948           defˇghi
 7949
 7950           jk
 7951           nlmo
 7952           "#
 7953    ));
 7954
 7955    cx.update_editor(|editor, window, cx| {
 7956        editor.add_selection_above(&Default::default(), window, cx);
 7957    });
 7958
 7959    cx.assert_editor_state(indoc!(
 7960        r#"abcˇ
 7961            defˇghi
 7962
 7963            jk
 7964            nlmo
 7965            "#
 7966    ));
 7967
 7968    cx.update_editor(|editor, window, cx| {
 7969        editor.add_selection_below(&Default::default(), window, cx);
 7970    });
 7971
 7972    cx.assert_editor_state(indoc!(
 7973        r#"abc
 7974           defˇghi
 7975
 7976           jk
 7977           nlmo
 7978           "#
 7979    ));
 7980
 7981    cx.update_editor(|editor, window, cx| {
 7982        editor.undo_selection(&Default::default(), window, cx);
 7983    });
 7984
 7985    cx.assert_editor_state(indoc!(
 7986        r#"abcˇ
 7987           defˇghi
 7988
 7989           jk
 7990           nlmo
 7991           "#
 7992    ));
 7993
 7994    cx.update_editor(|editor, window, cx| {
 7995        editor.redo_selection(&Default::default(), window, cx);
 7996    });
 7997
 7998    cx.assert_editor_state(indoc!(
 7999        r#"abc
 8000           defˇghi
 8001
 8002           jk
 8003           nlmo
 8004           "#
 8005    ));
 8006
 8007    cx.update_editor(|editor, window, cx| {
 8008        editor.add_selection_below(&Default::default(), window, cx);
 8009    });
 8010
 8011    cx.assert_editor_state(indoc!(
 8012        r#"abc
 8013           defˇghi
 8014           ˇ
 8015           jk
 8016           nlmo
 8017           "#
 8018    ));
 8019
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.add_selection_below(&Default::default(), window, cx);
 8022    });
 8023
 8024    cx.assert_editor_state(indoc!(
 8025        r#"abc
 8026           defˇghi
 8027           ˇ
 8028           jkˇ
 8029           nlmo
 8030           "#
 8031    ));
 8032
 8033    cx.update_editor(|editor, window, cx| {
 8034        editor.add_selection_below(&Default::default(), window, cx);
 8035    });
 8036
 8037    cx.assert_editor_state(indoc!(
 8038        r#"abc
 8039           defˇghi
 8040           ˇ
 8041           jkˇ
 8042           nlmˇo
 8043           "#
 8044    ));
 8045
 8046    cx.update_editor(|editor, window, cx| {
 8047        editor.add_selection_below(&Default::default(), window, cx);
 8048    });
 8049
 8050    cx.assert_editor_state(indoc!(
 8051        r#"abc
 8052           defˇghi
 8053           ˇ
 8054           jkˇ
 8055           nlmˇo
 8056           ˇ"#
 8057    ));
 8058
 8059    // change selections
 8060    cx.set_state(indoc!(
 8061        r#"abc
 8062           def«ˇg»hi
 8063
 8064           jk
 8065           nlmo
 8066           "#
 8067    ));
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.add_selection_below(&Default::default(), window, cx);
 8071    });
 8072
 8073    cx.assert_editor_state(indoc!(
 8074        r#"abc
 8075           def«ˇg»hi
 8076
 8077           jk
 8078           nlm«ˇo»
 8079           "#
 8080    ));
 8081
 8082    cx.update_editor(|editor, window, cx| {
 8083        editor.add_selection_below(&Default::default(), window, cx);
 8084    });
 8085
 8086    cx.assert_editor_state(indoc!(
 8087        r#"abc
 8088           def«ˇg»hi
 8089
 8090           jk
 8091           nlm«ˇo»
 8092           "#
 8093    ));
 8094
 8095    cx.update_editor(|editor, window, cx| {
 8096        editor.add_selection_above(&Default::default(), window, cx);
 8097    });
 8098
 8099    cx.assert_editor_state(indoc!(
 8100        r#"abc
 8101           def«ˇg»hi
 8102
 8103           jk
 8104           nlmo
 8105           "#
 8106    ));
 8107
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.add_selection_above(&Default::default(), window, cx);
 8110    });
 8111
 8112    cx.assert_editor_state(indoc!(
 8113        r#"abc
 8114           def«ˇg»hi
 8115
 8116           jk
 8117           nlmo
 8118           "#
 8119    ));
 8120
 8121    // Change selections again
 8122    cx.set_state(indoc!(
 8123        r#"a«bc
 8124           defgˇ»hi
 8125
 8126           jk
 8127           nlmo
 8128           "#
 8129    ));
 8130
 8131    cx.update_editor(|editor, window, cx| {
 8132        editor.add_selection_below(&Default::default(), window, cx);
 8133    });
 8134
 8135    cx.assert_editor_state(indoc!(
 8136        r#"a«bcˇ»
 8137           d«efgˇ»hi
 8138
 8139           j«kˇ»
 8140           nlmo
 8141           "#
 8142    ));
 8143
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.add_selection_below(&Default::default(), window, cx);
 8146    });
 8147    cx.assert_editor_state(indoc!(
 8148        r#"a«bcˇ»
 8149           d«efgˇ»hi
 8150
 8151           j«kˇ»
 8152           n«lmoˇ»
 8153           "#
 8154    ));
 8155    cx.update_editor(|editor, window, cx| {
 8156        editor.add_selection_above(&Default::default(), window, cx);
 8157    });
 8158
 8159    cx.assert_editor_state(indoc!(
 8160        r#"a«bcˇ»
 8161           d«efgˇ»hi
 8162
 8163           j«kˇ»
 8164           nlmo
 8165           "#
 8166    ));
 8167
 8168    // Change selections again
 8169    cx.set_state(indoc!(
 8170        r#"abc
 8171           d«ˇefghi
 8172
 8173           jk
 8174           nlm»o
 8175           "#
 8176    ));
 8177
 8178    cx.update_editor(|editor, window, cx| {
 8179        editor.add_selection_above(&Default::default(), window, cx);
 8180    });
 8181
 8182    cx.assert_editor_state(indoc!(
 8183        r#"a«ˇbc»
 8184           d«ˇef»ghi
 8185
 8186           j«ˇk»
 8187           n«ˇlm»o
 8188           "#
 8189    ));
 8190
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.add_selection_below(&Default::default(), window, cx);
 8193    });
 8194
 8195    cx.assert_editor_state(indoc!(
 8196        r#"abc
 8197           d«ˇef»ghi
 8198
 8199           j«ˇk»
 8200           n«ˇlm»o
 8201           "#
 8202    ));
 8203}
 8204
 8205#[gpui::test]
 8206async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8207    init_test(cx, |_| {});
 8208    let mut cx = EditorTestContext::new(cx).await;
 8209
 8210    cx.set_state(indoc!(
 8211        r#"line onˇe
 8212           liˇne two
 8213           line three
 8214           line four"#
 8215    ));
 8216
 8217    cx.update_editor(|editor, window, cx| {
 8218        editor.add_selection_below(&Default::default(), window, cx);
 8219    });
 8220
 8221    // test multiple cursors expand in the same direction
 8222    cx.assert_editor_state(indoc!(
 8223        r#"line onˇe
 8224           liˇne twˇo
 8225           liˇne three
 8226           line four"#
 8227    ));
 8228
 8229    cx.update_editor(|editor, window, cx| {
 8230        editor.add_selection_below(&Default::default(), window, cx);
 8231    });
 8232
 8233    cx.update_editor(|editor, window, cx| {
 8234        editor.add_selection_below(&Default::default(), window, cx);
 8235    });
 8236
 8237    // test multiple cursors expand below overflow
 8238    cx.assert_editor_state(indoc!(
 8239        r#"line onˇe
 8240           liˇne twˇo
 8241           liˇne thˇree
 8242           liˇne foˇur"#
 8243    ));
 8244
 8245    cx.update_editor(|editor, window, cx| {
 8246        editor.add_selection_above(&Default::default(), window, cx);
 8247    });
 8248
 8249    // test multiple cursors retrieves back correctly
 8250    cx.assert_editor_state(indoc!(
 8251        r#"line onˇe
 8252           liˇne twˇo
 8253           liˇne thˇree
 8254           line four"#
 8255    ));
 8256
 8257    cx.update_editor(|editor, window, cx| {
 8258        editor.add_selection_above(&Default::default(), window, cx);
 8259    });
 8260
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.add_selection_above(&Default::default(), window, cx);
 8263    });
 8264
 8265    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8266    cx.assert_editor_state(indoc!(
 8267        r#"liˇne onˇe
 8268           liˇne two
 8269           line three
 8270           line four"#
 8271    ));
 8272
 8273    cx.update_editor(|editor, window, cx| {
 8274        editor.undo_selection(&Default::default(), window, cx);
 8275    });
 8276
 8277    // test undo
 8278    cx.assert_editor_state(indoc!(
 8279        r#"line onˇe
 8280           liˇne twˇo
 8281           line three
 8282           line four"#
 8283    ));
 8284
 8285    cx.update_editor(|editor, window, cx| {
 8286        editor.redo_selection(&Default::default(), window, cx);
 8287    });
 8288
 8289    // test redo
 8290    cx.assert_editor_state(indoc!(
 8291        r#"liˇne onˇe
 8292           liˇne two
 8293           line three
 8294           line four"#
 8295    ));
 8296
 8297    cx.set_state(indoc!(
 8298        r#"abcd
 8299           ef«ghˇ»
 8300           ijkl
 8301           «mˇ»nop"#
 8302    ));
 8303
 8304    cx.update_editor(|editor, window, cx| {
 8305        editor.add_selection_above(&Default::default(), window, cx);
 8306    });
 8307
 8308    // test multiple selections expand in the same direction
 8309    cx.assert_editor_state(indoc!(
 8310        r#"ab«cdˇ»
 8311           ef«ghˇ»
 8312           «iˇ»jkl
 8313           «mˇ»nop"#
 8314    ));
 8315
 8316    cx.update_editor(|editor, window, cx| {
 8317        editor.add_selection_above(&Default::default(), window, cx);
 8318    });
 8319
 8320    // test multiple selection upward overflow
 8321    cx.assert_editor_state(indoc!(
 8322        r#"ab«cdˇ»
 8323           «eˇ»f«ghˇ»
 8324           «iˇ»jkl
 8325           «mˇ»nop"#
 8326    ));
 8327
 8328    cx.update_editor(|editor, window, cx| {
 8329        editor.add_selection_below(&Default::default(), window, cx);
 8330    });
 8331
 8332    // test multiple selection retrieves back correctly
 8333    cx.assert_editor_state(indoc!(
 8334        r#"abcd
 8335           ef«ghˇ»
 8336           «iˇ»jkl
 8337           «mˇ»nop"#
 8338    ));
 8339
 8340    cx.update_editor(|editor, window, cx| {
 8341        editor.add_selection_below(&Default::default(), window, cx);
 8342    });
 8343
 8344    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8345    cx.assert_editor_state(indoc!(
 8346        r#"abcd
 8347           ef«ghˇ»
 8348           ij«klˇ»
 8349           «mˇ»nop"#
 8350    ));
 8351
 8352    cx.update_editor(|editor, window, cx| {
 8353        editor.undo_selection(&Default::default(), window, cx);
 8354    });
 8355
 8356    // test undo
 8357    cx.assert_editor_state(indoc!(
 8358        r#"abcd
 8359           ef«ghˇ»
 8360           «iˇ»jkl
 8361           «mˇ»nop"#
 8362    ));
 8363
 8364    cx.update_editor(|editor, window, cx| {
 8365        editor.redo_selection(&Default::default(), window, cx);
 8366    });
 8367
 8368    // test redo
 8369    cx.assert_editor_state(indoc!(
 8370        r#"abcd
 8371           ef«ghˇ»
 8372           ij«klˇ»
 8373           «mˇ»nop"#
 8374    ));
 8375}
 8376
 8377#[gpui::test]
 8378async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8379    init_test(cx, |_| {});
 8380    let mut cx = EditorTestContext::new(cx).await;
 8381
 8382    cx.set_state(indoc!(
 8383        r#"line onˇe
 8384           liˇne two
 8385           line three
 8386           line four"#
 8387    ));
 8388
 8389    cx.update_editor(|editor, window, cx| {
 8390        editor.add_selection_below(&Default::default(), window, cx);
 8391        editor.add_selection_below(&Default::default(), window, cx);
 8392        editor.add_selection_below(&Default::default(), window, cx);
 8393    });
 8394
 8395    // initial state with two multi cursor groups
 8396    cx.assert_editor_state(indoc!(
 8397        r#"line onˇe
 8398           liˇne twˇo
 8399           liˇne thˇree
 8400           liˇne foˇur"#
 8401    ));
 8402
 8403    // add single cursor in middle - simulate opt click
 8404    cx.update_editor(|editor, window, cx| {
 8405        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8406        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8407        editor.end_selection(window, cx);
 8408    });
 8409
 8410    cx.assert_editor_state(indoc!(
 8411        r#"line onˇe
 8412           liˇne twˇo
 8413           liˇneˇ thˇree
 8414           liˇne foˇur"#
 8415    ));
 8416
 8417    cx.update_editor(|editor, window, cx| {
 8418        editor.add_selection_above(&Default::default(), window, cx);
 8419    });
 8420
 8421    // test new added selection expands above and existing selection shrinks
 8422    cx.assert_editor_state(indoc!(
 8423        r#"line onˇe
 8424           liˇneˇ twˇo
 8425           liˇneˇ thˇree
 8426           line four"#
 8427    ));
 8428
 8429    cx.update_editor(|editor, window, cx| {
 8430        editor.add_selection_above(&Default::default(), window, cx);
 8431    });
 8432
 8433    // test new added selection expands above and existing selection shrinks
 8434    cx.assert_editor_state(indoc!(
 8435        r#"lineˇ onˇe
 8436           liˇneˇ twˇo
 8437           lineˇ three
 8438           line four"#
 8439    ));
 8440
 8441    // intial state with two selection groups
 8442    cx.set_state(indoc!(
 8443        r#"abcd
 8444           ef«ghˇ»
 8445           ijkl
 8446           «mˇ»nop"#
 8447    ));
 8448
 8449    cx.update_editor(|editor, window, cx| {
 8450        editor.add_selection_above(&Default::default(), window, cx);
 8451        editor.add_selection_above(&Default::default(), window, cx);
 8452    });
 8453
 8454    cx.assert_editor_state(indoc!(
 8455        r#"ab«cdˇ»
 8456           «eˇ»f«ghˇ»
 8457           «iˇ»jkl
 8458           «mˇ»nop"#
 8459    ));
 8460
 8461    // add single selection in middle - simulate opt drag
 8462    cx.update_editor(|editor, window, cx| {
 8463        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8464        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8465        editor.update_selection(
 8466            DisplayPoint::new(DisplayRow(2), 4),
 8467            0,
 8468            gpui::Point::<f32>::default(),
 8469            window,
 8470            cx,
 8471        );
 8472        editor.end_selection(window, cx);
 8473    });
 8474
 8475    cx.assert_editor_state(indoc!(
 8476        r#"ab«cdˇ»
 8477           «eˇ»f«ghˇ»
 8478           «iˇ»jk«lˇ»
 8479           «mˇ»nop"#
 8480    ));
 8481
 8482    cx.update_editor(|editor, window, cx| {
 8483        editor.add_selection_below(&Default::default(), window, cx);
 8484    });
 8485
 8486    // test new added selection expands below, others shrinks from above
 8487    cx.assert_editor_state(indoc!(
 8488        r#"abcd
 8489           ef«ghˇ»
 8490           «iˇ»jk«lˇ»
 8491           «mˇ»no«pˇ»"#
 8492    ));
 8493}
 8494
 8495#[gpui::test]
 8496async fn test_select_next(cx: &mut TestAppContext) {
 8497    init_test(cx, |_| {});
 8498    let mut cx = EditorTestContext::new(cx).await;
 8499
 8500    // Enable case sensitive search.
 8501    update_test_editor_settings(&mut cx, |settings| {
 8502        let mut search_settings = SearchSettingsContent::default();
 8503        search_settings.case_sensitive = Some(true);
 8504        settings.search = Some(search_settings);
 8505    });
 8506
 8507    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8508
 8509    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8510        .unwrap();
 8511    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8512
 8513    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8514        .unwrap();
 8515    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8516
 8517    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8518    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8519
 8520    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8521    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8522
 8523    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8524        .unwrap();
 8525    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8526
 8527    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8528        .unwrap();
 8529    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8530
 8531    // Test selection direction should be preserved
 8532    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8533
 8534    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8535        .unwrap();
 8536    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8537
 8538    // Test case sensitivity
 8539    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8540    cx.update_editor(|e, window, cx| {
 8541        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8542    });
 8543    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8544
 8545    // Disable case sensitive search.
 8546    update_test_editor_settings(&mut cx, |settings| {
 8547        let mut search_settings = SearchSettingsContent::default();
 8548        search_settings.case_sensitive = Some(false);
 8549        settings.search = Some(search_settings);
 8550    });
 8551
 8552    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8553    cx.update_editor(|e, window, cx| {
 8554        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8555        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8556    });
 8557    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8558}
 8559
 8560#[gpui::test]
 8561async fn test_select_all_matches(cx: &mut TestAppContext) {
 8562    init_test(cx, |_| {});
 8563    let mut cx = EditorTestContext::new(cx).await;
 8564
 8565    // Enable case sensitive search.
 8566    update_test_editor_settings(&mut cx, |settings| {
 8567        let mut search_settings = SearchSettingsContent::default();
 8568        search_settings.case_sensitive = Some(true);
 8569        settings.search = Some(search_settings);
 8570    });
 8571
 8572    // Test caret-only selections
 8573    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8574    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8575        .unwrap();
 8576    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8577
 8578    // Test left-to-right selections
 8579    cx.set_state("abc\n«abcˇ»\nabc");
 8580    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8581        .unwrap();
 8582    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8583
 8584    // Test right-to-left selections
 8585    cx.set_state("abc\n«ˇabc»\nabc");
 8586    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8587        .unwrap();
 8588    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8589
 8590    // Test selecting whitespace with caret selection
 8591    cx.set_state("abc\nˇ   abc\nabc");
 8592    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8593        .unwrap();
 8594    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8595
 8596    // Test selecting whitespace with left-to-right selection
 8597    cx.set_state("abc\n«ˇ  »abc\nabc");
 8598    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8599        .unwrap();
 8600    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8601
 8602    // Test no matches with right-to-left selection
 8603    cx.set_state("abc\n«  ˇ»abc\nabc");
 8604    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8605        .unwrap();
 8606    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8607
 8608    // Test with a single word and clip_at_line_ends=true (#29823)
 8609    cx.set_state("aˇbc");
 8610    cx.update_editor(|e, window, cx| {
 8611        e.set_clip_at_line_ends(true, cx);
 8612        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8613        e.set_clip_at_line_ends(false, cx);
 8614    });
 8615    cx.assert_editor_state("«abcˇ»");
 8616
 8617    // Test case sensitivity
 8618    cx.set_state("fˇoo\nFOO\nFoo");
 8619    cx.update_editor(|e, window, cx| {
 8620        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8621    });
 8622    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8623
 8624    // Disable case sensitive search.
 8625    update_test_editor_settings(&mut cx, |settings| {
 8626        let mut search_settings = SearchSettingsContent::default();
 8627        search_settings.case_sensitive = Some(false);
 8628        settings.search = Some(search_settings);
 8629    });
 8630
 8631    cx.set_state("fˇoo\nFOO\nFoo");
 8632    cx.update_editor(|e, window, cx| {
 8633        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8634    });
 8635    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8636}
 8637
 8638#[gpui::test]
 8639async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8640    init_test(cx, |_| {});
 8641
 8642    let mut cx = EditorTestContext::new(cx).await;
 8643
 8644    let large_body_1 = "\nd".repeat(200);
 8645    let large_body_2 = "\ne".repeat(200);
 8646
 8647    cx.set_state(&format!(
 8648        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8649    ));
 8650    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8651        let scroll_position = editor.scroll_position(cx);
 8652        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8653        scroll_position
 8654    });
 8655
 8656    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8657        .unwrap();
 8658    cx.assert_editor_state(&format!(
 8659        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8660    ));
 8661    let scroll_position_after_selection =
 8662        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8663    assert_eq!(
 8664        initial_scroll_position, scroll_position_after_selection,
 8665        "Scroll position should not change after selecting all matches"
 8666    );
 8667}
 8668
 8669#[gpui::test]
 8670async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8671    init_test(cx, |_| {});
 8672
 8673    let mut cx = EditorLspTestContext::new_rust(
 8674        lsp::ServerCapabilities {
 8675            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8676            ..Default::default()
 8677        },
 8678        cx,
 8679    )
 8680    .await;
 8681
 8682    cx.set_state(indoc! {"
 8683        line 1
 8684        line 2
 8685        linˇe 3
 8686        line 4
 8687        line 5
 8688    "});
 8689
 8690    // Make an edit
 8691    cx.update_editor(|editor, window, cx| {
 8692        editor.handle_input("X", window, cx);
 8693    });
 8694
 8695    // Move cursor to a different position
 8696    cx.update_editor(|editor, window, cx| {
 8697        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8698            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8699        });
 8700    });
 8701
 8702    cx.assert_editor_state(indoc! {"
 8703        line 1
 8704        line 2
 8705        linXe 3
 8706        line 4
 8707        liˇne 5
 8708    "});
 8709
 8710    cx.lsp
 8711        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8712            Ok(Some(vec![lsp::TextEdit::new(
 8713                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8714                "PREFIX ".to_string(),
 8715            )]))
 8716        });
 8717
 8718    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8719        .unwrap()
 8720        .await
 8721        .unwrap();
 8722
 8723    cx.assert_editor_state(indoc! {"
 8724        PREFIX line 1
 8725        line 2
 8726        linXe 3
 8727        line 4
 8728        liˇne 5
 8729    "});
 8730
 8731    // Undo formatting
 8732    cx.update_editor(|editor, window, cx| {
 8733        editor.undo(&Default::default(), window, cx);
 8734    });
 8735
 8736    // Verify cursor moved back to position after edit
 8737    cx.assert_editor_state(indoc! {"
 8738        line 1
 8739        line 2
 8740        linXˇe 3
 8741        line 4
 8742        line 5
 8743    "});
 8744}
 8745
 8746#[gpui::test]
 8747async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8748    init_test(cx, |_| {});
 8749
 8750    let mut cx = EditorTestContext::new(cx).await;
 8751
 8752    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8753    cx.update_editor(|editor, window, cx| {
 8754        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8755    });
 8756
 8757    cx.set_state(indoc! {"
 8758        line 1
 8759        line 2
 8760        linˇe 3
 8761        line 4
 8762        line 5
 8763        line 6
 8764        line 7
 8765        line 8
 8766        line 9
 8767        line 10
 8768    "});
 8769
 8770    let snapshot = cx.buffer_snapshot();
 8771    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8772
 8773    cx.update(|_, cx| {
 8774        provider.update(cx, |provider, _| {
 8775            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8776                id: None,
 8777                edits: vec![(edit_position..edit_position, "X".into())],
 8778                edit_preview: None,
 8779            }))
 8780        })
 8781    });
 8782
 8783    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8784    cx.update_editor(|editor, window, cx| {
 8785        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8786    });
 8787
 8788    cx.assert_editor_state(indoc! {"
 8789        line 1
 8790        line 2
 8791        lineXˇ 3
 8792        line 4
 8793        line 5
 8794        line 6
 8795        line 7
 8796        line 8
 8797        line 9
 8798        line 10
 8799    "});
 8800
 8801    cx.update_editor(|editor, window, cx| {
 8802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8803            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8804        });
 8805    });
 8806
 8807    cx.assert_editor_state(indoc! {"
 8808        line 1
 8809        line 2
 8810        lineX 3
 8811        line 4
 8812        line 5
 8813        line 6
 8814        line 7
 8815        line 8
 8816        line 9
 8817        liˇne 10
 8818    "});
 8819
 8820    cx.update_editor(|editor, window, cx| {
 8821        editor.undo(&Default::default(), window, cx);
 8822    });
 8823
 8824    cx.assert_editor_state(indoc! {"
 8825        line 1
 8826        line 2
 8827        lineˇ 3
 8828        line 4
 8829        line 5
 8830        line 6
 8831        line 7
 8832        line 8
 8833        line 9
 8834        line 10
 8835    "});
 8836}
 8837
 8838#[gpui::test]
 8839async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8840    init_test(cx, |_| {});
 8841
 8842    let mut cx = EditorTestContext::new(cx).await;
 8843    cx.set_state(
 8844        r#"let foo = 2;
 8845lˇet foo = 2;
 8846let fooˇ = 2;
 8847let foo = 2;
 8848let foo = ˇ2;"#,
 8849    );
 8850
 8851    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state(
 8854        r#"let foo = 2;
 8855«letˇ» foo = 2;
 8856let «fooˇ» = 2;
 8857let foo = 2;
 8858let foo = «2ˇ»;"#,
 8859    );
 8860
 8861    // noop for multiple selections with different contents
 8862    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8863        .unwrap();
 8864    cx.assert_editor_state(
 8865        r#"let foo = 2;
 8866«letˇ» foo = 2;
 8867let «fooˇ» = 2;
 8868let foo = 2;
 8869let foo = «2ˇ»;"#,
 8870    );
 8871
 8872    // Test last selection direction should be preserved
 8873    cx.set_state(
 8874        r#"let foo = 2;
 8875let foo = 2;
 8876let «fooˇ» = 2;
 8877let «ˇfoo» = 2;
 8878let foo = 2;"#,
 8879    );
 8880
 8881    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8882        .unwrap();
 8883    cx.assert_editor_state(
 8884        r#"let foo = 2;
 8885let foo = 2;
 8886let «fooˇ» = 2;
 8887let «ˇfoo» = 2;
 8888let «ˇfoo» = 2;"#,
 8889    );
 8890}
 8891
 8892#[gpui::test]
 8893async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8894    init_test(cx, |_| {});
 8895
 8896    let mut cx =
 8897        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8898
 8899    cx.assert_editor_state(indoc! {"
 8900        ˇbbb
 8901        ccc
 8902
 8903        bbb
 8904        ccc
 8905        "});
 8906    cx.dispatch_action(SelectPrevious::default());
 8907    cx.assert_editor_state(indoc! {"
 8908                «bbbˇ»
 8909                ccc
 8910
 8911                bbb
 8912                ccc
 8913                "});
 8914    cx.dispatch_action(SelectPrevious::default());
 8915    cx.assert_editor_state(indoc! {"
 8916                «bbbˇ»
 8917                ccc
 8918
 8919                «bbbˇ»
 8920                ccc
 8921                "});
 8922}
 8923
 8924#[gpui::test]
 8925async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8926    init_test(cx, |_| {});
 8927
 8928    let mut cx = EditorTestContext::new(cx).await;
 8929    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8930
 8931    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8932        .unwrap();
 8933    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8934
 8935    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8936        .unwrap();
 8937    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8938
 8939    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8940    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8941
 8942    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8943    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8944
 8945    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8946        .unwrap();
 8947    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8948
 8949    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8950        .unwrap();
 8951    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8952}
 8953
 8954#[gpui::test]
 8955async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8956    init_test(cx, |_| {});
 8957
 8958    let mut cx = EditorTestContext::new(cx).await;
 8959    cx.set_state("");
 8960
 8961    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8962        .unwrap();
 8963    cx.assert_editor_state("«aˇ»");
 8964    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8965        .unwrap();
 8966    cx.assert_editor_state("«aˇ»");
 8967}
 8968
 8969#[gpui::test]
 8970async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8971    init_test(cx, |_| {});
 8972
 8973    let mut cx = EditorTestContext::new(cx).await;
 8974    cx.set_state(
 8975        r#"let foo = 2;
 8976lˇet foo = 2;
 8977let fooˇ = 2;
 8978let foo = 2;
 8979let foo = ˇ2;"#,
 8980    );
 8981
 8982    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8983        .unwrap();
 8984    cx.assert_editor_state(
 8985        r#"let foo = 2;
 8986«letˇ» foo = 2;
 8987let «fooˇ» = 2;
 8988let foo = 2;
 8989let foo = «2ˇ»;"#,
 8990    );
 8991
 8992    // noop for multiple selections with different contents
 8993    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8994        .unwrap();
 8995    cx.assert_editor_state(
 8996        r#"let foo = 2;
 8997«letˇ» foo = 2;
 8998let «fooˇ» = 2;
 8999let foo = 2;
 9000let foo = «2ˇ»;"#,
 9001    );
 9002}
 9003
 9004#[gpui::test]
 9005async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9006    init_test(cx, |_| {});
 9007    let mut cx = EditorTestContext::new(cx).await;
 9008
 9009    // Enable case sensitive search.
 9010    update_test_editor_settings(&mut cx, |settings| {
 9011        let mut search_settings = SearchSettingsContent::default();
 9012        search_settings.case_sensitive = Some(true);
 9013        settings.search = Some(search_settings);
 9014    });
 9015
 9016    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9017
 9018    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9019        .unwrap();
 9020    // selection direction is preserved
 9021    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9022
 9023    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9024        .unwrap();
 9025    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9026
 9027    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9028    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9029
 9030    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9031    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9032
 9033    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9034        .unwrap();
 9035    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9036
 9037    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9038        .unwrap();
 9039    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9040
 9041    // Test case sensitivity
 9042    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9043    cx.update_editor(|e, window, cx| {
 9044        e.select_previous(&SelectPrevious::default(), window, cx)
 9045            .unwrap();
 9046        e.select_previous(&SelectPrevious::default(), window, cx)
 9047            .unwrap();
 9048    });
 9049    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9050
 9051    // Disable case sensitive search.
 9052    update_test_editor_settings(&mut cx, |settings| {
 9053        let mut search_settings = SearchSettingsContent::default();
 9054        search_settings.case_sensitive = Some(false);
 9055        settings.search = Some(search_settings);
 9056    });
 9057
 9058    cx.set_state("foo\nFOO\n«ˇFoo»");
 9059    cx.update_editor(|e, window, cx| {
 9060        e.select_previous(&SelectPrevious::default(), window, cx)
 9061            .unwrap();
 9062        e.select_previous(&SelectPrevious::default(), window, cx)
 9063            .unwrap();
 9064    });
 9065    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9066}
 9067
 9068#[gpui::test]
 9069async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9070    init_test(cx, |_| {});
 9071
 9072    let language = Arc::new(Language::new(
 9073        LanguageConfig::default(),
 9074        Some(tree_sitter_rust::LANGUAGE.into()),
 9075    ));
 9076
 9077    let text = r#"
 9078        use mod1::mod2::{mod3, mod4};
 9079
 9080        fn fn_1(param1: bool, param2: &str) {
 9081            let var1 = "text";
 9082        }
 9083    "#
 9084    .unindent();
 9085
 9086    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9087    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9088    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9089
 9090    editor
 9091        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9092        .await;
 9093
 9094    editor.update_in(cx, |editor, window, cx| {
 9095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9096            s.select_display_ranges([
 9097                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9098                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9099                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9100            ]);
 9101        });
 9102        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9103    });
 9104    editor.update(cx, |editor, cx| {
 9105        assert_text_with_selections(
 9106            editor,
 9107            indoc! {r#"
 9108                use mod1::mod2::{mod3, «mod4ˇ»};
 9109
 9110                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9111                    let var1 = "«ˇtext»";
 9112                }
 9113            "#},
 9114            cx,
 9115        );
 9116    });
 9117
 9118    editor.update_in(cx, |editor, window, cx| {
 9119        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9120    });
 9121    editor.update(cx, |editor, cx| {
 9122        assert_text_with_selections(
 9123            editor,
 9124            indoc! {r#"
 9125                use mod1::mod2::«{mod3, mod4}ˇ»;
 9126
 9127                «ˇfn fn_1(param1: bool, param2: &str) {
 9128                    let var1 = "text";
 9129 9130            "#},
 9131            cx,
 9132        );
 9133    });
 9134
 9135    editor.update_in(cx, |editor, window, cx| {
 9136        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9137    });
 9138    assert_eq!(
 9139        editor.update(cx, |editor, cx| editor
 9140            .selections
 9141            .display_ranges(&editor.display_snapshot(cx))),
 9142        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9143    );
 9144
 9145    // Trying to expand the selected syntax node one more time has no effect.
 9146    editor.update_in(cx, |editor, window, cx| {
 9147        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9148    });
 9149    assert_eq!(
 9150        editor.update(cx, |editor, cx| editor
 9151            .selections
 9152            .display_ranges(&editor.display_snapshot(cx))),
 9153        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9154    );
 9155
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9158    });
 9159    editor.update(cx, |editor, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                use mod1::mod2::«{mod3, mod4}ˇ»;
 9164
 9165                «ˇfn fn_1(param1: bool, param2: &str) {
 9166                    let var1 = "text";
 9167 9168            "#},
 9169            cx,
 9170        );
 9171    });
 9172
 9173    editor.update_in(cx, |editor, window, cx| {
 9174        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9175    });
 9176    editor.update(cx, |editor, cx| {
 9177        assert_text_with_selections(
 9178            editor,
 9179            indoc! {r#"
 9180                use mod1::mod2::{mod3, «mod4ˇ»};
 9181
 9182                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9183                    let var1 = "«ˇtext»";
 9184                }
 9185            "#},
 9186            cx,
 9187        );
 9188    });
 9189
 9190    editor.update_in(cx, |editor, window, cx| {
 9191        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9192    });
 9193    editor.update(cx, |editor, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                use mod1::mod2::{mod3, moˇd4};
 9198
 9199                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9200                    let var1 = "teˇxt";
 9201                }
 9202            "#},
 9203            cx,
 9204        );
 9205    });
 9206
 9207    // Trying to shrink the selected syntax node one more time has no effect.
 9208    editor.update_in(cx, |editor, window, cx| {
 9209        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9210    });
 9211    editor.update_in(cx, |editor, _, cx| {
 9212        assert_text_with_selections(
 9213            editor,
 9214            indoc! {r#"
 9215                use mod1::mod2::{mod3, moˇd4};
 9216
 9217                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9218                    let var1 = "teˇxt";
 9219                }
 9220            "#},
 9221            cx,
 9222        );
 9223    });
 9224
 9225    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9226    // a fold.
 9227    editor.update_in(cx, |editor, window, cx| {
 9228        editor.fold_creases(
 9229            vec![
 9230                Crease::simple(
 9231                    Point::new(0, 21)..Point::new(0, 24),
 9232                    FoldPlaceholder::test(),
 9233                ),
 9234                Crease::simple(
 9235                    Point::new(3, 20)..Point::new(3, 22),
 9236                    FoldPlaceholder::test(),
 9237                ),
 9238            ],
 9239            true,
 9240            window,
 9241            cx,
 9242        );
 9243        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9244    });
 9245    editor.update(cx, |editor, cx| {
 9246        assert_text_with_selections(
 9247            editor,
 9248            indoc! {r#"
 9249                use mod1::mod2::«{mod3, mod4}ˇ»;
 9250
 9251                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9252                    let var1 = "«ˇtext»";
 9253                }
 9254            "#},
 9255            cx,
 9256        );
 9257    });
 9258}
 9259
 9260#[gpui::test]
 9261async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9262    init_test(cx, |_| {});
 9263
 9264    let language = Arc::new(Language::new(
 9265        LanguageConfig::default(),
 9266        Some(tree_sitter_rust::LANGUAGE.into()),
 9267    ));
 9268
 9269    let text = "let a = 2;";
 9270
 9271    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9272    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9273    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9274
 9275    editor
 9276        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9277        .await;
 9278
 9279    // Test case 1: Cursor at end of word
 9280    editor.update_in(cx, |editor, window, cx| {
 9281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9282            s.select_display_ranges([
 9283                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9284            ]);
 9285        });
 9286    });
 9287    editor.update(cx, |editor, cx| {
 9288        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9289    });
 9290    editor.update_in(cx, |editor, window, cx| {
 9291        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9292    });
 9293    editor.update(cx, |editor, cx| {
 9294        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9295    });
 9296    editor.update_in(cx, |editor, window, cx| {
 9297        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9298    });
 9299    editor.update(cx, |editor, cx| {
 9300        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9301    });
 9302
 9303    // Test case 2: Cursor at end of statement
 9304    editor.update_in(cx, |editor, window, cx| {
 9305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9306            s.select_display_ranges([
 9307                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9308            ]);
 9309        });
 9310    });
 9311    editor.update(cx, |editor, cx| {
 9312        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9313    });
 9314    editor.update_in(cx, |editor, window, cx| {
 9315        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9316    });
 9317    editor.update(cx, |editor, cx| {
 9318        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9319    });
 9320}
 9321
 9322#[gpui::test]
 9323async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9324    init_test(cx, |_| {});
 9325
 9326    let language = Arc::new(Language::new(
 9327        LanguageConfig {
 9328            name: "JavaScript".into(),
 9329            ..Default::default()
 9330        },
 9331        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9332    ));
 9333
 9334    let text = r#"
 9335        let a = {
 9336            key: "value",
 9337        };
 9338    "#
 9339    .unindent();
 9340
 9341    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9342    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9343    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9344
 9345    editor
 9346        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9347        .await;
 9348
 9349    // Test case 1: Cursor after '{'
 9350    editor.update_in(cx, |editor, window, cx| {
 9351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9352            s.select_display_ranges([
 9353                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9354            ]);
 9355        });
 9356    });
 9357    editor.update(cx, |editor, cx| {
 9358        assert_text_with_selections(
 9359            editor,
 9360            indoc! {r#"
 9361                let a = {ˇ
 9362                    key: "value",
 9363                };
 9364            "#},
 9365            cx,
 9366        );
 9367    });
 9368    editor.update_in(cx, |editor, window, cx| {
 9369        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9370    });
 9371    editor.update(cx, |editor, cx| {
 9372        assert_text_with_selections(
 9373            editor,
 9374            indoc! {r#"
 9375                let a = «ˇ{
 9376                    key: "value",
 9377                }»;
 9378            "#},
 9379            cx,
 9380        );
 9381    });
 9382
 9383    // Test case 2: Cursor after ':'
 9384    editor.update_in(cx, |editor, window, cx| {
 9385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9386            s.select_display_ranges([
 9387                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9388            ]);
 9389        });
 9390    });
 9391    editor.update(cx, |editor, cx| {
 9392        assert_text_with_selections(
 9393            editor,
 9394            indoc! {r#"
 9395                let a = {
 9396                    key:ˇ "value",
 9397                };
 9398            "#},
 9399            cx,
 9400        );
 9401    });
 9402    editor.update_in(cx, |editor, window, cx| {
 9403        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9404    });
 9405    editor.update(cx, |editor, cx| {
 9406        assert_text_with_selections(
 9407            editor,
 9408            indoc! {r#"
 9409                let a = {
 9410                    «ˇkey: "value"»,
 9411                };
 9412            "#},
 9413            cx,
 9414        );
 9415    });
 9416    editor.update_in(cx, |editor, window, cx| {
 9417        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9418    });
 9419    editor.update(cx, |editor, cx| {
 9420        assert_text_with_selections(
 9421            editor,
 9422            indoc! {r#"
 9423                let a = «ˇ{
 9424                    key: "value",
 9425                }»;
 9426            "#},
 9427            cx,
 9428        );
 9429    });
 9430
 9431    // Test case 3: Cursor after ','
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9434            s.select_display_ranges([
 9435                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9436            ]);
 9437        });
 9438    });
 9439    editor.update(cx, |editor, cx| {
 9440        assert_text_with_selections(
 9441            editor,
 9442            indoc! {r#"
 9443                let a = {
 9444                    key: "value",ˇ
 9445                };
 9446            "#},
 9447            cx,
 9448        );
 9449    });
 9450    editor.update_in(cx, |editor, window, cx| {
 9451        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9452    });
 9453    editor.update(cx, |editor, cx| {
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                let a = «ˇ{
 9458                    key: "value",
 9459                }»;
 9460            "#},
 9461            cx,
 9462        );
 9463    });
 9464
 9465    // Test case 4: Cursor after ';'
 9466    editor.update_in(cx, |editor, window, cx| {
 9467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9468            s.select_display_ranges([
 9469                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9470            ]);
 9471        });
 9472    });
 9473    editor.update(cx, |editor, cx| {
 9474        assert_text_with_selections(
 9475            editor,
 9476            indoc! {r#"
 9477                let a = {
 9478                    key: "value",
 9479                };ˇ
 9480            "#},
 9481            cx,
 9482        );
 9483    });
 9484    editor.update_in(cx, |editor, window, cx| {
 9485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9486    });
 9487    editor.update(cx, |editor, cx| {
 9488        assert_text_with_selections(
 9489            editor,
 9490            indoc! {r#"
 9491                «ˇlet a = {
 9492                    key: "value",
 9493                };
 9494                »"#},
 9495            cx,
 9496        );
 9497    });
 9498}
 9499
 9500#[gpui::test]
 9501async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9502    init_test(cx, |_| {});
 9503
 9504    let language = Arc::new(Language::new(
 9505        LanguageConfig::default(),
 9506        Some(tree_sitter_rust::LANGUAGE.into()),
 9507    ));
 9508
 9509    let text = r#"
 9510        use mod1::mod2::{mod3, mod4};
 9511
 9512        fn fn_1(param1: bool, param2: &str) {
 9513            let var1 = "hello world";
 9514        }
 9515    "#
 9516    .unindent();
 9517
 9518    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9519    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9520    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9521
 9522    editor
 9523        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9524        .await;
 9525
 9526    // Test 1: Cursor on a letter of a string word
 9527    editor.update_in(cx, |editor, window, cx| {
 9528        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9529            s.select_display_ranges([
 9530                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9531            ]);
 9532        });
 9533    });
 9534    editor.update_in(cx, |editor, window, cx| {
 9535        assert_text_with_selections(
 9536            editor,
 9537            indoc! {r#"
 9538                use mod1::mod2::{mod3, mod4};
 9539
 9540                fn fn_1(param1: bool, param2: &str) {
 9541                    let var1 = "hˇello world";
 9542                }
 9543            "#},
 9544            cx,
 9545        );
 9546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9547        assert_text_with_selections(
 9548            editor,
 9549            indoc! {r#"
 9550                use mod1::mod2::{mod3, mod4};
 9551
 9552                fn fn_1(param1: bool, param2: &str) {
 9553                    let var1 = "«ˇhello» world";
 9554                }
 9555            "#},
 9556            cx,
 9557        );
 9558    });
 9559
 9560    // Test 2: Partial selection within a word
 9561    editor.update_in(cx, |editor, window, cx| {
 9562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9563            s.select_display_ranges([
 9564                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9565            ]);
 9566        });
 9567    });
 9568    editor.update_in(cx, |editor, window, cx| {
 9569        assert_text_with_selections(
 9570            editor,
 9571            indoc! {r#"
 9572                use mod1::mod2::{mod3, mod4};
 9573
 9574                fn fn_1(param1: bool, param2: &str) {
 9575                    let var1 = "h«elˇ»lo world";
 9576                }
 9577            "#},
 9578            cx,
 9579        );
 9580        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9581        assert_text_with_selections(
 9582            editor,
 9583            indoc! {r#"
 9584                use mod1::mod2::{mod3, mod4};
 9585
 9586                fn fn_1(param1: bool, param2: &str) {
 9587                    let var1 = "«ˇhello» world";
 9588                }
 9589            "#},
 9590            cx,
 9591        );
 9592    });
 9593
 9594    // Test 3: Complete word already selected
 9595    editor.update_in(cx, |editor, window, cx| {
 9596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9597            s.select_display_ranges([
 9598                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9599            ]);
 9600        });
 9601    });
 9602    editor.update_in(cx, |editor, window, cx| {
 9603        assert_text_with_selections(
 9604            editor,
 9605            indoc! {r#"
 9606                use mod1::mod2::{mod3, mod4};
 9607
 9608                fn fn_1(param1: bool, param2: &str) {
 9609                    let var1 = "«helloˇ» world";
 9610                }
 9611            "#},
 9612            cx,
 9613        );
 9614        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9615        assert_text_with_selections(
 9616            editor,
 9617            indoc! {r#"
 9618                use mod1::mod2::{mod3, mod4};
 9619
 9620                fn fn_1(param1: bool, param2: &str) {
 9621                    let var1 = "«hello worldˇ»";
 9622                }
 9623            "#},
 9624            cx,
 9625        );
 9626    });
 9627
 9628    // Test 4: Selection spanning across words
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9631            s.select_display_ranges([
 9632                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9633            ]);
 9634        });
 9635    });
 9636    editor.update_in(cx, |editor, window, cx| {
 9637        assert_text_with_selections(
 9638            editor,
 9639            indoc! {r#"
 9640                use mod1::mod2::{mod3, mod4};
 9641
 9642                fn fn_1(param1: bool, param2: &str) {
 9643                    let var1 = "hel«lo woˇ»rld";
 9644                }
 9645            "#},
 9646            cx,
 9647        );
 9648        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9649        assert_text_with_selections(
 9650            editor,
 9651            indoc! {r#"
 9652                use mod1::mod2::{mod3, mod4};
 9653
 9654                fn fn_1(param1: bool, param2: &str) {
 9655                    let var1 = "«ˇhello world»";
 9656                }
 9657            "#},
 9658            cx,
 9659        );
 9660    });
 9661
 9662    // Test 5: Expansion beyond string
 9663    editor.update_in(cx, |editor, window, cx| {
 9664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9665        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9666        assert_text_with_selections(
 9667            editor,
 9668            indoc! {r#"
 9669                use mod1::mod2::{mod3, mod4};
 9670
 9671                fn fn_1(param1: bool, param2: &str) {
 9672                    «ˇlet var1 = "hello world";»
 9673                }
 9674            "#},
 9675            cx,
 9676        );
 9677    });
 9678}
 9679
 9680#[gpui::test]
 9681async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9682    init_test(cx, |_| {});
 9683
 9684    let mut cx = EditorTestContext::new(cx).await;
 9685
 9686    let language = Arc::new(Language::new(
 9687        LanguageConfig::default(),
 9688        Some(tree_sitter_rust::LANGUAGE.into()),
 9689    ));
 9690
 9691    cx.update_buffer(|buffer, cx| {
 9692        buffer.set_language(Some(language), cx);
 9693    });
 9694
 9695    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9696    cx.update_editor(|editor, window, cx| {
 9697        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9698    });
 9699
 9700    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9701
 9702    cx.set_state(indoc! { r#"fn a() {
 9703          // what
 9704          // a
 9705          // ˇlong
 9706          // method
 9707          // I
 9708          // sure
 9709          // hope
 9710          // it
 9711          // works
 9712    }"# });
 9713
 9714    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9715    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9716    cx.update(|_, cx| {
 9717        multi_buffer.update(cx, |multi_buffer, cx| {
 9718            multi_buffer.set_excerpts_for_path(
 9719                PathKey::for_buffer(&buffer, cx),
 9720                buffer,
 9721                [Point::new(1, 0)..Point::new(1, 0)],
 9722                3,
 9723                cx,
 9724            );
 9725        });
 9726    });
 9727
 9728    let editor2 = cx.new_window_entity(|window, cx| {
 9729        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9730    });
 9731
 9732    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9733    cx.update_editor(|editor, window, cx| {
 9734        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9735            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9736        })
 9737    });
 9738
 9739    cx.assert_editor_state(indoc! { "
 9740        fn a() {
 9741              // what
 9742              // a
 9743        ˇ      // long
 9744              // method"});
 9745
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9748    });
 9749
 9750    // Although we could potentially make the action work when the syntax node
 9751    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9752    // did. Maybe we could also expand the excerpt to contain the range?
 9753    cx.assert_editor_state(indoc! { "
 9754        fn a() {
 9755              // what
 9756              // a
 9757        ˇ      // long
 9758              // method"});
 9759}
 9760
 9761#[gpui::test]
 9762async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9763    init_test(cx, |_| {});
 9764
 9765    let base_text = r#"
 9766        impl A {
 9767            // this is an uncommitted comment
 9768
 9769            fn b() {
 9770                c();
 9771            }
 9772
 9773            // this is another uncommitted comment
 9774
 9775            fn d() {
 9776                // e
 9777                // f
 9778            }
 9779        }
 9780
 9781        fn g() {
 9782            // h
 9783        }
 9784    "#
 9785    .unindent();
 9786
 9787    let text = r#"
 9788        ˇimpl A {
 9789
 9790            fn b() {
 9791                c();
 9792            }
 9793
 9794            fn d() {
 9795                // e
 9796                // f
 9797            }
 9798        }
 9799
 9800        fn g() {
 9801            // h
 9802        }
 9803    "#
 9804    .unindent();
 9805
 9806    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9807    cx.set_state(&text);
 9808    cx.set_head_text(&base_text);
 9809    cx.update_editor(|editor, window, cx| {
 9810        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9811    });
 9812
 9813    cx.assert_state_with_diff(
 9814        "
 9815        ˇimpl A {
 9816      -     // this is an uncommitted comment
 9817
 9818            fn b() {
 9819                c();
 9820            }
 9821
 9822      -     // this is another uncommitted comment
 9823      -
 9824            fn d() {
 9825                // e
 9826                // f
 9827            }
 9828        }
 9829
 9830        fn g() {
 9831            // h
 9832        }
 9833    "
 9834        .unindent(),
 9835    );
 9836
 9837    let expected_display_text = "
 9838        impl A {
 9839            // this is an uncommitted comment
 9840
 9841            fn b() {
 9842 9843            }
 9844
 9845            // this is another uncommitted comment
 9846
 9847            fn d() {
 9848 9849            }
 9850        }
 9851
 9852        fn g() {
 9853 9854        }
 9855        "
 9856    .unindent();
 9857
 9858    cx.update_editor(|editor, window, cx| {
 9859        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9860        assert_eq!(editor.display_text(cx), expected_display_text);
 9861    });
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_autoindent(cx: &mut TestAppContext) {
 9866    init_test(cx, |_| {});
 9867
 9868    let language = Arc::new(
 9869        Language::new(
 9870            LanguageConfig {
 9871                brackets: BracketPairConfig {
 9872                    pairs: vec![
 9873                        BracketPair {
 9874                            start: "{".to_string(),
 9875                            end: "}".to_string(),
 9876                            close: false,
 9877                            surround: false,
 9878                            newline: true,
 9879                        },
 9880                        BracketPair {
 9881                            start: "(".to_string(),
 9882                            end: ")".to_string(),
 9883                            close: false,
 9884                            surround: false,
 9885                            newline: true,
 9886                        },
 9887                    ],
 9888                    ..Default::default()
 9889                },
 9890                ..Default::default()
 9891            },
 9892            Some(tree_sitter_rust::LANGUAGE.into()),
 9893        )
 9894        .with_indents_query(
 9895            r#"
 9896                (_ "(" ")" @end) @indent
 9897                (_ "{" "}" @end) @indent
 9898            "#,
 9899        )
 9900        .unwrap(),
 9901    );
 9902
 9903    let text = "fn a() {}";
 9904
 9905    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9906    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9907    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9908    editor
 9909        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9910        .await;
 9911
 9912    editor.update_in(cx, |editor, window, cx| {
 9913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9914            s.select_ranges([
 9915                MultiBufferOffset(5)..MultiBufferOffset(5),
 9916                MultiBufferOffset(8)..MultiBufferOffset(8),
 9917                MultiBufferOffset(9)..MultiBufferOffset(9),
 9918            ])
 9919        });
 9920        editor.newline(&Newline, window, cx);
 9921        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9922        assert_eq!(
 9923            editor.selections.ranges(&editor.display_snapshot(cx)),
 9924            &[
 9925                Point::new(1, 4)..Point::new(1, 4),
 9926                Point::new(3, 4)..Point::new(3, 4),
 9927                Point::new(5, 0)..Point::new(5, 0)
 9928            ]
 9929        );
 9930    });
 9931}
 9932
 9933#[gpui::test]
 9934async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9935    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9936
 9937    let language = Arc::new(
 9938        Language::new(
 9939            LanguageConfig {
 9940                brackets: BracketPairConfig {
 9941                    pairs: vec![
 9942                        BracketPair {
 9943                            start: "{".to_string(),
 9944                            end: "}".to_string(),
 9945                            close: false,
 9946                            surround: false,
 9947                            newline: true,
 9948                        },
 9949                        BracketPair {
 9950                            start: "(".to_string(),
 9951                            end: ")".to_string(),
 9952                            close: false,
 9953                            surround: false,
 9954                            newline: true,
 9955                        },
 9956                    ],
 9957                    ..Default::default()
 9958                },
 9959                ..Default::default()
 9960            },
 9961            Some(tree_sitter_rust::LANGUAGE.into()),
 9962        )
 9963        .with_indents_query(
 9964            r#"
 9965                (_ "(" ")" @end) @indent
 9966                (_ "{" "}" @end) @indent
 9967            "#,
 9968        )
 9969        .unwrap(),
 9970    );
 9971
 9972    let text = "fn a() {}";
 9973
 9974    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9975    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9976    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9977    editor
 9978        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9979        .await;
 9980
 9981    editor.update_in(cx, |editor, window, cx| {
 9982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9983            s.select_ranges([
 9984                MultiBufferOffset(5)..MultiBufferOffset(5),
 9985                MultiBufferOffset(8)..MultiBufferOffset(8),
 9986                MultiBufferOffset(9)..MultiBufferOffset(9),
 9987            ])
 9988        });
 9989        editor.newline(&Newline, window, cx);
 9990        assert_eq!(
 9991            editor.text(cx),
 9992            indoc!(
 9993                "
 9994                fn a(
 9995
 9996                ) {
 9997
 9998                }
 9999                "
10000            )
10001        );
10002        assert_eq!(
10003            editor.selections.ranges(&editor.display_snapshot(cx)),
10004            &[
10005                Point::new(1, 0)..Point::new(1, 0),
10006                Point::new(3, 0)..Point::new(3, 0),
10007                Point::new(5, 0)..Point::new(5, 0)
10008            ]
10009        );
10010    });
10011}
10012
10013#[gpui::test]
10014async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10015    init_test(cx, |settings| {
10016        settings.defaults.auto_indent = Some(true);
10017        settings.languages.0.insert(
10018            "python".into(),
10019            LanguageSettingsContent {
10020                auto_indent: Some(false),
10021                ..Default::default()
10022            },
10023        );
10024    });
10025
10026    let mut cx = EditorTestContext::new(cx).await;
10027
10028    let injected_language = Arc::new(
10029        Language::new(
10030            LanguageConfig {
10031                brackets: BracketPairConfig {
10032                    pairs: vec![
10033                        BracketPair {
10034                            start: "{".to_string(),
10035                            end: "}".to_string(),
10036                            close: false,
10037                            surround: false,
10038                            newline: true,
10039                        },
10040                        BracketPair {
10041                            start: "(".to_string(),
10042                            end: ")".to_string(),
10043                            close: true,
10044                            surround: false,
10045                            newline: true,
10046                        },
10047                    ],
10048                    ..Default::default()
10049                },
10050                name: "python".into(),
10051                ..Default::default()
10052            },
10053            Some(tree_sitter_python::LANGUAGE.into()),
10054        )
10055        .with_indents_query(
10056            r#"
10057                (_ "(" ")" @end) @indent
10058                (_ "{" "}" @end) @indent
10059            "#,
10060        )
10061        .unwrap(),
10062    );
10063
10064    let language = Arc::new(
10065        Language::new(
10066            LanguageConfig {
10067                brackets: BracketPairConfig {
10068                    pairs: vec![
10069                        BracketPair {
10070                            start: "{".to_string(),
10071                            end: "}".to_string(),
10072                            close: false,
10073                            surround: false,
10074                            newline: true,
10075                        },
10076                        BracketPair {
10077                            start: "(".to_string(),
10078                            end: ")".to_string(),
10079                            close: true,
10080                            surround: false,
10081                            newline: true,
10082                        },
10083                    ],
10084                    ..Default::default()
10085                },
10086                name: LanguageName::new_static("rust"),
10087                ..Default::default()
10088            },
10089            Some(tree_sitter_rust::LANGUAGE.into()),
10090        )
10091        .with_indents_query(
10092            r#"
10093                (_ "(" ")" @end) @indent
10094                (_ "{" "}" @end) @indent
10095            "#,
10096        )
10097        .unwrap()
10098        .with_injection_query(
10099            r#"
10100            (macro_invocation
10101                macro: (identifier) @_macro_name
10102                (token_tree) @injection.content
10103                (#set! injection.language "python"))
10104           "#,
10105        )
10106        .unwrap(),
10107    );
10108
10109    cx.language_registry().add(injected_language);
10110    cx.language_registry().add(language.clone());
10111
10112    cx.update_buffer(|buffer, cx| {
10113        buffer.set_language(Some(language), cx);
10114    });
10115
10116    cx.set_state(r#"struct A {ˇ}"#);
10117
10118    cx.update_editor(|editor, window, cx| {
10119        editor.newline(&Default::default(), window, cx);
10120    });
10121
10122    cx.assert_editor_state(indoc!(
10123        "struct A {
10124            ˇ
10125        }"
10126    ));
10127
10128    cx.set_state(r#"select_biased!(ˇ)"#);
10129
10130    cx.update_editor(|editor, window, cx| {
10131        editor.newline(&Default::default(), window, cx);
10132        editor.handle_input("def ", window, cx);
10133        editor.handle_input("(", window, cx);
10134        editor.newline(&Default::default(), window, cx);
10135        editor.handle_input("a", window, cx);
10136    });
10137
10138    cx.assert_editor_state(indoc!(
10139        "select_biased!(
10140        def (
1014110142        )
10143        )"
10144    ));
10145}
10146
10147#[gpui::test]
10148async fn test_autoindent_selections(cx: &mut TestAppContext) {
10149    init_test(cx, |_| {});
10150
10151    {
10152        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10153        cx.set_state(indoc! {"
10154            impl A {
10155
10156                fn b() {}
10157
10158            «fn c() {
10159
10160            }ˇ»
10161            }
10162        "});
10163
10164        cx.update_editor(|editor, window, cx| {
10165            editor.autoindent(&Default::default(), window, cx);
10166        });
10167
10168        cx.assert_editor_state(indoc! {"
10169            impl A {
10170
10171                fn b() {}
10172
10173                «fn c() {
10174
10175                }ˇ»
10176            }
10177        "});
10178    }
10179
10180    {
10181        let mut cx = EditorTestContext::new_multibuffer(
10182            cx,
10183            [indoc! { "
10184                impl A {
10185                «
10186                // a
10187                fn b(){}
10188                »
10189                «
10190                    }
10191                    fn c(){}
10192                »
10193            "}],
10194        );
10195
10196        let buffer = cx.update_editor(|editor, _, cx| {
10197            let buffer = editor.buffer().update(cx, |buffer, _| {
10198                buffer.all_buffers().iter().next().unwrap().clone()
10199            });
10200            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10201            buffer
10202        });
10203
10204        cx.run_until_parked();
10205        cx.update_editor(|editor, window, cx| {
10206            editor.select_all(&Default::default(), window, cx);
10207            editor.autoindent(&Default::default(), window, cx)
10208        });
10209        cx.run_until_parked();
10210
10211        cx.update(|_, cx| {
10212            assert_eq!(
10213                buffer.read(cx).text(),
10214                indoc! { "
10215                    impl A {
10216
10217                        // a
10218                        fn b(){}
10219
10220
10221                    }
10222                    fn c(){}
10223
10224                " }
10225            )
10226        });
10227    }
10228}
10229
10230#[gpui::test]
10231async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10232    init_test(cx, |_| {});
10233
10234    let mut cx = EditorTestContext::new(cx).await;
10235
10236    let language = Arc::new(Language::new(
10237        LanguageConfig {
10238            brackets: BracketPairConfig {
10239                pairs: vec![
10240                    BracketPair {
10241                        start: "{".to_string(),
10242                        end: "}".to_string(),
10243                        close: true,
10244                        surround: true,
10245                        newline: true,
10246                    },
10247                    BracketPair {
10248                        start: "(".to_string(),
10249                        end: ")".to_string(),
10250                        close: true,
10251                        surround: true,
10252                        newline: true,
10253                    },
10254                    BracketPair {
10255                        start: "/*".to_string(),
10256                        end: " */".to_string(),
10257                        close: true,
10258                        surround: true,
10259                        newline: true,
10260                    },
10261                    BracketPair {
10262                        start: "[".to_string(),
10263                        end: "]".to_string(),
10264                        close: false,
10265                        surround: false,
10266                        newline: true,
10267                    },
10268                    BracketPair {
10269                        start: "\"".to_string(),
10270                        end: "\"".to_string(),
10271                        close: true,
10272                        surround: true,
10273                        newline: false,
10274                    },
10275                    BracketPair {
10276                        start: "<".to_string(),
10277                        end: ">".to_string(),
10278                        close: false,
10279                        surround: true,
10280                        newline: true,
10281                    },
10282                ],
10283                ..Default::default()
10284            },
10285            autoclose_before: "})]".to_string(),
10286            ..Default::default()
10287        },
10288        Some(tree_sitter_rust::LANGUAGE.into()),
10289    ));
10290
10291    cx.language_registry().add(language.clone());
10292    cx.update_buffer(|buffer, cx| {
10293        buffer.set_language(Some(language), cx);
10294    });
10295
10296    cx.set_state(
10297        &r#"
10298            🏀ˇ
10299            εˇ
10300            ❤️ˇ
10301        "#
10302        .unindent(),
10303    );
10304
10305    // autoclose multiple nested brackets at multiple cursors
10306    cx.update_editor(|editor, window, cx| {
10307        editor.handle_input("{", window, cx);
10308        editor.handle_input("{", window, cx);
10309        editor.handle_input("{", window, cx);
10310    });
10311    cx.assert_editor_state(
10312        &"
10313            🏀{{{ˇ}}}
10314            ε{{{ˇ}}}
10315            ❤️{{{ˇ}}}
10316        "
10317        .unindent(),
10318    );
10319
10320    // insert a different closing bracket
10321    cx.update_editor(|editor, window, cx| {
10322        editor.handle_input(")", window, cx);
10323    });
10324    cx.assert_editor_state(
10325        &"
10326            🏀{{{)ˇ}}}
10327            ε{{{)ˇ}}}
10328            ❤️{{{)ˇ}}}
10329        "
10330        .unindent(),
10331    );
10332
10333    // skip over the auto-closed brackets when typing a closing bracket
10334    cx.update_editor(|editor, window, cx| {
10335        editor.move_right(&MoveRight, window, cx);
10336        editor.handle_input("}", window, cx);
10337        editor.handle_input("}", window, cx);
10338        editor.handle_input("}", window, cx);
10339    });
10340    cx.assert_editor_state(
10341        &"
10342            🏀{{{)}}}}ˇ
10343            ε{{{)}}}}ˇ
10344            ❤️{{{)}}}}ˇ
10345        "
10346        .unindent(),
10347    );
10348
10349    // autoclose multi-character pairs
10350    cx.set_state(
10351        &"
10352            ˇ
10353            ˇ
10354        "
10355        .unindent(),
10356    );
10357    cx.update_editor(|editor, window, cx| {
10358        editor.handle_input("/", window, cx);
10359        editor.handle_input("*", window, cx);
10360    });
10361    cx.assert_editor_state(
10362        &"
10363            /*ˇ */
10364            /*ˇ */
10365        "
10366        .unindent(),
10367    );
10368
10369    // one cursor autocloses a multi-character pair, one cursor
10370    // does not autoclose.
10371    cx.set_state(
10372        &"
1037310374            ˇ
10375        "
10376        .unindent(),
10377    );
10378    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10379    cx.assert_editor_state(
10380        &"
10381            /*ˇ */
1038210383        "
10384        .unindent(),
10385    );
10386
10387    // Don't autoclose if the next character isn't whitespace and isn't
10388    // listed in the language's "autoclose_before" section.
10389    cx.set_state("ˇa b");
10390    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10391    cx.assert_editor_state("{ˇa b");
10392
10393    // Don't autoclose if `close` is false for the bracket pair
10394    cx.set_state("ˇ");
10395    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10396    cx.assert_editor_state("");
10397
10398    // Surround with brackets if text is selected
10399    cx.set_state("«aˇ» b");
10400    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10401    cx.assert_editor_state("{«aˇ»} b");
10402
10403    // Autoclose when not immediately after a word character
10404    cx.set_state("a ˇ");
10405    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10406    cx.assert_editor_state("a \"ˇ\"");
10407
10408    // Autoclose pair where the start and end characters are the same
10409    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10410    cx.assert_editor_state("a \"\"ˇ");
10411
10412    // Don't autoclose when immediately after a word character
10413    cx.set_state("");
10414    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10415    cx.assert_editor_state("a\"ˇ");
10416
10417    // Do autoclose when after a non-word character
10418    cx.set_state("");
10419    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10420    cx.assert_editor_state("{\"ˇ\"");
10421
10422    // Non identical pairs autoclose regardless of preceding character
10423    cx.set_state("");
10424    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10425    cx.assert_editor_state("a{ˇ}");
10426
10427    // Don't autoclose pair if autoclose is disabled
10428    cx.set_state("ˇ");
10429    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10430    cx.assert_editor_state("");
10431
10432    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10433    cx.set_state("«aˇ» b");
10434    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10435    cx.assert_editor_state("<«aˇ»> b");
10436}
10437
10438#[gpui::test]
10439async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10440    init_test(cx, |settings| {
10441        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10442    });
10443
10444    let mut cx = EditorTestContext::new(cx).await;
10445
10446    let language = Arc::new(Language::new(
10447        LanguageConfig {
10448            brackets: BracketPairConfig {
10449                pairs: vec![
10450                    BracketPair {
10451                        start: "{".to_string(),
10452                        end: "}".to_string(),
10453                        close: true,
10454                        surround: true,
10455                        newline: true,
10456                    },
10457                    BracketPair {
10458                        start: "(".to_string(),
10459                        end: ")".to_string(),
10460                        close: true,
10461                        surround: true,
10462                        newline: true,
10463                    },
10464                    BracketPair {
10465                        start: "[".to_string(),
10466                        end: "]".to_string(),
10467                        close: false,
10468                        surround: false,
10469                        newline: true,
10470                    },
10471                ],
10472                ..Default::default()
10473            },
10474            autoclose_before: "})]".to_string(),
10475            ..Default::default()
10476        },
10477        Some(tree_sitter_rust::LANGUAGE.into()),
10478    ));
10479
10480    cx.language_registry().add(language.clone());
10481    cx.update_buffer(|buffer, cx| {
10482        buffer.set_language(Some(language), cx);
10483    });
10484
10485    cx.set_state(
10486        &"
10487            ˇ
10488            ˇ
10489            ˇ
10490        "
10491        .unindent(),
10492    );
10493
10494    // ensure only matching closing brackets are skipped over
10495    cx.update_editor(|editor, window, cx| {
10496        editor.handle_input("}", window, cx);
10497        editor.move_left(&MoveLeft, window, cx);
10498        editor.handle_input(")", window, cx);
10499        editor.move_left(&MoveLeft, window, cx);
10500    });
10501    cx.assert_editor_state(
10502        &"
10503            ˇ)}
10504            ˇ)}
10505            ˇ)}
10506        "
10507        .unindent(),
10508    );
10509
10510    // skip-over closing brackets at multiple cursors
10511    cx.update_editor(|editor, window, cx| {
10512        editor.handle_input(")", window, cx);
10513        editor.handle_input("}", window, cx);
10514    });
10515    cx.assert_editor_state(
10516        &"
10517            )}ˇ
10518            )}ˇ
10519            )}ˇ
10520        "
10521        .unindent(),
10522    );
10523
10524    // ignore non-close brackets
10525    cx.update_editor(|editor, window, cx| {
10526        editor.handle_input("]", window, cx);
10527        editor.move_left(&MoveLeft, window, cx);
10528        editor.handle_input("]", window, cx);
10529    });
10530    cx.assert_editor_state(
10531        &"
10532            )}]ˇ]
10533            )}]ˇ]
10534            )}]ˇ]
10535        "
10536        .unindent(),
10537    );
10538}
10539
10540#[gpui::test]
10541async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10542    init_test(cx, |_| {});
10543
10544    let mut cx = EditorTestContext::new(cx).await;
10545
10546    let html_language = Arc::new(
10547        Language::new(
10548            LanguageConfig {
10549                name: "HTML".into(),
10550                brackets: BracketPairConfig {
10551                    pairs: vec![
10552                        BracketPair {
10553                            start: "<".into(),
10554                            end: ">".into(),
10555                            close: true,
10556                            ..Default::default()
10557                        },
10558                        BracketPair {
10559                            start: "{".into(),
10560                            end: "}".into(),
10561                            close: true,
10562                            ..Default::default()
10563                        },
10564                        BracketPair {
10565                            start: "(".into(),
10566                            end: ")".into(),
10567                            close: true,
10568                            ..Default::default()
10569                        },
10570                    ],
10571                    ..Default::default()
10572                },
10573                autoclose_before: "})]>".into(),
10574                ..Default::default()
10575            },
10576            Some(tree_sitter_html::LANGUAGE.into()),
10577        )
10578        .with_injection_query(
10579            r#"
10580            (script_element
10581                (raw_text) @injection.content
10582                (#set! injection.language "javascript"))
10583            "#,
10584        )
10585        .unwrap(),
10586    );
10587
10588    let javascript_language = Arc::new(Language::new(
10589        LanguageConfig {
10590            name: "JavaScript".into(),
10591            brackets: BracketPairConfig {
10592                pairs: vec![
10593                    BracketPair {
10594                        start: "/*".into(),
10595                        end: " */".into(),
10596                        close: true,
10597                        ..Default::default()
10598                    },
10599                    BracketPair {
10600                        start: "{".into(),
10601                        end: "}".into(),
10602                        close: true,
10603                        ..Default::default()
10604                    },
10605                    BracketPair {
10606                        start: "(".into(),
10607                        end: ")".into(),
10608                        close: true,
10609                        ..Default::default()
10610                    },
10611                ],
10612                ..Default::default()
10613            },
10614            autoclose_before: "})]>".into(),
10615            ..Default::default()
10616        },
10617        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10618    ));
10619
10620    cx.language_registry().add(html_language.clone());
10621    cx.language_registry().add(javascript_language);
10622    cx.executor().run_until_parked();
10623
10624    cx.update_buffer(|buffer, cx| {
10625        buffer.set_language(Some(html_language), cx);
10626    });
10627
10628    cx.set_state(
10629        &r#"
10630            <body>ˇ
10631                <script>
10632                    var x = 1;ˇ
10633                </script>
10634            </body>ˇ
10635        "#
10636        .unindent(),
10637    );
10638
10639    // Precondition: different languages are active at different locations.
10640    cx.update_editor(|editor, window, cx| {
10641        let snapshot = editor.snapshot(window, cx);
10642        let cursors = editor
10643            .selections
10644            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10645        let languages = cursors
10646            .iter()
10647            .map(|c| snapshot.language_at(c.start).unwrap().name())
10648            .collect::<Vec<_>>();
10649        assert_eq!(
10650            languages,
10651            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10652        );
10653    });
10654
10655    // Angle brackets autoclose in HTML, but not JavaScript.
10656    cx.update_editor(|editor, window, cx| {
10657        editor.handle_input("<", window, cx);
10658        editor.handle_input("a", window, cx);
10659    });
10660    cx.assert_editor_state(
10661        &r#"
10662            <body><aˇ>
10663                <script>
10664                    var x = 1;<aˇ
10665                </script>
10666            </body><aˇ>
10667        "#
10668        .unindent(),
10669    );
10670
10671    // Curly braces and parens autoclose in both HTML and JavaScript.
10672    cx.update_editor(|editor, window, cx| {
10673        editor.handle_input(" b=", window, cx);
10674        editor.handle_input("{", window, cx);
10675        editor.handle_input("c", window, cx);
10676        editor.handle_input("(", window, cx);
10677    });
10678    cx.assert_editor_state(
10679        &r#"
10680            <body><a b={c(ˇ)}>
10681                <script>
10682                    var x = 1;<a b={c(ˇ)}
10683                </script>
10684            </body><a b={c(ˇ)}>
10685        "#
10686        .unindent(),
10687    );
10688
10689    // Brackets that were already autoclosed are skipped.
10690    cx.update_editor(|editor, window, cx| {
10691        editor.handle_input(")", window, cx);
10692        editor.handle_input("d", window, cx);
10693        editor.handle_input("}", window, cx);
10694    });
10695    cx.assert_editor_state(
10696        &r#"
10697            <body><a b={c()d}ˇ>
10698                <script>
10699                    var x = 1;<a b={c()d}ˇ
10700                </script>
10701            </body><a b={c()d}ˇ>
10702        "#
10703        .unindent(),
10704    );
10705    cx.update_editor(|editor, window, cx| {
10706        editor.handle_input(">", window, cx);
10707    });
10708    cx.assert_editor_state(
10709        &r#"
10710            <body><a b={c()d}>ˇ
10711                <script>
10712                    var x = 1;<a b={c()d}>ˇ
10713                </script>
10714            </body><a b={c()d}>ˇ
10715        "#
10716        .unindent(),
10717    );
10718
10719    // Reset
10720    cx.set_state(
10721        &r#"
10722            <body>ˇ
10723                <script>
10724                    var x = 1;ˇ
10725                </script>
10726            </body>ˇ
10727        "#
10728        .unindent(),
10729    );
10730
10731    cx.update_editor(|editor, window, cx| {
10732        editor.handle_input("<", window, cx);
10733    });
10734    cx.assert_editor_state(
10735        &r#"
10736            <body><ˇ>
10737                <script>
10738                    var x = 1;<ˇ
10739                </script>
10740            </body><ˇ>
10741        "#
10742        .unindent(),
10743    );
10744
10745    // When backspacing, the closing angle brackets are removed.
10746    cx.update_editor(|editor, window, cx| {
10747        editor.backspace(&Backspace, window, cx);
10748    });
10749    cx.assert_editor_state(
10750        &r#"
10751            <body>ˇ
10752                <script>
10753                    var x = 1;ˇ
10754                </script>
10755            </body>ˇ
10756        "#
10757        .unindent(),
10758    );
10759
10760    // Block comments autoclose in JavaScript, but not HTML.
10761    cx.update_editor(|editor, window, cx| {
10762        editor.handle_input("/", window, cx);
10763        editor.handle_input("*", window, cx);
10764    });
10765    cx.assert_editor_state(
10766        &r#"
10767            <body>/*ˇ
10768                <script>
10769                    var x = 1;/*ˇ */
10770                </script>
10771            </body>/*ˇ
10772        "#
10773        .unindent(),
10774    );
10775}
10776
10777#[gpui::test]
10778async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10779    init_test(cx, |_| {});
10780
10781    let mut cx = EditorTestContext::new(cx).await;
10782
10783    let rust_language = Arc::new(
10784        Language::new(
10785            LanguageConfig {
10786                name: "Rust".into(),
10787                brackets: serde_json::from_value(json!([
10788                    { "start": "{", "end": "}", "close": true, "newline": true },
10789                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10790                ]))
10791                .unwrap(),
10792                autoclose_before: "})]>".into(),
10793                ..Default::default()
10794            },
10795            Some(tree_sitter_rust::LANGUAGE.into()),
10796        )
10797        .with_override_query("(string_literal) @string")
10798        .unwrap(),
10799    );
10800
10801    cx.language_registry().add(rust_language.clone());
10802    cx.update_buffer(|buffer, cx| {
10803        buffer.set_language(Some(rust_language), cx);
10804    });
10805
10806    cx.set_state(
10807        &r#"
10808            let x = ˇ
10809        "#
10810        .unindent(),
10811    );
10812
10813    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10814    cx.update_editor(|editor, window, cx| {
10815        editor.handle_input("\"", window, cx);
10816    });
10817    cx.assert_editor_state(
10818        &r#"
10819            let x = "ˇ"
10820        "#
10821        .unindent(),
10822    );
10823
10824    // Inserting another quotation mark. The cursor moves across the existing
10825    // automatically-inserted quotation mark.
10826    cx.update_editor(|editor, window, cx| {
10827        editor.handle_input("\"", window, cx);
10828    });
10829    cx.assert_editor_state(
10830        &r#"
10831            let x = ""ˇ
10832        "#
10833        .unindent(),
10834    );
10835
10836    // Reset
10837    cx.set_state(
10838        &r#"
10839            let x = ˇ
10840        "#
10841        .unindent(),
10842    );
10843
10844    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10845    cx.update_editor(|editor, window, cx| {
10846        editor.handle_input("\"", window, cx);
10847        editor.handle_input(" ", window, cx);
10848        editor.move_left(&Default::default(), window, cx);
10849        editor.handle_input("\\", window, cx);
10850        editor.handle_input("\"", window, cx);
10851    });
10852    cx.assert_editor_state(
10853        &r#"
10854            let x = "\"ˇ "
10855        "#
10856        .unindent(),
10857    );
10858
10859    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10860    // mark. Nothing is inserted.
10861    cx.update_editor(|editor, window, cx| {
10862        editor.move_right(&Default::default(), window, cx);
10863        editor.handle_input("\"", window, cx);
10864    });
10865    cx.assert_editor_state(
10866        &r#"
10867            let x = "\" "ˇ
10868        "#
10869        .unindent(),
10870    );
10871}
10872
10873#[gpui::test]
10874async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
10875    init_test(cx, |_| {});
10876
10877    let mut cx = EditorTestContext::new(cx).await;
10878    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10879
10880    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10881
10882    // Double quote inside single-quoted string
10883    cx.set_state(indoc! {r#"
10884        def main():
10885            items = ['"', ˇ]
10886    "#});
10887    cx.update_editor(|editor, window, cx| {
10888        editor.handle_input("\"", window, cx);
10889    });
10890    cx.assert_editor_state(indoc! {r#"
10891        def main():
10892            items = ['"', "ˇ"]
10893    "#});
10894
10895    // Two double quotes inside single-quoted string
10896    cx.set_state(indoc! {r#"
10897        def main():
10898            items = ['""', ˇ]
10899    "#});
10900    cx.update_editor(|editor, window, cx| {
10901        editor.handle_input("\"", window, cx);
10902    });
10903    cx.assert_editor_state(indoc! {r#"
10904        def main():
10905            items = ['""', "ˇ"]
10906    "#});
10907
10908    // Single quote inside double-quoted string
10909    cx.set_state(indoc! {r#"
10910        def main():
10911            items = ["'", ˇ]
10912    "#});
10913    cx.update_editor(|editor, window, cx| {
10914        editor.handle_input("'", window, cx);
10915    });
10916    cx.assert_editor_state(indoc! {r#"
10917        def main():
10918            items = ["'", 'ˇ']
10919    "#});
10920
10921    // Two single quotes inside double-quoted string
10922    cx.set_state(indoc! {r#"
10923        def main():
10924            items = ["''", ˇ]
10925    "#});
10926    cx.update_editor(|editor, window, cx| {
10927        editor.handle_input("'", window, cx);
10928    });
10929    cx.assert_editor_state(indoc! {r#"
10930        def main():
10931            items = ["''", 'ˇ']
10932    "#});
10933
10934    // Mixed quotes on same line
10935    cx.set_state(indoc! {r#"
10936        def main():
10937            items = ['"""', "'''''", ˇ]
10938    "#});
10939    cx.update_editor(|editor, window, cx| {
10940        editor.handle_input("\"", window, cx);
10941    });
10942    cx.assert_editor_state(indoc! {r#"
10943        def main():
10944            items = ['"""', "'''''", "ˇ"]
10945    "#});
10946    cx.update_editor(|editor, window, cx| {
10947        editor.move_right(&MoveRight, window, cx);
10948    });
10949    cx.update_editor(|editor, window, cx| {
10950        editor.handle_input(", ", window, cx);
10951    });
10952    cx.update_editor(|editor, window, cx| {
10953        editor.handle_input("'", window, cx);
10954    });
10955    cx.assert_editor_state(indoc! {r#"
10956        def main():
10957            items = ['"""', "'''''", "", 'ˇ']
10958    "#});
10959}
10960
10961#[gpui::test]
10962async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
10963    init_test(cx, |_| {});
10964
10965    let mut cx = EditorTestContext::new(cx).await;
10966    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10967    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10968
10969    cx.set_state(indoc! {r#"
10970        def main():
10971            items = ["🎉", ˇ]
10972    "#});
10973    cx.update_editor(|editor, window, cx| {
10974        editor.handle_input("\"", window, cx);
10975    });
10976    cx.assert_editor_state(indoc! {r#"
10977        def main():
10978            items = ["🎉", "ˇ"]
10979    "#});
10980}
10981
10982#[gpui::test]
10983async fn test_surround_with_pair(cx: &mut TestAppContext) {
10984    init_test(cx, |_| {});
10985
10986    let language = Arc::new(Language::new(
10987        LanguageConfig {
10988            brackets: BracketPairConfig {
10989                pairs: vec![
10990                    BracketPair {
10991                        start: "{".to_string(),
10992                        end: "}".to_string(),
10993                        close: true,
10994                        surround: true,
10995                        newline: true,
10996                    },
10997                    BracketPair {
10998                        start: "/* ".to_string(),
10999                        end: "*/".to_string(),
11000                        close: true,
11001                        surround: true,
11002                        ..Default::default()
11003                    },
11004                ],
11005                ..Default::default()
11006            },
11007            ..Default::default()
11008        },
11009        Some(tree_sitter_rust::LANGUAGE.into()),
11010    ));
11011
11012    let text = r#"
11013        a
11014        b
11015        c
11016    "#
11017    .unindent();
11018
11019    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11020    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11021    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11022    editor
11023        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11024        .await;
11025
11026    editor.update_in(cx, |editor, window, cx| {
11027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11028            s.select_display_ranges([
11029                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11030                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11031                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11032            ])
11033        });
11034
11035        editor.handle_input("{", window, cx);
11036        editor.handle_input("{", window, cx);
11037        editor.handle_input("{", window, cx);
11038        assert_eq!(
11039            editor.text(cx),
11040            "
11041                {{{a}}}
11042                {{{b}}}
11043                {{{c}}}
11044            "
11045            .unindent()
11046        );
11047        assert_eq!(
11048            display_ranges(editor, cx),
11049            [
11050                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11051                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11052                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11053            ]
11054        );
11055
11056        editor.undo(&Undo, window, cx);
11057        editor.undo(&Undo, window, cx);
11058        editor.undo(&Undo, window, cx);
11059        assert_eq!(
11060            editor.text(cx),
11061            "
11062                a
11063                b
11064                c
11065            "
11066            .unindent()
11067        );
11068        assert_eq!(
11069            display_ranges(editor, cx),
11070            [
11071                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11072                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11073                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11074            ]
11075        );
11076
11077        // Ensure inserting the first character of a multi-byte bracket pair
11078        // doesn't surround the selections with the bracket.
11079        editor.handle_input("/", window, cx);
11080        assert_eq!(
11081            editor.text(cx),
11082            "
11083                /
11084                /
11085                /
11086            "
11087            .unindent()
11088        );
11089        assert_eq!(
11090            display_ranges(editor, cx),
11091            [
11092                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11093                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11094                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11095            ]
11096        );
11097
11098        editor.undo(&Undo, window, cx);
11099        assert_eq!(
11100            editor.text(cx),
11101            "
11102                a
11103                b
11104                c
11105            "
11106            .unindent()
11107        );
11108        assert_eq!(
11109            display_ranges(editor, cx),
11110            [
11111                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11112                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11113                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11114            ]
11115        );
11116
11117        // Ensure inserting the last character of a multi-byte bracket pair
11118        // doesn't surround the selections with the bracket.
11119        editor.handle_input("*", window, cx);
11120        assert_eq!(
11121            editor.text(cx),
11122            "
11123                *
11124                *
11125                *
11126            "
11127            .unindent()
11128        );
11129        assert_eq!(
11130            display_ranges(editor, cx),
11131            [
11132                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11133                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11134                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11135            ]
11136        );
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11142    init_test(cx, |_| {});
11143
11144    let language = Arc::new(Language::new(
11145        LanguageConfig {
11146            brackets: BracketPairConfig {
11147                pairs: vec![BracketPair {
11148                    start: "{".to_string(),
11149                    end: "}".to_string(),
11150                    close: true,
11151                    surround: true,
11152                    newline: true,
11153                }],
11154                ..Default::default()
11155            },
11156            autoclose_before: "}".to_string(),
11157            ..Default::default()
11158        },
11159        Some(tree_sitter_rust::LANGUAGE.into()),
11160    ));
11161
11162    let text = r#"
11163        a
11164        b
11165        c
11166    "#
11167    .unindent();
11168
11169    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11170    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11171    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11172    editor
11173        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11174        .await;
11175
11176    editor.update_in(cx, |editor, window, cx| {
11177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11178            s.select_ranges([
11179                Point::new(0, 1)..Point::new(0, 1),
11180                Point::new(1, 1)..Point::new(1, 1),
11181                Point::new(2, 1)..Point::new(2, 1),
11182            ])
11183        });
11184
11185        editor.handle_input("{", window, cx);
11186        editor.handle_input("{", window, cx);
11187        editor.handle_input("_", window, cx);
11188        assert_eq!(
11189            editor.text(cx),
11190            "
11191                a{{_}}
11192                b{{_}}
11193                c{{_}}
11194            "
11195            .unindent()
11196        );
11197        assert_eq!(
11198            editor
11199                .selections
11200                .ranges::<Point>(&editor.display_snapshot(cx)),
11201            [
11202                Point::new(0, 4)..Point::new(0, 4),
11203                Point::new(1, 4)..Point::new(1, 4),
11204                Point::new(2, 4)..Point::new(2, 4)
11205            ]
11206        );
11207
11208        editor.backspace(&Default::default(), window, cx);
11209        editor.backspace(&Default::default(), window, cx);
11210        assert_eq!(
11211            editor.text(cx),
11212            "
11213                a{}
11214                b{}
11215                c{}
11216            "
11217            .unindent()
11218        );
11219        assert_eq!(
11220            editor
11221                .selections
11222                .ranges::<Point>(&editor.display_snapshot(cx)),
11223            [
11224                Point::new(0, 2)..Point::new(0, 2),
11225                Point::new(1, 2)..Point::new(1, 2),
11226                Point::new(2, 2)..Point::new(2, 2)
11227            ]
11228        );
11229
11230        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11231        assert_eq!(
11232            editor.text(cx),
11233            "
11234                a
11235                b
11236                c
11237            "
11238            .unindent()
11239        );
11240        assert_eq!(
11241            editor
11242                .selections
11243                .ranges::<Point>(&editor.display_snapshot(cx)),
11244            [
11245                Point::new(0, 1)..Point::new(0, 1),
11246                Point::new(1, 1)..Point::new(1, 1),
11247                Point::new(2, 1)..Point::new(2, 1)
11248            ]
11249        );
11250    });
11251}
11252
11253#[gpui::test]
11254async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11255    init_test(cx, |settings| {
11256        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11257    });
11258
11259    let mut cx = EditorTestContext::new(cx).await;
11260
11261    let language = Arc::new(Language::new(
11262        LanguageConfig {
11263            brackets: BracketPairConfig {
11264                pairs: vec![
11265                    BracketPair {
11266                        start: "{".to_string(),
11267                        end: "}".to_string(),
11268                        close: true,
11269                        surround: true,
11270                        newline: true,
11271                    },
11272                    BracketPair {
11273                        start: "(".to_string(),
11274                        end: ")".to_string(),
11275                        close: true,
11276                        surround: true,
11277                        newline: true,
11278                    },
11279                    BracketPair {
11280                        start: "[".to_string(),
11281                        end: "]".to_string(),
11282                        close: false,
11283                        surround: true,
11284                        newline: true,
11285                    },
11286                ],
11287                ..Default::default()
11288            },
11289            autoclose_before: "})]".to_string(),
11290            ..Default::default()
11291        },
11292        Some(tree_sitter_rust::LANGUAGE.into()),
11293    ));
11294
11295    cx.language_registry().add(language.clone());
11296    cx.update_buffer(|buffer, cx| {
11297        buffer.set_language(Some(language), cx);
11298    });
11299
11300    cx.set_state(
11301        &"
11302            {(ˇ)}
11303            [[ˇ]]
11304            {(ˇ)}
11305        "
11306        .unindent(),
11307    );
11308
11309    cx.update_editor(|editor, window, cx| {
11310        editor.backspace(&Default::default(), window, cx);
11311        editor.backspace(&Default::default(), window, cx);
11312    });
11313
11314    cx.assert_editor_state(
11315        &"
11316            ˇ
11317            ˇ]]
11318            ˇ
11319        "
11320        .unindent(),
11321    );
11322
11323    cx.update_editor(|editor, window, cx| {
11324        editor.handle_input("{", window, cx);
11325        editor.handle_input("{", window, cx);
11326        editor.move_right(&MoveRight, window, cx);
11327        editor.move_right(&MoveRight, window, cx);
11328        editor.move_left(&MoveLeft, window, cx);
11329        editor.move_left(&MoveLeft, window, cx);
11330        editor.backspace(&Default::default(), window, cx);
11331    });
11332
11333    cx.assert_editor_state(
11334        &"
11335            {ˇ}
11336            {ˇ}]]
11337            {ˇ}
11338        "
11339        .unindent(),
11340    );
11341
11342    cx.update_editor(|editor, window, cx| {
11343        editor.backspace(&Default::default(), window, cx);
11344    });
11345
11346    cx.assert_editor_state(
11347        &"
11348            ˇ
11349            ˇ]]
11350            ˇ
11351        "
11352        .unindent(),
11353    );
11354}
11355
11356#[gpui::test]
11357async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11358    init_test(cx, |_| {});
11359
11360    let language = Arc::new(Language::new(
11361        LanguageConfig::default(),
11362        Some(tree_sitter_rust::LANGUAGE.into()),
11363    ));
11364
11365    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11366    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11367    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11368    editor
11369        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11370        .await;
11371
11372    editor.update_in(cx, |editor, window, cx| {
11373        editor.set_auto_replace_emoji_shortcode(true);
11374
11375        editor.handle_input("Hello ", window, cx);
11376        editor.handle_input(":wave", window, cx);
11377        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11378
11379        editor.handle_input(":", window, cx);
11380        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11381
11382        editor.handle_input(" :smile", window, cx);
11383        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11384
11385        editor.handle_input(":", window, cx);
11386        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11387
11388        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11389        editor.handle_input(":wave", window, cx);
11390        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11391
11392        editor.handle_input(":", window, cx);
11393        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11394
11395        editor.handle_input(":1", window, cx);
11396        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11397
11398        editor.handle_input(":", window, cx);
11399        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11400
11401        // Ensure shortcode does not get replaced when it is part of a word
11402        editor.handle_input(" Test:wave", window, cx);
11403        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11404
11405        editor.handle_input(":", window, cx);
11406        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11407
11408        editor.set_auto_replace_emoji_shortcode(false);
11409
11410        // Ensure shortcode does not get replaced when auto replace is off
11411        editor.handle_input(" :wave", window, cx);
11412        assert_eq!(
11413            editor.text(cx),
11414            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11415        );
11416
11417        editor.handle_input(":", window, cx);
11418        assert_eq!(
11419            editor.text(cx),
11420            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11421        );
11422    });
11423}
11424
11425#[gpui::test]
11426async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11427    init_test(cx, |_| {});
11428
11429    let (text, insertion_ranges) = marked_text_ranges(
11430        indoc! {"
11431            ˇ
11432        "},
11433        false,
11434    );
11435
11436    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11437    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11438
11439    _ = editor.update_in(cx, |editor, window, cx| {
11440        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11441
11442        editor
11443            .insert_snippet(
11444                &insertion_ranges
11445                    .iter()
11446                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11447                    .collect::<Vec<_>>(),
11448                snippet,
11449                window,
11450                cx,
11451            )
11452            .unwrap();
11453
11454        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11455            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11456            assert_eq!(editor.text(cx), expected_text);
11457            assert_eq!(
11458                editor.selections.ranges(&editor.display_snapshot(cx)),
11459                selection_ranges
11460                    .iter()
11461                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11462                    .collect::<Vec<_>>()
11463            );
11464        }
11465
11466        assert(
11467            editor,
11468            cx,
11469            indoc! {"
11470            type «» =•
11471            "},
11472        );
11473
11474        assert!(editor.context_menu_visible(), "There should be a matches");
11475    });
11476}
11477
11478#[gpui::test]
11479async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11480    init_test(cx, |_| {});
11481
11482    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11483        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11484        assert_eq!(editor.text(cx), expected_text);
11485        assert_eq!(
11486            editor.selections.ranges(&editor.display_snapshot(cx)),
11487            selection_ranges
11488                .iter()
11489                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11490                .collect::<Vec<_>>()
11491        );
11492    }
11493
11494    let (text, insertion_ranges) = marked_text_ranges(
11495        indoc! {"
11496            ˇ
11497        "},
11498        false,
11499    );
11500
11501    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11502    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11503
11504    _ = editor.update_in(cx, |editor, window, cx| {
11505        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11506
11507        editor
11508            .insert_snippet(
11509                &insertion_ranges
11510                    .iter()
11511                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11512                    .collect::<Vec<_>>(),
11513                snippet,
11514                window,
11515                cx,
11516            )
11517            .unwrap();
11518
11519        assert_state(
11520            editor,
11521            cx,
11522            indoc! {"
11523            type «» = ;•
11524            "},
11525        );
11526
11527        assert!(
11528            editor.context_menu_visible(),
11529            "Context menu should be visible for placeholder choices"
11530        );
11531
11532        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11533
11534        assert_state(
11535            editor,
11536            cx,
11537            indoc! {"
11538            type  = «»;•
11539            "},
11540        );
11541
11542        assert!(
11543            !editor.context_menu_visible(),
11544            "Context menu should be hidden after moving to next tabstop"
11545        );
11546
11547        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11548
11549        assert_state(
11550            editor,
11551            cx,
11552            indoc! {"
11553            type  = ; ˇ
11554            "},
11555        );
11556
11557        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11558
11559        assert_state(
11560            editor,
11561            cx,
11562            indoc! {"
11563            type  = ; ˇ
11564            "},
11565        );
11566    });
11567
11568    _ = editor.update_in(cx, |editor, window, cx| {
11569        editor.select_all(&SelectAll, window, cx);
11570        editor.backspace(&Backspace, window, cx);
11571
11572        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11573        let insertion_ranges = editor
11574            .selections
11575            .all(&editor.display_snapshot(cx))
11576            .iter()
11577            .map(|s| s.range())
11578            .collect::<Vec<_>>();
11579
11580        editor
11581            .insert_snippet(&insertion_ranges, snippet, window, cx)
11582            .unwrap();
11583
11584        assert_state(editor, cx, "fn «» = value;•");
11585
11586        assert!(
11587            editor.context_menu_visible(),
11588            "Context menu should be visible for placeholder choices"
11589        );
11590
11591        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11592
11593        assert_state(editor, cx, "fn  = «valueˇ»;•");
11594
11595        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11596
11597        assert_state(editor, cx, "fn «» = value;•");
11598
11599        assert!(
11600            editor.context_menu_visible(),
11601            "Context menu should be visible again after returning to first tabstop"
11602        );
11603
11604        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11605
11606        assert_state(editor, cx, "fn «» = value;•");
11607    });
11608}
11609
11610#[gpui::test]
11611async fn test_snippets(cx: &mut TestAppContext) {
11612    init_test(cx, |_| {});
11613
11614    let mut cx = EditorTestContext::new(cx).await;
11615
11616    cx.set_state(indoc! {"
11617        a.ˇ b
11618        a.ˇ b
11619        a.ˇ b
11620    "});
11621
11622    cx.update_editor(|editor, window, cx| {
11623        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11624        let insertion_ranges = editor
11625            .selections
11626            .all(&editor.display_snapshot(cx))
11627            .iter()
11628            .map(|s| s.range())
11629            .collect::<Vec<_>>();
11630        editor
11631            .insert_snippet(&insertion_ranges, snippet, window, cx)
11632            .unwrap();
11633    });
11634
11635    cx.assert_editor_state(indoc! {"
11636        a.f(«oneˇ», two, «threeˇ») b
11637        a.f(«oneˇ», two, «threeˇ») b
11638        a.f(«oneˇ», two, «threeˇ») b
11639    "});
11640
11641    // Can't move earlier than the first tab stop
11642    cx.update_editor(|editor, window, cx| {
11643        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11644    });
11645    cx.assert_editor_state(indoc! {"
11646        a.f(«oneˇ», two, «threeˇ») b
11647        a.f(«oneˇ», two, «threeˇ») b
11648        a.f(«oneˇ», two, «threeˇ») b
11649    "});
11650
11651    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11652    cx.assert_editor_state(indoc! {"
11653        a.f(one, «twoˇ», three) b
11654        a.f(one, «twoˇ», three) b
11655        a.f(one, «twoˇ», three) b
11656    "});
11657
11658    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11659    cx.assert_editor_state(indoc! {"
11660        a.f(«oneˇ», two, «threeˇ») b
11661        a.f(«oneˇ», two, «threeˇ») b
11662        a.f(«oneˇ», two, «threeˇ») b
11663    "});
11664
11665    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11666    cx.assert_editor_state(indoc! {"
11667        a.f(one, «twoˇ», three) b
11668        a.f(one, «twoˇ», three) b
11669        a.f(one, «twoˇ», three) b
11670    "});
11671    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11672    cx.assert_editor_state(indoc! {"
11673        a.f(one, two, three)ˇ b
11674        a.f(one, two, three)ˇ b
11675        a.f(one, two, three)ˇ b
11676    "});
11677
11678    // As soon as the last tab stop is reached, snippet state is gone
11679    cx.update_editor(|editor, window, cx| {
11680        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11681    });
11682    cx.assert_editor_state(indoc! {"
11683        a.f(one, two, three)ˇ b
11684        a.f(one, two, three)ˇ b
11685        a.f(one, two, three)ˇ b
11686    "});
11687}
11688
11689#[gpui::test]
11690async fn test_snippet_indentation(cx: &mut TestAppContext) {
11691    init_test(cx, |_| {});
11692
11693    let mut cx = EditorTestContext::new(cx).await;
11694
11695    cx.update_editor(|editor, window, cx| {
11696        let snippet = Snippet::parse(indoc! {"
11697            /*
11698             * Multiline comment with leading indentation
11699             *
11700             * $1
11701             */
11702            $0"})
11703        .unwrap();
11704        let insertion_ranges = editor
11705            .selections
11706            .all(&editor.display_snapshot(cx))
11707            .iter()
11708            .map(|s| s.range())
11709            .collect::<Vec<_>>();
11710        editor
11711            .insert_snippet(&insertion_ranges, snippet, window, cx)
11712            .unwrap();
11713    });
11714
11715    cx.assert_editor_state(indoc! {"
11716        /*
11717         * Multiline comment with leading indentation
11718         *
11719         * ˇ
11720         */
11721    "});
11722
11723    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11724    cx.assert_editor_state(indoc! {"
11725        /*
11726         * Multiline comment with leading indentation
11727         *
11728         *•
11729         */
11730        ˇ"});
11731}
11732
11733#[gpui::test]
11734async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11735    init_test(cx, |_| {});
11736
11737    let mut cx = EditorTestContext::new(cx).await;
11738    cx.update_editor(|editor, _, cx| {
11739        editor.project().unwrap().update(cx, |project, cx| {
11740            project.snippets().update(cx, |snippets, _cx| {
11741                let snippet = project::snippet_provider::Snippet {
11742                    prefix: vec!["multi word".to_string()],
11743                    body: "this is many words".to_string(),
11744                    description: Some("description".to_string()),
11745                    name: "multi-word snippet test".to_string(),
11746                };
11747                snippets.add_snippet_for_test(
11748                    None,
11749                    PathBuf::from("test_snippets.json"),
11750                    vec![Arc::new(snippet)],
11751                );
11752            });
11753        })
11754    });
11755
11756    for (input_to_simulate, should_match_snippet) in [
11757        ("m", true),
11758        ("m ", true),
11759        ("m w", true),
11760        ("aa m w", true),
11761        ("aa m g", false),
11762    ] {
11763        cx.set_state("ˇ");
11764        cx.simulate_input(input_to_simulate); // fails correctly
11765
11766        cx.update_editor(|editor, _, _| {
11767            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11768            else {
11769                assert!(!should_match_snippet); // no completions! don't even show the menu
11770                return;
11771            };
11772            assert!(context_menu.visible());
11773            let completions = context_menu.completions.borrow();
11774
11775            assert_eq!(!completions.is_empty(), should_match_snippet);
11776        });
11777    }
11778}
11779
11780#[gpui::test]
11781async fn test_document_format_during_save(cx: &mut TestAppContext) {
11782    init_test(cx, |_| {});
11783
11784    let fs = FakeFs::new(cx.executor());
11785    fs.insert_file(path!("/file.rs"), Default::default()).await;
11786
11787    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11788
11789    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11790    language_registry.add(rust_lang());
11791    let mut fake_servers = language_registry.register_fake_lsp(
11792        "Rust",
11793        FakeLspAdapter {
11794            capabilities: lsp::ServerCapabilities {
11795                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11796                ..Default::default()
11797            },
11798            ..Default::default()
11799        },
11800    );
11801
11802    let buffer = project
11803        .update(cx, |project, cx| {
11804            project.open_local_buffer(path!("/file.rs"), cx)
11805        })
11806        .await
11807        .unwrap();
11808
11809    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11810    let (editor, cx) = cx.add_window_view(|window, cx| {
11811        build_editor_with_project(project.clone(), buffer, window, cx)
11812    });
11813    editor.update_in(cx, |editor, window, cx| {
11814        editor.set_text("one\ntwo\nthree\n", window, cx)
11815    });
11816    assert!(cx.read(|cx| editor.is_dirty(cx)));
11817
11818    cx.executor().start_waiting();
11819    let fake_server = fake_servers.next().await.unwrap();
11820
11821    {
11822        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11823            move |params, _| async move {
11824                assert_eq!(
11825                    params.text_document.uri,
11826                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11827                );
11828                assert_eq!(params.options.tab_size, 4);
11829                Ok(Some(vec![lsp::TextEdit::new(
11830                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11831                    ", ".to_string(),
11832                )]))
11833            },
11834        );
11835        let save = editor
11836            .update_in(cx, |editor, window, cx| {
11837                editor.save(
11838                    SaveOptions {
11839                        format: true,
11840                        autosave: false,
11841                    },
11842                    project.clone(),
11843                    window,
11844                    cx,
11845                )
11846            })
11847            .unwrap();
11848        cx.executor().start_waiting();
11849        save.await;
11850
11851        assert_eq!(
11852            editor.update(cx, |editor, cx| editor.text(cx)),
11853            "one, two\nthree\n"
11854        );
11855        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11856    }
11857
11858    {
11859        editor.update_in(cx, |editor, window, cx| {
11860            editor.set_text("one\ntwo\nthree\n", window, cx)
11861        });
11862        assert!(cx.read(|cx| editor.is_dirty(cx)));
11863
11864        // Ensure we can still save even if formatting hangs.
11865        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11866            move |params, _| async move {
11867                assert_eq!(
11868                    params.text_document.uri,
11869                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11870                );
11871                futures::future::pending::<()>().await;
11872                unreachable!()
11873            },
11874        );
11875        let save = editor
11876            .update_in(cx, |editor, window, cx| {
11877                editor.save(
11878                    SaveOptions {
11879                        format: true,
11880                        autosave: false,
11881                    },
11882                    project.clone(),
11883                    window,
11884                    cx,
11885                )
11886            })
11887            .unwrap();
11888        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11889        cx.executor().start_waiting();
11890        save.await;
11891        assert_eq!(
11892            editor.update(cx, |editor, cx| editor.text(cx)),
11893            "one\ntwo\nthree\n"
11894        );
11895    }
11896
11897    // Set rust language override and assert overridden tabsize is sent to language server
11898    update_test_language_settings(cx, |settings| {
11899        settings.languages.0.insert(
11900            "Rust".into(),
11901            LanguageSettingsContent {
11902                tab_size: NonZeroU32::new(8),
11903                ..Default::default()
11904            },
11905        );
11906    });
11907
11908    {
11909        editor.update_in(cx, |editor, window, cx| {
11910            editor.set_text("somehting_new\n", window, cx)
11911        });
11912        assert!(cx.read(|cx| editor.is_dirty(cx)));
11913        let _formatting_request_signal = fake_server
11914            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11915                assert_eq!(
11916                    params.text_document.uri,
11917                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11918                );
11919                assert_eq!(params.options.tab_size, 8);
11920                Ok(Some(vec![]))
11921            });
11922        let save = editor
11923            .update_in(cx, |editor, window, cx| {
11924                editor.save(
11925                    SaveOptions {
11926                        format: true,
11927                        autosave: false,
11928                    },
11929                    project.clone(),
11930                    window,
11931                    cx,
11932                )
11933            })
11934            .unwrap();
11935        cx.executor().start_waiting();
11936        save.await;
11937    }
11938}
11939
11940#[gpui::test]
11941async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11942    init_test(cx, |settings| {
11943        settings.defaults.ensure_final_newline_on_save = Some(false);
11944    });
11945
11946    let fs = FakeFs::new(cx.executor());
11947    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11948
11949    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11950
11951    let buffer = project
11952        .update(cx, |project, cx| {
11953            project.open_local_buffer(path!("/file.txt"), cx)
11954        })
11955        .await
11956        .unwrap();
11957
11958    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11959    let (editor, cx) = cx.add_window_view(|window, cx| {
11960        build_editor_with_project(project.clone(), buffer, window, cx)
11961    });
11962    editor.update_in(cx, |editor, window, cx| {
11963        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11964            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11965        });
11966    });
11967    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11968
11969    editor.update_in(cx, |editor, window, cx| {
11970        editor.handle_input("\n", window, cx)
11971    });
11972    cx.run_until_parked();
11973    save(&editor, &project, cx).await;
11974    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11975
11976    editor.update_in(cx, |editor, window, cx| {
11977        editor.undo(&Default::default(), window, cx);
11978    });
11979    save(&editor, &project, cx).await;
11980    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11981
11982    editor.update_in(cx, |editor, window, cx| {
11983        editor.redo(&Default::default(), window, cx);
11984    });
11985    cx.run_until_parked();
11986    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11987
11988    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11989        let save = editor
11990            .update_in(cx, |editor, window, cx| {
11991                editor.save(
11992                    SaveOptions {
11993                        format: true,
11994                        autosave: false,
11995                    },
11996                    project.clone(),
11997                    window,
11998                    cx,
11999                )
12000            })
12001            .unwrap();
12002        cx.executor().start_waiting();
12003        save.await;
12004        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12005    }
12006}
12007
12008#[gpui::test]
12009async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12010    init_test(cx, |_| {});
12011
12012    let cols = 4;
12013    let rows = 10;
12014    let sample_text_1 = sample_text(rows, cols, 'a');
12015    assert_eq!(
12016        sample_text_1,
12017        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12018    );
12019    let sample_text_2 = sample_text(rows, cols, 'l');
12020    assert_eq!(
12021        sample_text_2,
12022        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12023    );
12024    let sample_text_3 = sample_text(rows, cols, 'v');
12025    assert_eq!(
12026        sample_text_3,
12027        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12028    );
12029
12030    let fs = FakeFs::new(cx.executor());
12031    fs.insert_tree(
12032        path!("/a"),
12033        json!({
12034            "main.rs": sample_text_1,
12035            "other.rs": sample_text_2,
12036            "lib.rs": sample_text_3,
12037        }),
12038    )
12039    .await;
12040
12041    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12042    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12043    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12044
12045    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12046    language_registry.add(rust_lang());
12047    let mut fake_servers = language_registry.register_fake_lsp(
12048        "Rust",
12049        FakeLspAdapter {
12050            capabilities: lsp::ServerCapabilities {
12051                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12052                ..Default::default()
12053            },
12054            ..Default::default()
12055        },
12056    );
12057
12058    let worktree = project.update(cx, |project, cx| {
12059        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12060        assert_eq!(worktrees.len(), 1);
12061        worktrees.pop().unwrap()
12062    });
12063    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12064
12065    let buffer_1 = project
12066        .update(cx, |project, cx| {
12067            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12068        })
12069        .await
12070        .unwrap();
12071    let buffer_2 = project
12072        .update(cx, |project, cx| {
12073            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12074        })
12075        .await
12076        .unwrap();
12077    let buffer_3 = project
12078        .update(cx, |project, cx| {
12079            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12080        })
12081        .await
12082        .unwrap();
12083
12084    let multi_buffer = cx.new(|cx| {
12085        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12086        multi_buffer.push_excerpts(
12087            buffer_1.clone(),
12088            [
12089                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12090                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12091                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12092            ],
12093            cx,
12094        );
12095        multi_buffer.push_excerpts(
12096            buffer_2.clone(),
12097            [
12098                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12099                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12100                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12101            ],
12102            cx,
12103        );
12104        multi_buffer.push_excerpts(
12105            buffer_3.clone(),
12106            [
12107                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12108                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12109                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12110            ],
12111            cx,
12112        );
12113        multi_buffer
12114    });
12115    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12116        Editor::new(
12117            EditorMode::full(),
12118            multi_buffer,
12119            Some(project.clone()),
12120            window,
12121            cx,
12122        )
12123    });
12124
12125    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12126        editor.change_selections(
12127            SelectionEffects::scroll(Autoscroll::Next),
12128            window,
12129            cx,
12130            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12131        );
12132        editor.insert("|one|two|three|", window, cx);
12133    });
12134    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12135    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12136        editor.change_selections(
12137            SelectionEffects::scroll(Autoscroll::Next),
12138            window,
12139            cx,
12140            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12141        );
12142        editor.insert("|four|five|six|", window, cx);
12143    });
12144    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12145
12146    // First two buffers should be edited, but not the third one.
12147    assert_eq!(
12148        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12149        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
12150    );
12151    buffer_1.update(cx, |buffer, _| {
12152        assert!(buffer.is_dirty());
12153        assert_eq!(
12154            buffer.text(),
12155            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12156        )
12157    });
12158    buffer_2.update(cx, |buffer, _| {
12159        assert!(buffer.is_dirty());
12160        assert_eq!(
12161            buffer.text(),
12162            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12163        )
12164    });
12165    buffer_3.update(cx, |buffer, _| {
12166        assert!(!buffer.is_dirty());
12167        assert_eq!(buffer.text(), sample_text_3,)
12168    });
12169    cx.executor().run_until_parked();
12170
12171    cx.executor().start_waiting();
12172    let save = multi_buffer_editor
12173        .update_in(cx, |editor, window, cx| {
12174            editor.save(
12175                SaveOptions {
12176                    format: true,
12177                    autosave: false,
12178                },
12179                project.clone(),
12180                window,
12181                cx,
12182            )
12183        })
12184        .unwrap();
12185
12186    let fake_server = fake_servers.next().await.unwrap();
12187    fake_server
12188        .server
12189        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12190            Ok(Some(vec![lsp::TextEdit::new(
12191                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12192                format!("[{} formatted]", params.text_document.uri),
12193            )]))
12194        })
12195        .detach();
12196    save.await;
12197
12198    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12199    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12200    assert_eq!(
12201        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12202        uri!(
12203            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
12204        ),
12205    );
12206    buffer_1.update(cx, |buffer, _| {
12207        assert!(!buffer.is_dirty());
12208        assert_eq!(
12209            buffer.text(),
12210            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12211        )
12212    });
12213    buffer_2.update(cx, |buffer, _| {
12214        assert!(!buffer.is_dirty());
12215        assert_eq!(
12216            buffer.text(),
12217            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12218        )
12219    });
12220    buffer_3.update(cx, |buffer, _| {
12221        assert!(!buffer.is_dirty());
12222        assert_eq!(buffer.text(), sample_text_3,)
12223    });
12224}
12225
12226#[gpui::test]
12227async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12228    init_test(cx, |_| {});
12229
12230    let fs = FakeFs::new(cx.executor());
12231    fs.insert_tree(
12232        path!("/dir"),
12233        json!({
12234            "file1.rs": "fn main() { println!(\"hello\"); }",
12235            "file2.rs": "fn test() { println!(\"test\"); }",
12236            "file3.rs": "fn other() { println!(\"other\"); }\n",
12237        }),
12238    )
12239    .await;
12240
12241    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12242    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12243    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12244
12245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12246    language_registry.add(rust_lang());
12247
12248    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12249    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12250
12251    // Open three buffers
12252    let buffer_1 = project
12253        .update(cx, |project, cx| {
12254            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12255        })
12256        .await
12257        .unwrap();
12258    let buffer_2 = project
12259        .update(cx, |project, cx| {
12260            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12261        })
12262        .await
12263        .unwrap();
12264    let buffer_3 = project
12265        .update(cx, |project, cx| {
12266            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12267        })
12268        .await
12269        .unwrap();
12270
12271    // Create a multi-buffer with all three buffers
12272    let multi_buffer = cx.new(|cx| {
12273        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12274        multi_buffer.push_excerpts(
12275            buffer_1.clone(),
12276            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12277            cx,
12278        );
12279        multi_buffer.push_excerpts(
12280            buffer_2.clone(),
12281            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12282            cx,
12283        );
12284        multi_buffer.push_excerpts(
12285            buffer_3.clone(),
12286            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12287            cx,
12288        );
12289        multi_buffer
12290    });
12291
12292    let editor = cx.new_window_entity(|window, cx| {
12293        Editor::new(
12294            EditorMode::full(),
12295            multi_buffer,
12296            Some(project.clone()),
12297            window,
12298            cx,
12299        )
12300    });
12301
12302    // Edit only the first buffer
12303    editor.update_in(cx, |editor, window, cx| {
12304        editor.change_selections(
12305            SelectionEffects::scroll(Autoscroll::Next),
12306            window,
12307            cx,
12308            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12309        );
12310        editor.insert("// edited", window, cx);
12311    });
12312
12313    // Verify that only buffer 1 is dirty
12314    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12315    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12316    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12317
12318    // Get write counts after file creation (files were created with initial content)
12319    // We expect each file to have been written once during creation
12320    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12321    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12322    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12323
12324    // Perform autosave
12325    let save_task = editor.update_in(cx, |editor, window, cx| {
12326        editor.save(
12327            SaveOptions {
12328                format: true,
12329                autosave: true,
12330            },
12331            project.clone(),
12332            window,
12333            cx,
12334        )
12335    });
12336    save_task.await.unwrap();
12337
12338    // Only the dirty buffer should have been saved
12339    assert_eq!(
12340        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12341        1,
12342        "Buffer 1 was dirty, so it should have been written once during autosave"
12343    );
12344    assert_eq!(
12345        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12346        0,
12347        "Buffer 2 was clean, so it should not have been written during autosave"
12348    );
12349    assert_eq!(
12350        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12351        0,
12352        "Buffer 3 was clean, so it should not have been written during autosave"
12353    );
12354
12355    // Verify buffer states after autosave
12356    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12357    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12358    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12359
12360    // Now perform a manual save (format = true)
12361    let save_task = editor.update_in(cx, |editor, window, cx| {
12362        editor.save(
12363            SaveOptions {
12364                format: true,
12365                autosave: false,
12366            },
12367            project.clone(),
12368            window,
12369            cx,
12370        )
12371    });
12372    save_task.await.unwrap();
12373
12374    // During manual save, clean buffers don't get written to disk
12375    // They just get did_save called for language server notifications
12376    assert_eq!(
12377        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12378        1,
12379        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12380    );
12381    assert_eq!(
12382        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12383        0,
12384        "Buffer 2 should not have been written at all"
12385    );
12386    assert_eq!(
12387        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12388        0,
12389        "Buffer 3 should not have been written at all"
12390    );
12391}
12392
12393async fn setup_range_format_test(
12394    cx: &mut TestAppContext,
12395) -> (
12396    Entity<Project>,
12397    Entity<Editor>,
12398    &mut gpui::VisualTestContext,
12399    lsp::FakeLanguageServer,
12400) {
12401    init_test(cx, |_| {});
12402
12403    let fs = FakeFs::new(cx.executor());
12404    fs.insert_file(path!("/file.rs"), Default::default()).await;
12405
12406    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12407
12408    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12409    language_registry.add(rust_lang());
12410    let mut fake_servers = language_registry.register_fake_lsp(
12411        "Rust",
12412        FakeLspAdapter {
12413            capabilities: lsp::ServerCapabilities {
12414                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12415                ..lsp::ServerCapabilities::default()
12416            },
12417            ..FakeLspAdapter::default()
12418        },
12419    );
12420
12421    let buffer = project
12422        .update(cx, |project, cx| {
12423            project.open_local_buffer(path!("/file.rs"), cx)
12424        })
12425        .await
12426        .unwrap();
12427
12428    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12429    let (editor, cx) = cx.add_window_view(|window, cx| {
12430        build_editor_with_project(project.clone(), buffer, window, cx)
12431    });
12432
12433    cx.executor().start_waiting();
12434    let fake_server = fake_servers.next().await.unwrap();
12435
12436    (project, editor, cx, fake_server)
12437}
12438
12439#[gpui::test]
12440async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12441    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12442
12443    editor.update_in(cx, |editor, window, cx| {
12444        editor.set_text("one\ntwo\nthree\n", window, cx)
12445    });
12446    assert!(cx.read(|cx| editor.is_dirty(cx)));
12447
12448    let save = editor
12449        .update_in(cx, |editor, window, cx| {
12450            editor.save(
12451                SaveOptions {
12452                    format: true,
12453                    autosave: false,
12454                },
12455                project.clone(),
12456                window,
12457                cx,
12458            )
12459        })
12460        .unwrap();
12461    fake_server
12462        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12463            assert_eq!(
12464                params.text_document.uri,
12465                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12466            );
12467            assert_eq!(params.options.tab_size, 4);
12468            Ok(Some(vec![lsp::TextEdit::new(
12469                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12470                ", ".to_string(),
12471            )]))
12472        })
12473        .next()
12474        .await;
12475    cx.executor().start_waiting();
12476    save.await;
12477    assert_eq!(
12478        editor.update(cx, |editor, cx| editor.text(cx)),
12479        "one, two\nthree\n"
12480    );
12481    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12482}
12483
12484#[gpui::test]
12485async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12486    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12487
12488    editor.update_in(cx, |editor, window, cx| {
12489        editor.set_text("one\ntwo\nthree\n", window, cx)
12490    });
12491    assert!(cx.read(|cx| editor.is_dirty(cx)));
12492
12493    // Test that save still works when formatting hangs
12494    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12495        move |params, _| async move {
12496            assert_eq!(
12497                params.text_document.uri,
12498                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12499            );
12500            futures::future::pending::<()>().await;
12501            unreachable!()
12502        },
12503    );
12504    let save = editor
12505        .update_in(cx, |editor, window, cx| {
12506            editor.save(
12507                SaveOptions {
12508                    format: true,
12509                    autosave: false,
12510                },
12511                project.clone(),
12512                window,
12513                cx,
12514            )
12515        })
12516        .unwrap();
12517    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12518    cx.executor().start_waiting();
12519    save.await;
12520    assert_eq!(
12521        editor.update(cx, |editor, cx| editor.text(cx)),
12522        "one\ntwo\nthree\n"
12523    );
12524    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12525}
12526
12527#[gpui::test]
12528async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12529    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12530
12531    // Buffer starts clean, no formatting should be requested
12532    let save = editor
12533        .update_in(cx, |editor, window, cx| {
12534            editor.save(
12535                SaveOptions {
12536                    format: false,
12537                    autosave: false,
12538                },
12539                project.clone(),
12540                window,
12541                cx,
12542            )
12543        })
12544        .unwrap();
12545    let _pending_format_request = fake_server
12546        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12547            panic!("Should not be invoked");
12548        })
12549        .next();
12550    cx.executor().start_waiting();
12551    save.await;
12552    cx.run_until_parked();
12553}
12554
12555#[gpui::test]
12556async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12557    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12558
12559    // Set Rust language override and assert overridden tabsize is sent to language server
12560    update_test_language_settings(cx, |settings| {
12561        settings.languages.0.insert(
12562            "Rust".into(),
12563            LanguageSettingsContent {
12564                tab_size: NonZeroU32::new(8),
12565                ..Default::default()
12566            },
12567        );
12568    });
12569
12570    editor.update_in(cx, |editor, window, cx| {
12571        editor.set_text("something_new\n", window, cx)
12572    });
12573    assert!(cx.read(|cx| editor.is_dirty(cx)));
12574    let save = editor
12575        .update_in(cx, |editor, window, cx| {
12576            editor.save(
12577                SaveOptions {
12578                    format: true,
12579                    autosave: false,
12580                },
12581                project.clone(),
12582                window,
12583                cx,
12584            )
12585        })
12586        .unwrap();
12587    fake_server
12588        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12589            assert_eq!(
12590                params.text_document.uri,
12591                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12592            );
12593            assert_eq!(params.options.tab_size, 8);
12594            Ok(Some(Vec::new()))
12595        })
12596        .next()
12597        .await;
12598    save.await;
12599}
12600
12601#[gpui::test]
12602async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12603    init_test(cx, |settings| {
12604        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12605            settings::LanguageServerFormatterSpecifier::Current,
12606        )))
12607    });
12608
12609    let fs = FakeFs::new(cx.executor());
12610    fs.insert_file(path!("/file.rs"), Default::default()).await;
12611
12612    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12613
12614    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12615    language_registry.add(Arc::new(Language::new(
12616        LanguageConfig {
12617            name: "Rust".into(),
12618            matcher: LanguageMatcher {
12619                path_suffixes: vec!["rs".to_string()],
12620                ..Default::default()
12621            },
12622            ..LanguageConfig::default()
12623        },
12624        Some(tree_sitter_rust::LANGUAGE.into()),
12625    )));
12626    update_test_language_settings(cx, |settings| {
12627        // Enable Prettier formatting for the same buffer, and ensure
12628        // LSP is called instead of Prettier.
12629        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12630    });
12631    let mut fake_servers = language_registry.register_fake_lsp(
12632        "Rust",
12633        FakeLspAdapter {
12634            capabilities: lsp::ServerCapabilities {
12635                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12636                ..Default::default()
12637            },
12638            ..Default::default()
12639        },
12640    );
12641
12642    let buffer = project
12643        .update(cx, |project, cx| {
12644            project.open_local_buffer(path!("/file.rs"), cx)
12645        })
12646        .await
12647        .unwrap();
12648
12649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12650    let (editor, cx) = cx.add_window_view(|window, cx| {
12651        build_editor_with_project(project.clone(), buffer, window, cx)
12652    });
12653    editor.update_in(cx, |editor, window, cx| {
12654        editor.set_text("one\ntwo\nthree\n", window, cx)
12655    });
12656
12657    cx.executor().start_waiting();
12658    let fake_server = fake_servers.next().await.unwrap();
12659
12660    let format = editor
12661        .update_in(cx, |editor, window, cx| {
12662            editor.perform_format(
12663                project.clone(),
12664                FormatTrigger::Manual,
12665                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12666                window,
12667                cx,
12668            )
12669        })
12670        .unwrap();
12671    fake_server
12672        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12673            assert_eq!(
12674                params.text_document.uri,
12675                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12676            );
12677            assert_eq!(params.options.tab_size, 4);
12678            Ok(Some(vec![lsp::TextEdit::new(
12679                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12680                ", ".to_string(),
12681            )]))
12682        })
12683        .next()
12684        .await;
12685    cx.executor().start_waiting();
12686    format.await;
12687    assert_eq!(
12688        editor.update(cx, |editor, cx| editor.text(cx)),
12689        "one, two\nthree\n"
12690    );
12691
12692    editor.update_in(cx, |editor, window, cx| {
12693        editor.set_text("one\ntwo\nthree\n", window, cx)
12694    });
12695    // Ensure we don't lock if formatting hangs.
12696    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12697        move |params, _| async move {
12698            assert_eq!(
12699                params.text_document.uri,
12700                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12701            );
12702            futures::future::pending::<()>().await;
12703            unreachable!()
12704        },
12705    );
12706    let format = editor
12707        .update_in(cx, |editor, window, cx| {
12708            editor.perform_format(
12709                project,
12710                FormatTrigger::Manual,
12711                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12712                window,
12713                cx,
12714            )
12715        })
12716        .unwrap();
12717    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12718    cx.executor().start_waiting();
12719    format.await;
12720    assert_eq!(
12721        editor.update(cx, |editor, cx| editor.text(cx)),
12722        "one\ntwo\nthree\n"
12723    );
12724}
12725
12726#[gpui::test]
12727async fn test_multiple_formatters(cx: &mut TestAppContext) {
12728    init_test(cx, |settings| {
12729        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12730        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12731            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12732            Formatter::CodeAction("code-action-1".into()),
12733            Formatter::CodeAction("code-action-2".into()),
12734        ]))
12735    });
12736
12737    let fs = FakeFs::new(cx.executor());
12738    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12739        .await;
12740
12741    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12742    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12743    language_registry.add(rust_lang());
12744
12745    let mut fake_servers = language_registry.register_fake_lsp(
12746        "Rust",
12747        FakeLspAdapter {
12748            capabilities: lsp::ServerCapabilities {
12749                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12750                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12751                    commands: vec!["the-command-for-code-action-1".into()],
12752                    ..Default::default()
12753                }),
12754                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12755                ..Default::default()
12756            },
12757            ..Default::default()
12758        },
12759    );
12760
12761    let buffer = project
12762        .update(cx, |project, cx| {
12763            project.open_local_buffer(path!("/file.rs"), cx)
12764        })
12765        .await
12766        .unwrap();
12767
12768    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12769    let (editor, cx) = cx.add_window_view(|window, cx| {
12770        build_editor_with_project(project.clone(), buffer, window, cx)
12771    });
12772
12773    cx.executor().start_waiting();
12774
12775    let fake_server = fake_servers.next().await.unwrap();
12776    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12777        move |_params, _| async move {
12778            Ok(Some(vec![lsp::TextEdit::new(
12779                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12780                "applied-formatting\n".to_string(),
12781            )]))
12782        },
12783    );
12784    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12785        move |params, _| async move {
12786            let requested_code_actions = params.context.only.expect("Expected code action request");
12787            assert_eq!(requested_code_actions.len(), 1);
12788
12789            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12790            let code_action = match requested_code_actions[0].as_str() {
12791                "code-action-1" => lsp::CodeAction {
12792                    kind: Some("code-action-1".into()),
12793                    edit: Some(lsp::WorkspaceEdit::new(
12794                        [(
12795                            uri,
12796                            vec![lsp::TextEdit::new(
12797                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12798                                "applied-code-action-1-edit\n".to_string(),
12799                            )],
12800                        )]
12801                        .into_iter()
12802                        .collect(),
12803                    )),
12804                    command: Some(lsp::Command {
12805                        command: "the-command-for-code-action-1".into(),
12806                        ..Default::default()
12807                    }),
12808                    ..Default::default()
12809                },
12810                "code-action-2" => lsp::CodeAction {
12811                    kind: Some("code-action-2".into()),
12812                    edit: Some(lsp::WorkspaceEdit::new(
12813                        [(
12814                            uri,
12815                            vec![lsp::TextEdit::new(
12816                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12817                                "applied-code-action-2-edit\n".to_string(),
12818                            )],
12819                        )]
12820                        .into_iter()
12821                        .collect(),
12822                    )),
12823                    ..Default::default()
12824                },
12825                req => panic!("Unexpected code action request: {:?}", req),
12826            };
12827            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12828                code_action,
12829            )]))
12830        },
12831    );
12832
12833    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12834        move |params, _| async move { Ok(params) }
12835    });
12836
12837    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12838    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12839        let fake = fake_server.clone();
12840        let lock = command_lock.clone();
12841        move |params, _| {
12842            assert_eq!(params.command, "the-command-for-code-action-1");
12843            let fake = fake.clone();
12844            let lock = lock.clone();
12845            async move {
12846                lock.lock().await;
12847                fake.server
12848                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12849                        label: None,
12850                        edit: lsp::WorkspaceEdit {
12851                            changes: Some(
12852                                [(
12853                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12854                                    vec![lsp::TextEdit {
12855                                        range: lsp::Range::new(
12856                                            lsp::Position::new(0, 0),
12857                                            lsp::Position::new(0, 0),
12858                                        ),
12859                                        new_text: "applied-code-action-1-command\n".into(),
12860                                    }],
12861                                )]
12862                                .into_iter()
12863                                .collect(),
12864                            ),
12865                            ..Default::default()
12866                        },
12867                    })
12868                    .await
12869                    .into_response()
12870                    .unwrap();
12871                Ok(Some(json!(null)))
12872            }
12873        }
12874    });
12875
12876    cx.executor().start_waiting();
12877    editor
12878        .update_in(cx, |editor, window, cx| {
12879            editor.perform_format(
12880                project.clone(),
12881                FormatTrigger::Manual,
12882                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12883                window,
12884                cx,
12885            )
12886        })
12887        .unwrap()
12888        .await;
12889    editor.update(cx, |editor, cx| {
12890        assert_eq!(
12891            editor.text(cx),
12892            r#"
12893                applied-code-action-2-edit
12894                applied-code-action-1-command
12895                applied-code-action-1-edit
12896                applied-formatting
12897                one
12898                two
12899                three
12900            "#
12901            .unindent()
12902        );
12903    });
12904
12905    editor.update_in(cx, |editor, window, cx| {
12906        editor.undo(&Default::default(), window, cx);
12907        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12908    });
12909
12910    // Perform a manual edit while waiting for an LSP command
12911    // that's being run as part of a formatting code action.
12912    let lock_guard = command_lock.lock().await;
12913    let format = editor
12914        .update_in(cx, |editor, window, cx| {
12915            editor.perform_format(
12916                project.clone(),
12917                FormatTrigger::Manual,
12918                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12919                window,
12920                cx,
12921            )
12922        })
12923        .unwrap();
12924    cx.run_until_parked();
12925    editor.update(cx, |editor, cx| {
12926        assert_eq!(
12927            editor.text(cx),
12928            r#"
12929                applied-code-action-1-edit
12930                applied-formatting
12931                one
12932                two
12933                three
12934            "#
12935            .unindent()
12936        );
12937
12938        editor.buffer.update(cx, |buffer, cx| {
12939            let ix = buffer.len(cx);
12940            buffer.edit([(ix..ix, "edited\n")], None, cx);
12941        });
12942    });
12943
12944    // Allow the LSP command to proceed. Because the buffer was edited,
12945    // the second code action will not be run.
12946    drop(lock_guard);
12947    format.await;
12948    editor.update_in(cx, |editor, window, cx| {
12949        assert_eq!(
12950            editor.text(cx),
12951            r#"
12952                applied-code-action-1-command
12953                applied-code-action-1-edit
12954                applied-formatting
12955                one
12956                two
12957                three
12958                edited
12959            "#
12960            .unindent()
12961        );
12962
12963        // The manual edit is undone first, because it is the last thing the user did
12964        // (even though the command completed afterwards).
12965        editor.undo(&Default::default(), window, cx);
12966        assert_eq!(
12967            editor.text(cx),
12968            r#"
12969                applied-code-action-1-command
12970                applied-code-action-1-edit
12971                applied-formatting
12972                one
12973                two
12974                three
12975            "#
12976            .unindent()
12977        );
12978
12979        // All the formatting (including the command, which completed after the manual edit)
12980        // is undone together.
12981        editor.undo(&Default::default(), window, cx);
12982        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12983    });
12984}
12985
12986#[gpui::test]
12987async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12988    init_test(cx, |settings| {
12989        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12990            settings::LanguageServerFormatterSpecifier::Current,
12991        )]))
12992    });
12993
12994    let fs = FakeFs::new(cx.executor());
12995    fs.insert_file(path!("/file.ts"), Default::default()).await;
12996
12997    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12998
12999    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13000    language_registry.add(Arc::new(Language::new(
13001        LanguageConfig {
13002            name: "TypeScript".into(),
13003            matcher: LanguageMatcher {
13004                path_suffixes: vec!["ts".to_string()],
13005                ..Default::default()
13006            },
13007            ..LanguageConfig::default()
13008        },
13009        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13010    )));
13011    update_test_language_settings(cx, |settings| {
13012        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13013    });
13014    let mut fake_servers = language_registry.register_fake_lsp(
13015        "TypeScript",
13016        FakeLspAdapter {
13017            capabilities: lsp::ServerCapabilities {
13018                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13019                ..Default::default()
13020            },
13021            ..Default::default()
13022        },
13023    );
13024
13025    let buffer = project
13026        .update(cx, |project, cx| {
13027            project.open_local_buffer(path!("/file.ts"), cx)
13028        })
13029        .await
13030        .unwrap();
13031
13032    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13033    let (editor, cx) = cx.add_window_view(|window, cx| {
13034        build_editor_with_project(project.clone(), buffer, window, cx)
13035    });
13036    editor.update_in(cx, |editor, window, cx| {
13037        editor.set_text(
13038            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13039            window,
13040            cx,
13041        )
13042    });
13043
13044    cx.executor().start_waiting();
13045    let fake_server = fake_servers.next().await.unwrap();
13046
13047    let format = editor
13048        .update_in(cx, |editor, window, cx| {
13049            editor.perform_code_action_kind(
13050                project.clone(),
13051                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13052                window,
13053                cx,
13054            )
13055        })
13056        .unwrap();
13057    fake_server
13058        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13059            assert_eq!(
13060                params.text_document.uri,
13061                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13062            );
13063            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13064                lsp::CodeAction {
13065                    title: "Organize Imports".to_string(),
13066                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13067                    edit: Some(lsp::WorkspaceEdit {
13068                        changes: Some(
13069                            [(
13070                                params.text_document.uri.clone(),
13071                                vec![lsp::TextEdit::new(
13072                                    lsp::Range::new(
13073                                        lsp::Position::new(1, 0),
13074                                        lsp::Position::new(2, 0),
13075                                    ),
13076                                    "".to_string(),
13077                                )],
13078                            )]
13079                            .into_iter()
13080                            .collect(),
13081                        ),
13082                        ..Default::default()
13083                    }),
13084                    ..Default::default()
13085                },
13086            )]))
13087        })
13088        .next()
13089        .await;
13090    cx.executor().start_waiting();
13091    format.await;
13092    assert_eq!(
13093        editor.update(cx, |editor, cx| editor.text(cx)),
13094        "import { a } from 'module';\n\nconst x = a;\n"
13095    );
13096
13097    editor.update_in(cx, |editor, window, cx| {
13098        editor.set_text(
13099            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13100            window,
13101            cx,
13102        )
13103    });
13104    // Ensure we don't lock if code action hangs.
13105    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13106        move |params, _| async move {
13107            assert_eq!(
13108                params.text_document.uri,
13109                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13110            );
13111            futures::future::pending::<()>().await;
13112            unreachable!()
13113        },
13114    );
13115    let format = editor
13116        .update_in(cx, |editor, window, cx| {
13117            editor.perform_code_action_kind(
13118                project,
13119                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13120                window,
13121                cx,
13122            )
13123        })
13124        .unwrap();
13125    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13126    cx.executor().start_waiting();
13127    format.await;
13128    assert_eq!(
13129        editor.update(cx, |editor, cx| editor.text(cx)),
13130        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13131    );
13132}
13133
13134#[gpui::test]
13135async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13136    init_test(cx, |_| {});
13137
13138    let mut cx = EditorLspTestContext::new_rust(
13139        lsp::ServerCapabilities {
13140            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13141            ..Default::default()
13142        },
13143        cx,
13144    )
13145    .await;
13146
13147    cx.set_state(indoc! {"
13148        one.twoˇ
13149    "});
13150
13151    // The format request takes a long time. When it completes, it inserts
13152    // a newline and an indent before the `.`
13153    cx.lsp
13154        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13155            let executor = cx.background_executor().clone();
13156            async move {
13157                executor.timer(Duration::from_millis(100)).await;
13158                Ok(Some(vec![lsp::TextEdit {
13159                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13160                    new_text: "\n    ".into(),
13161                }]))
13162            }
13163        });
13164
13165    // Submit a format request.
13166    let format_1 = cx
13167        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13168        .unwrap();
13169    cx.executor().run_until_parked();
13170
13171    // Submit a second format request.
13172    let format_2 = cx
13173        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13174        .unwrap();
13175    cx.executor().run_until_parked();
13176
13177    // Wait for both format requests to complete
13178    cx.executor().advance_clock(Duration::from_millis(200));
13179    cx.executor().start_waiting();
13180    format_1.await.unwrap();
13181    cx.executor().start_waiting();
13182    format_2.await.unwrap();
13183
13184    // The formatting edits only happens once.
13185    cx.assert_editor_state(indoc! {"
13186        one
13187            .twoˇ
13188    "});
13189}
13190
13191#[gpui::test]
13192async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13193    init_test(cx, |settings| {
13194        settings.defaults.formatter = Some(FormatterList::default())
13195    });
13196
13197    let mut cx = EditorLspTestContext::new_rust(
13198        lsp::ServerCapabilities {
13199            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13200            ..Default::default()
13201        },
13202        cx,
13203    )
13204    .await;
13205
13206    // Record which buffer changes have been sent to the language server
13207    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13208    cx.lsp
13209        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13210            let buffer_changes = buffer_changes.clone();
13211            move |params, _| {
13212                buffer_changes.lock().extend(
13213                    params
13214                        .content_changes
13215                        .into_iter()
13216                        .map(|e| (e.range.unwrap(), e.text)),
13217                );
13218            }
13219        });
13220    // Handle formatting requests to the language server.
13221    cx.lsp
13222        .set_request_handler::<lsp::request::Formatting, _, _>({
13223            let buffer_changes = buffer_changes.clone();
13224            move |_, _| {
13225                let buffer_changes = buffer_changes.clone();
13226                // Insert blank lines between each line of the buffer.
13227                async move {
13228                    // When formatting is requested, trailing whitespace has already been stripped,
13229                    // and the trailing newline has already been added.
13230                    assert_eq!(
13231                        &buffer_changes.lock()[1..],
13232                        &[
13233                            (
13234                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13235                                "".into()
13236                            ),
13237                            (
13238                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13239                                "".into()
13240                            ),
13241                            (
13242                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13243                                "\n".into()
13244                            ),
13245                        ]
13246                    );
13247
13248                    Ok(Some(vec![
13249                        lsp::TextEdit {
13250                            range: lsp::Range::new(
13251                                lsp::Position::new(1, 0),
13252                                lsp::Position::new(1, 0),
13253                            ),
13254                            new_text: "\n".into(),
13255                        },
13256                        lsp::TextEdit {
13257                            range: lsp::Range::new(
13258                                lsp::Position::new(2, 0),
13259                                lsp::Position::new(2, 0),
13260                            ),
13261                            new_text: "\n".into(),
13262                        },
13263                    ]))
13264                }
13265            }
13266        });
13267
13268    // Set up a buffer white some trailing whitespace and no trailing newline.
13269    cx.set_state(
13270        &[
13271            "one ",   //
13272            "twoˇ",   //
13273            "three ", //
13274            "four",   //
13275        ]
13276        .join("\n"),
13277    );
13278    cx.run_until_parked();
13279
13280    // Submit a format request.
13281    let format = cx
13282        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13283        .unwrap();
13284
13285    cx.run_until_parked();
13286    // After formatting the buffer, the trailing whitespace is stripped,
13287    // a newline is appended, and the edits provided by the language server
13288    // have been applied.
13289    format.await.unwrap();
13290
13291    cx.assert_editor_state(
13292        &[
13293            "one",   //
13294            "",      //
13295            "twoˇ",  //
13296            "",      //
13297            "three", //
13298            "four",  //
13299            "",      //
13300        ]
13301        .join("\n"),
13302    );
13303
13304    // Undoing the formatting undoes the trailing whitespace removal, the
13305    // trailing newline, and the LSP edits.
13306    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13307    cx.assert_editor_state(
13308        &[
13309            "one ",   //
13310            "twoˇ",   //
13311            "three ", //
13312            "four",   //
13313        ]
13314        .join("\n"),
13315    );
13316}
13317
13318#[gpui::test]
13319async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13320    cx: &mut TestAppContext,
13321) {
13322    init_test(cx, |_| {});
13323
13324    cx.update(|cx| {
13325        cx.update_global::<SettingsStore, _>(|settings, cx| {
13326            settings.update_user_settings(cx, |settings| {
13327                settings.editor.auto_signature_help = Some(true);
13328            });
13329        });
13330    });
13331
13332    let mut cx = EditorLspTestContext::new_rust(
13333        lsp::ServerCapabilities {
13334            signature_help_provider: Some(lsp::SignatureHelpOptions {
13335                ..Default::default()
13336            }),
13337            ..Default::default()
13338        },
13339        cx,
13340    )
13341    .await;
13342
13343    let language = Language::new(
13344        LanguageConfig {
13345            name: "Rust".into(),
13346            brackets: BracketPairConfig {
13347                pairs: vec![
13348                    BracketPair {
13349                        start: "{".to_string(),
13350                        end: "}".to_string(),
13351                        close: true,
13352                        surround: true,
13353                        newline: true,
13354                    },
13355                    BracketPair {
13356                        start: "(".to_string(),
13357                        end: ")".to_string(),
13358                        close: true,
13359                        surround: true,
13360                        newline: true,
13361                    },
13362                    BracketPair {
13363                        start: "/*".to_string(),
13364                        end: " */".to_string(),
13365                        close: true,
13366                        surround: true,
13367                        newline: true,
13368                    },
13369                    BracketPair {
13370                        start: "[".to_string(),
13371                        end: "]".to_string(),
13372                        close: false,
13373                        surround: false,
13374                        newline: true,
13375                    },
13376                    BracketPair {
13377                        start: "\"".to_string(),
13378                        end: "\"".to_string(),
13379                        close: true,
13380                        surround: true,
13381                        newline: false,
13382                    },
13383                    BracketPair {
13384                        start: "<".to_string(),
13385                        end: ">".to_string(),
13386                        close: false,
13387                        surround: true,
13388                        newline: true,
13389                    },
13390                ],
13391                ..Default::default()
13392            },
13393            autoclose_before: "})]".to_string(),
13394            ..Default::default()
13395        },
13396        Some(tree_sitter_rust::LANGUAGE.into()),
13397    );
13398    let language = Arc::new(language);
13399
13400    cx.language_registry().add(language.clone());
13401    cx.update_buffer(|buffer, cx| {
13402        buffer.set_language(Some(language), cx);
13403    });
13404
13405    cx.set_state(
13406        &r#"
13407            fn main() {
13408                sampleˇ
13409            }
13410        "#
13411        .unindent(),
13412    );
13413
13414    cx.update_editor(|editor, window, cx| {
13415        editor.handle_input("(", window, cx);
13416    });
13417    cx.assert_editor_state(
13418        &"
13419            fn main() {
13420                sample(ˇ)
13421            }
13422        "
13423        .unindent(),
13424    );
13425
13426    let mocked_response = lsp::SignatureHelp {
13427        signatures: vec![lsp::SignatureInformation {
13428            label: "fn sample(param1: u8, param2: u8)".to_string(),
13429            documentation: None,
13430            parameters: Some(vec![
13431                lsp::ParameterInformation {
13432                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13433                    documentation: None,
13434                },
13435                lsp::ParameterInformation {
13436                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13437                    documentation: None,
13438                },
13439            ]),
13440            active_parameter: None,
13441        }],
13442        active_signature: Some(0),
13443        active_parameter: Some(0),
13444    };
13445    handle_signature_help_request(&mut cx, mocked_response).await;
13446
13447    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13448        .await;
13449
13450    cx.editor(|editor, _, _| {
13451        let signature_help_state = editor.signature_help_state.popover().cloned();
13452        let signature = signature_help_state.unwrap();
13453        assert_eq!(
13454            signature.signatures[signature.current_signature].label,
13455            "fn sample(param1: u8, param2: u8)"
13456        );
13457    });
13458}
13459
13460#[gpui::test]
13461async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13462    init_test(cx, |_| {});
13463
13464    cx.update(|cx| {
13465        cx.update_global::<SettingsStore, _>(|settings, cx| {
13466            settings.update_user_settings(cx, |settings| {
13467                settings.editor.auto_signature_help = Some(false);
13468                settings.editor.show_signature_help_after_edits = Some(false);
13469            });
13470        });
13471    });
13472
13473    let mut cx = EditorLspTestContext::new_rust(
13474        lsp::ServerCapabilities {
13475            signature_help_provider: Some(lsp::SignatureHelpOptions {
13476                ..Default::default()
13477            }),
13478            ..Default::default()
13479        },
13480        cx,
13481    )
13482    .await;
13483
13484    let language = Language::new(
13485        LanguageConfig {
13486            name: "Rust".into(),
13487            brackets: BracketPairConfig {
13488                pairs: vec![
13489                    BracketPair {
13490                        start: "{".to_string(),
13491                        end: "}".to_string(),
13492                        close: true,
13493                        surround: true,
13494                        newline: true,
13495                    },
13496                    BracketPair {
13497                        start: "(".to_string(),
13498                        end: ")".to_string(),
13499                        close: true,
13500                        surround: true,
13501                        newline: true,
13502                    },
13503                    BracketPair {
13504                        start: "/*".to_string(),
13505                        end: " */".to_string(),
13506                        close: true,
13507                        surround: true,
13508                        newline: true,
13509                    },
13510                    BracketPair {
13511                        start: "[".to_string(),
13512                        end: "]".to_string(),
13513                        close: false,
13514                        surround: false,
13515                        newline: true,
13516                    },
13517                    BracketPair {
13518                        start: "\"".to_string(),
13519                        end: "\"".to_string(),
13520                        close: true,
13521                        surround: true,
13522                        newline: false,
13523                    },
13524                    BracketPair {
13525                        start: "<".to_string(),
13526                        end: ">".to_string(),
13527                        close: false,
13528                        surround: true,
13529                        newline: true,
13530                    },
13531                ],
13532                ..Default::default()
13533            },
13534            autoclose_before: "})]".to_string(),
13535            ..Default::default()
13536        },
13537        Some(tree_sitter_rust::LANGUAGE.into()),
13538    );
13539    let language = Arc::new(language);
13540
13541    cx.language_registry().add(language.clone());
13542    cx.update_buffer(|buffer, cx| {
13543        buffer.set_language(Some(language), cx);
13544    });
13545
13546    // Ensure that signature_help is not called when no signature help is enabled.
13547    cx.set_state(
13548        &r#"
13549            fn main() {
13550                sampleˇ
13551            }
13552        "#
13553        .unindent(),
13554    );
13555    cx.update_editor(|editor, window, cx| {
13556        editor.handle_input("(", window, cx);
13557    });
13558    cx.assert_editor_state(
13559        &"
13560            fn main() {
13561                sample(ˇ)
13562            }
13563        "
13564        .unindent(),
13565    );
13566    cx.editor(|editor, _, _| {
13567        assert!(editor.signature_help_state.task().is_none());
13568    });
13569
13570    let mocked_response = lsp::SignatureHelp {
13571        signatures: vec![lsp::SignatureInformation {
13572            label: "fn sample(param1: u8, param2: u8)".to_string(),
13573            documentation: None,
13574            parameters: Some(vec![
13575                lsp::ParameterInformation {
13576                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13577                    documentation: None,
13578                },
13579                lsp::ParameterInformation {
13580                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13581                    documentation: None,
13582                },
13583            ]),
13584            active_parameter: None,
13585        }],
13586        active_signature: Some(0),
13587        active_parameter: Some(0),
13588    };
13589
13590    // Ensure that signature_help is called when enabled afte edits
13591    cx.update(|_, cx| {
13592        cx.update_global::<SettingsStore, _>(|settings, cx| {
13593            settings.update_user_settings(cx, |settings| {
13594                settings.editor.auto_signature_help = Some(false);
13595                settings.editor.show_signature_help_after_edits = Some(true);
13596            });
13597        });
13598    });
13599    cx.set_state(
13600        &r#"
13601            fn main() {
13602                sampleˇ
13603            }
13604        "#
13605        .unindent(),
13606    );
13607    cx.update_editor(|editor, window, cx| {
13608        editor.handle_input("(", window, cx);
13609    });
13610    cx.assert_editor_state(
13611        &"
13612            fn main() {
13613                sample(ˇ)
13614            }
13615        "
13616        .unindent(),
13617    );
13618    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13619    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13620        .await;
13621    cx.update_editor(|editor, _, _| {
13622        let signature_help_state = editor.signature_help_state.popover().cloned();
13623        assert!(signature_help_state.is_some());
13624        let signature = signature_help_state.unwrap();
13625        assert_eq!(
13626            signature.signatures[signature.current_signature].label,
13627            "fn sample(param1: u8, param2: u8)"
13628        );
13629        editor.signature_help_state = SignatureHelpState::default();
13630    });
13631
13632    // Ensure that signature_help is called when auto signature help override is enabled
13633    cx.update(|_, cx| {
13634        cx.update_global::<SettingsStore, _>(|settings, cx| {
13635            settings.update_user_settings(cx, |settings| {
13636                settings.editor.auto_signature_help = Some(true);
13637                settings.editor.show_signature_help_after_edits = Some(false);
13638            });
13639        });
13640    });
13641    cx.set_state(
13642        &r#"
13643            fn main() {
13644                sampleˇ
13645            }
13646        "#
13647        .unindent(),
13648    );
13649    cx.update_editor(|editor, window, cx| {
13650        editor.handle_input("(", window, cx);
13651    });
13652    cx.assert_editor_state(
13653        &"
13654            fn main() {
13655                sample(ˇ)
13656            }
13657        "
13658        .unindent(),
13659    );
13660    handle_signature_help_request(&mut cx, mocked_response).await;
13661    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13662        .await;
13663    cx.editor(|editor, _, _| {
13664        let signature_help_state = editor.signature_help_state.popover().cloned();
13665        assert!(signature_help_state.is_some());
13666        let signature = signature_help_state.unwrap();
13667        assert_eq!(
13668            signature.signatures[signature.current_signature].label,
13669            "fn sample(param1: u8, param2: u8)"
13670        );
13671    });
13672}
13673
13674#[gpui::test]
13675async fn test_signature_help(cx: &mut TestAppContext) {
13676    init_test(cx, |_| {});
13677    cx.update(|cx| {
13678        cx.update_global::<SettingsStore, _>(|settings, cx| {
13679            settings.update_user_settings(cx, |settings| {
13680                settings.editor.auto_signature_help = Some(true);
13681            });
13682        });
13683    });
13684
13685    let mut cx = EditorLspTestContext::new_rust(
13686        lsp::ServerCapabilities {
13687            signature_help_provider: Some(lsp::SignatureHelpOptions {
13688                ..Default::default()
13689            }),
13690            ..Default::default()
13691        },
13692        cx,
13693    )
13694    .await;
13695
13696    // A test that directly calls `show_signature_help`
13697    cx.update_editor(|editor, window, cx| {
13698        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13699    });
13700
13701    let mocked_response = lsp::SignatureHelp {
13702        signatures: vec![lsp::SignatureInformation {
13703            label: "fn sample(param1: u8, param2: u8)".to_string(),
13704            documentation: None,
13705            parameters: Some(vec![
13706                lsp::ParameterInformation {
13707                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13708                    documentation: None,
13709                },
13710                lsp::ParameterInformation {
13711                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13712                    documentation: None,
13713                },
13714            ]),
13715            active_parameter: None,
13716        }],
13717        active_signature: Some(0),
13718        active_parameter: Some(0),
13719    };
13720    handle_signature_help_request(&mut cx, mocked_response).await;
13721
13722    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13723        .await;
13724
13725    cx.editor(|editor, _, _| {
13726        let signature_help_state = editor.signature_help_state.popover().cloned();
13727        assert!(signature_help_state.is_some());
13728        let signature = signature_help_state.unwrap();
13729        assert_eq!(
13730            signature.signatures[signature.current_signature].label,
13731            "fn sample(param1: u8, param2: u8)"
13732        );
13733    });
13734
13735    // When exiting outside from inside the brackets, `signature_help` is closed.
13736    cx.set_state(indoc! {"
13737        fn main() {
13738            sample(ˇ);
13739        }
13740
13741        fn sample(param1: u8, param2: u8) {}
13742    "});
13743
13744    cx.update_editor(|editor, window, cx| {
13745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13746            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13747        });
13748    });
13749
13750    let mocked_response = lsp::SignatureHelp {
13751        signatures: Vec::new(),
13752        active_signature: None,
13753        active_parameter: None,
13754    };
13755    handle_signature_help_request(&mut cx, mocked_response).await;
13756
13757    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13758        .await;
13759
13760    cx.editor(|editor, _, _| {
13761        assert!(!editor.signature_help_state.is_shown());
13762    });
13763
13764    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13765    cx.set_state(indoc! {"
13766        fn main() {
13767            sample(ˇ);
13768        }
13769
13770        fn sample(param1: u8, param2: u8) {}
13771    "});
13772
13773    let mocked_response = lsp::SignatureHelp {
13774        signatures: vec![lsp::SignatureInformation {
13775            label: "fn sample(param1: u8, param2: u8)".to_string(),
13776            documentation: None,
13777            parameters: Some(vec![
13778                lsp::ParameterInformation {
13779                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13780                    documentation: None,
13781                },
13782                lsp::ParameterInformation {
13783                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13784                    documentation: None,
13785                },
13786            ]),
13787            active_parameter: None,
13788        }],
13789        active_signature: Some(0),
13790        active_parameter: Some(0),
13791    };
13792    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13793    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13794        .await;
13795    cx.editor(|editor, _, _| {
13796        assert!(editor.signature_help_state.is_shown());
13797    });
13798
13799    // Restore the popover with more parameter input
13800    cx.set_state(indoc! {"
13801        fn main() {
13802            sample(param1, param2ˇ);
13803        }
13804
13805        fn sample(param1: u8, param2: u8) {}
13806    "});
13807
13808    let mocked_response = lsp::SignatureHelp {
13809        signatures: vec![lsp::SignatureInformation {
13810            label: "fn sample(param1: u8, param2: u8)".to_string(),
13811            documentation: None,
13812            parameters: Some(vec![
13813                lsp::ParameterInformation {
13814                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13815                    documentation: None,
13816                },
13817                lsp::ParameterInformation {
13818                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13819                    documentation: None,
13820                },
13821            ]),
13822            active_parameter: None,
13823        }],
13824        active_signature: Some(0),
13825        active_parameter: Some(1),
13826    };
13827    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13828    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13829        .await;
13830
13831    // When selecting a range, the popover is gone.
13832    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13833    cx.update_editor(|editor, window, cx| {
13834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13835            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13836        })
13837    });
13838    cx.assert_editor_state(indoc! {"
13839        fn main() {
13840            sample(param1, «ˇparam2»);
13841        }
13842
13843        fn sample(param1: u8, param2: u8) {}
13844    "});
13845    cx.editor(|editor, _, _| {
13846        assert!(!editor.signature_help_state.is_shown());
13847    });
13848
13849    // When unselecting again, the popover is back if within the brackets.
13850    cx.update_editor(|editor, window, cx| {
13851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13852            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13853        })
13854    });
13855    cx.assert_editor_state(indoc! {"
13856        fn main() {
13857            sample(param1, ˇparam2);
13858        }
13859
13860        fn sample(param1: u8, param2: u8) {}
13861    "});
13862    handle_signature_help_request(&mut cx, mocked_response).await;
13863    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13864        .await;
13865    cx.editor(|editor, _, _| {
13866        assert!(editor.signature_help_state.is_shown());
13867    });
13868
13869    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13870    cx.update_editor(|editor, window, cx| {
13871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13872            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13873            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13874        })
13875    });
13876    cx.assert_editor_state(indoc! {"
13877        fn main() {
13878            sample(param1, ˇparam2);
13879        }
13880
13881        fn sample(param1: u8, param2: u8) {}
13882    "});
13883
13884    let mocked_response = lsp::SignatureHelp {
13885        signatures: vec![lsp::SignatureInformation {
13886            label: "fn sample(param1: u8, param2: u8)".to_string(),
13887            documentation: None,
13888            parameters: Some(vec![
13889                lsp::ParameterInformation {
13890                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13891                    documentation: None,
13892                },
13893                lsp::ParameterInformation {
13894                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13895                    documentation: None,
13896                },
13897            ]),
13898            active_parameter: None,
13899        }],
13900        active_signature: Some(0),
13901        active_parameter: Some(1),
13902    };
13903    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13904    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13905        .await;
13906    cx.update_editor(|editor, _, cx| {
13907        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13908    });
13909    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13910        .await;
13911    cx.update_editor(|editor, window, cx| {
13912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13913            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13914        })
13915    });
13916    cx.assert_editor_state(indoc! {"
13917        fn main() {
13918            sample(param1, «ˇparam2»);
13919        }
13920
13921        fn sample(param1: u8, param2: u8) {}
13922    "});
13923    cx.update_editor(|editor, window, cx| {
13924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13925            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13926        })
13927    });
13928    cx.assert_editor_state(indoc! {"
13929        fn main() {
13930            sample(param1, ˇparam2);
13931        }
13932
13933        fn sample(param1: u8, param2: u8) {}
13934    "});
13935    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13936        .await;
13937}
13938
13939#[gpui::test]
13940async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13941    init_test(cx, |_| {});
13942
13943    let mut cx = EditorLspTestContext::new_rust(
13944        lsp::ServerCapabilities {
13945            signature_help_provider: Some(lsp::SignatureHelpOptions {
13946                ..Default::default()
13947            }),
13948            ..Default::default()
13949        },
13950        cx,
13951    )
13952    .await;
13953
13954    cx.set_state(indoc! {"
13955        fn main() {
13956            overloadedˇ
13957        }
13958    "});
13959
13960    cx.update_editor(|editor, window, cx| {
13961        editor.handle_input("(", window, cx);
13962        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13963    });
13964
13965    // Mock response with 3 signatures
13966    let mocked_response = lsp::SignatureHelp {
13967        signatures: vec![
13968            lsp::SignatureInformation {
13969                label: "fn overloaded(x: i32)".to_string(),
13970                documentation: None,
13971                parameters: Some(vec![lsp::ParameterInformation {
13972                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13973                    documentation: None,
13974                }]),
13975                active_parameter: None,
13976            },
13977            lsp::SignatureInformation {
13978                label: "fn overloaded(x: i32, y: i32)".to_string(),
13979                documentation: None,
13980                parameters: Some(vec![
13981                    lsp::ParameterInformation {
13982                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13983                        documentation: None,
13984                    },
13985                    lsp::ParameterInformation {
13986                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13987                        documentation: None,
13988                    },
13989                ]),
13990                active_parameter: None,
13991            },
13992            lsp::SignatureInformation {
13993                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13994                documentation: None,
13995                parameters: Some(vec![
13996                    lsp::ParameterInformation {
13997                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13998                        documentation: None,
13999                    },
14000                    lsp::ParameterInformation {
14001                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14002                        documentation: None,
14003                    },
14004                    lsp::ParameterInformation {
14005                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14006                        documentation: None,
14007                    },
14008                ]),
14009                active_parameter: None,
14010            },
14011        ],
14012        active_signature: Some(1),
14013        active_parameter: Some(0),
14014    };
14015    handle_signature_help_request(&mut cx, mocked_response).await;
14016
14017    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14018        .await;
14019
14020    // Verify we have multiple signatures and the right one is selected
14021    cx.editor(|editor, _, _| {
14022        let popover = editor.signature_help_state.popover().cloned().unwrap();
14023        assert_eq!(popover.signatures.len(), 3);
14024        // active_signature was 1, so that should be the current
14025        assert_eq!(popover.current_signature, 1);
14026        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14027        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14028        assert_eq!(
14029            popover.signatures[2].label,
14030            "fn overloaded(x: i32, y: i32, z: i32)"
14031        );
14032    });
14033
14034    // Test navigation functionality
14035    cx.update_editor(|editor, window, cx| {
14036        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14037    });
14038
14039    cx.editor(|editor, _, _| {
14040        let popover = editor.signature_help_state.popover().cloned().unwrap();
14041        assert_eq!(popover.current_signature, 2);
14042    });
14043
14044    // Test wrap around
14045    cx.update_editor(|editor, window, cx| {
14046        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14047    });
14048
14049    cx.editor(|editor, _, _| {
14050        let popover = editor.signature_help_state.popover().cloned().unwrap();
14051        assert_eq!(popover.current_signature, 0);
14052    });
14053
14054    // Test previous navigation
14055    cx.update_editor(|editor, window, cx| {
14056        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14057    });
14058
14059    cx.editor(|editor, _, _| {
14060        let popover = editor.signature_help_state.popover().cloned().unwrap();
14061        assert_eq!(popover.current_signature, 2);
14062    });
14063}
14064
14065#[gpui::test]
14066async fn test_completion_mode(cx: &mut TestAppContext) {
14067    init_test(cx, |_| {});
14068    let mut cx = EditorLspTestContext::new_rust(
14069        lsp::ServerCapabilities {
14070            completion_provider: Some(lsp::CompletionOptions {
14071                resolve_provider: Some(true),
14072                ..Default::default()
14073            }),
14074            ..Default::default()
14075        },
14076        cx,
14077    )
14078    .await;
14079
14080    struct Run {
14081        run_description: &'static str,
14082        initial_state: String,
14083        buffer_marked_text: String,
14084        completion_label: &'static str,
14085        completion_text: &'static str,
14086        expected_with_insert_mode: String,
14087        expected_with_replace_mode: String,
14088        expected_with_replace_subsequence_mode: String,
14089        expected_with_replace_suffix_mode: String,
14090    }
14091
14092    let runs = [
14093        Run {
14094            run_description: "Start of word matches completion text",
14095            initial_state: "before ediˇ after".into(),
14096            buffer_marked_text: "before <edi|> after".into(),
14097            completion_label: "editor",
14098            completion_text: "editor",
14099            expected_with_insert_mode: "before editorˇ after".into(),
14100            expected_with_replace_mode: "before editorˇ after".into(),
14101            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14102            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14103        },
14104        Run {
14105            run_description: "Accept same text at the middle of the word",
14106            initial_state: "before ediˇtor after".into(),
14107            buffer_marked_text: "before <edi|tor> after".into(),
14108            completion_label: "editor",
14109            completion_text: "editor",
14110            expected_with_insert_mode: "before editorˇtor after".into(),
14111            expected_with_replace_mode: "before editorˇ after".into(),
14112            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14113            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14114        },
14115        Run {
14116            run_description: "End of word matches completion text -- cursor at end",
14117            initial_state: "before torˇ after".into(),
14118            buffer_marked_text: "before <tor|> after".into(),
14119            completion_label: "editor",
14120            completion_text: "editor",
14121            expected_with_insert_mode: "before editorˇ after".into(),
14122            expected_with_replace_mode: "before editorˇ after".into(),
14123            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14124            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14125        },
14126        Run {
14127            run_description: "End of word matches completion text -- cursor at start",
14128            initial_state: "before ˇtor after".into(),
14129            buffer_marked_text: "before <|tor> after".into(),
14130            completion_label: "editor",
14131            completion_text: "editor",
14132            expected_with_insert_mode: "before editorˇtor after".into(),
14133            expected_with_replace_mode: "before editorˇ after".into(),
14134            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14135            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14136        },
14137        Run {
14138            run_description: "Prepend text containing whitespace",
14139            initial_state: "pˇfield: bool".into(),
14140            buffer_marked_text: "<p|field>: bool".into(),
14141            completion_label: "pub ",
14142            completion_text: "pub ",
14143            expected_with_insert_mode: "pub ˇfield: bool".into(),
14144            expected_with_replace_mode: "pub ˇ: bool".into(),
14145            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14146            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14147        },
14148        Run {
14149            run_description: "Add element to start of list",
14150            initial_state: "[element_ˇelement_2]".into(),
14151            buffer_marked_text: "[<element_|element_2>]".into(),
14152            completion_label: "element_1",
14153            completion_text: "element_1",
14154            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14155            expected_with_replace_mode: "[element_1ˇ]".into(),
14156            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14157            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14158        },
14159        Run {
14160            run_description: "Add element to start of list -- first and second elements are equal",
14161            initial_state: "[elˇelement]".into(),
14162            buffer_marked_text: "[<el|element>]".into(),
14163            completion_label: "element",
14164            completion_text: "element",
14165            expected_with_insert_mode: "[elementˇelement]".into(),
14166            expected_with_replace_mode: "[elementˇ]".into(),
14167            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14168            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14169        },
14170        Run {
14171            run_description: "Ends with matching suffix",
14172            initial_state: "SubˇError".into(),
14173            buffer_marked_text: "<Sub|Error>".into(),
14174            completion_label: "SubscriptionError",
14175            completion_text: "SubscriptionError",
14176            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14177            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14178            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14179            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14180        },
14181        Run {
14182            run_description: "Suffix is a subsequence -- contiguous",
14183            initial_state: "SubˇErr".into(),
14184            buffer_marked_text: "<Sub|Err>".into(),
14185            completion_label: "SubscriptionError",
14186            completion_text: "SubscriptionError",
14187            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14188            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14189            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14190            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14191        },
14192        Run {
14193            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14194            initial_state: "Suˇscrirr".into(),
14195            buffer_marked_text: "<Su|scrirr>".into(),
14196            completion_label: "SubscriptionError",
14197            completion_text: "SubscriptionError",
14198            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14199            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14200            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14201            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14202        },
14203        Run {
14204            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14205            initial_state: "foo(indˇix)".into(),
14206            buffer_marked_text: "foo(<ind|ix>)".into(),
14207            completion_label: "node_index",
14208            completion_text: "node_index",
14209            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14210            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14211            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14212            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14213        },
14214        Run {
14215            run_description: "Replace range ends before cursor - should extend to cursor",
14216            initial_state: "before editˇo after".into(),
14217            buffer_marked_text: "before <{ed}>it|o after".into(),
14218            completion_label: "editor",
14219            completion_text: "editor",
14220            expected_with_insert_mode: "before editorˇo after".into(),
14221            expected_with_replace_mode: "before editorˇo after".into(),
14222            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14223            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14224        },
14225        Run {
14226            run_description: "Uses label for suffix matching",
14227            initial_state: "before ediˇtor after".into(),
14228            buffer_marked_text: "before <edi|tor> after".into(),
14229            completion_label: "editor",
14230            completion_text: "editor()",
14231            expected_with_insert_mode: "before editor()ˇtor after".into(),
14232            expected_with_replace_mode: "before editor()ˇ after".into(),
14233            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14234            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14235        },
14236        Run {
14237            run_description: "Case insensitive subsequence and suffix matching",
14238            initial_state: "before EDiˇtoR after".into(),
14239            buffer_marked_text: "before <EDi|toR> after".into(),
14240            completion_label: "editor",
14241            completion_text: "editor",
14242            expected_with_insert_mode: "before editorˇtoR after".into(),
14243            expected_with_replace_mode: "before editorˇ after".into(),
14244            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14245            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14246        },
14247    ];
14248
14249    for run in runs {
14250        let run_variations = [
14251            (LspInsertMode::Insert, run.expected_with_insert_mode),
14252            (LspInsertMode::Replace, run.expected_with_replace_mode),
14253            (
14254                LspInsertMode::ReplaceSubsequence,
14255                run.expected_with_replace_subsequence_mode,
14256            ),
14257            (
14258                LspInsertMode::ReplaceSuffix,
14259                run.expected_with_replace_suffix_mode,
14260            ),
14261        ];
14262
14263        for (lsp_insert_mode, expected_text) in run_variations {
14264            eprintln!(
14265                "run = {:?}, mode = {lsp_insert_mode:.?}",
14266                run.run_description,
14267            );
14268
14269            update_test_language_settings(&mut cx, |settings| {
14270                settings.defaults.completions = Some(CompletionSettingsContent {
14271                    lsp_insert_mode: Some(lsp_insert_mode),
14272                    words: Some(WordsCompletionMode::Disabled),
14273                    words_min_length: Some(0),
14274                    ..Default::default()
14275                });
14276            });
14277
14278            cx.set_state(&run.initial_state);
14279            cx.update_editor(|editor, window, cx| {
14280                editor.show_completions(&ShowCompletions, window, cx);
14281            });
14282
14283            let counter = Arc::new(AtomicUsize::new(0));
14284            handle_completion_request_with_insert_and_replace(
14285                &mut cx,
14286                &run.buffer_marked_text,
14287                vec![(run.completion_label, run.completion_text)],
14288                counter.clone(),
14289            )
14290            .await;
14291            cx.condition(|editor, _| editor.context_menu_visible())
14292                .await;
14293            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14294
14295            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14296                editor
14297                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14298                    .unwrap()
14299            });
14300            cx.assert_editor_state(&expected_text);
14301            handle_resolve_completion_request(&mut cx, None).await;
14302            apply_additional_edits.await.unwrap();
14303        }
14304    }
14305}
14306
14307#[gpui::test]
14308async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14309    init_test(cx, |_| {});
14310    let mut cx = EditorLspTestContext::new_rust(
14311        lsp::ServerCapabilities {
14312            completion_provider: Some(lsp::CompletionOptions {
14313                resolve_provider: Some(true),
14314                ..Default::default()
14315            }),
14316            ..Default::default()
14317        },
14318        cx,
14319    )
14320    .await;
14321
14322    let initial_state = "SubˇError";
14323    let buffer_marked_text = "<Sub|Error>";
14324    let completion_text = "SubscriptionError";
14325    let expected_with_insert_mode = "SubscriptionErrorˇError";
14326    let expected_with_replace_mode = "SubscriptionErrorˇ";
14327
14328    update_test_language_settings(&mut cx, |settings| {
14329        settings.defaults.completions = Some(CompletionSettingsContent {
14330            words: Some(WordsCompletionMode::Disabled),
14331            words_min_length: Some(0),
14332            // set the opposite here to ensure that the action is overriding the default behavior
14333            lsp_insert_mode: Some(LspInsertMode::Insert),
14334            ..Default::default()
14335        });
14336    });
14337
14338    cx.set_state(initial_state);
14339    cx.update_editor(|editor, window, cx| {
14340        editor.show_completions(&ShowCompletions, window, cx);
14341    });
14342
14343    let counter = Arc::new(AtomicUsize::new(0));
14344    handle_completion_request_with_insert_and_replace(
14345        &mut cx,
14346        buffer_marked_text,
14347        vec![(completion_text, completion_text)],
14348        counter.clone(),
14349    )
14350    .await;
14351    cx.condition(|editor, _| editor.context_menu_visible())
14352        .await;
14353    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14354
14355    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14356        editor
14357            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14358            .unwrap()
14359    });
14360    cx.assert_editor_state(expected_with_replace_mode);
14361    handle_resolve_completion_request(&mut cx, None).await;
14362    apply_additional_edits.await.unwrap();
14363
14364    update_test_language_settings(&mut cx, |settings| {
14365        settings.defaults.completions = Some(CompletionSettingsContent {
14366            words: Some(WordsCompletionMode::Disabled),
14367            words_min_length: Some(0),
14368            // set the opposite here to ensure that the action is overriding the default behavior
14369            lsp_insert_mode: Some(LspInsertMode::Replace),
14370            ..Default::default()
14371        });
14372    });
14373
14374    cx.set_state(initial_state);
14375    cx.update_editor(|editor, window, cx| {
14376        editor.show_completions(&ShowCompletions, window, cx);
14377    });
14378    handle_completion_request_with_insert_and_replace(
14379        &mut cx,
14380        buffer_marked_text,
14381        vec![(completion_text, completion_text)],
14382        counter.clone(),
14383    )
14384    .await;
14385    cx.condition(|editor, _| editor.context_menu_visible())
14386        .await;
14387    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14388
14389    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14390        editor
14391            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14392            .unwrap()
14393    });
14394    cx.assert_editor_state(expected_with_insert_mode);
14395    handle_resolve_completion_request(&mut cx, None).await;
14396    apply_additional_edits.await.unwrap();
14397}
14398
14399#[gpui::test]
14400async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14401    init_test(cx, |_| {});
14402    let mut cx = EditorLspTestContext::new_rust(
14403        lsp::ServerCapabilities {
14404            completion_provider: Some(lsp::CompletionOptions {
14405                resolve_provider: Some(true),
14406                ..Default::default()
14407            }),
14408            ..Default::default()
14409        },
14410        cx,
14411    )
14412    .await;
14413
14414    // scenario: surrounding text matches completion text
14415    let completion_text = "to_offset";
14416    let initial_state = indoc! {"
14417        1. buf.to_offˇsuffix
14418        2. buf.to_offˇsuf
14419        3. buf.to_offˇfix
14420        4. buf.to_offˇ
14421        5. into_offˇensive
14422        6. ˇsuffix
14423        7. let ˇ //
14424        8. aaˇzz
14425        9. buf.to_off«zzzzzˇ»suffix
14426        10. buf.«ˇzzzzz»suffix
14427        11. to_off«ˇzzzzz»
14428
14429        buf.to_offˇsuffix  // newest cursor
14430    "};
14431    let completion_marked_buffer = indoc! {"
14432        1. buf.to_offsuffix
14433        2. buf.to_offsuf
14434        3. buf.to_offfix
14435        4. buf.to_off
14436        5. into_offensive
14437        6. suffix
14438        7. let  //
14439        8. aazz
14440        9. buf.to_offzzzzzsuffix
14441        10. buf.zzzzzsuffix
14442        11. to_offzzzzz
14443
14444        buf.<to_off|suffix>  // newest cursor
14445    "};
14446    let expected = indoc! {"
14447        1. buf.to_offsetˇ
14448        2. buf.to_offsetˇsuf
14449        3. buf.to_offsetˇfix
14450        4. buf.to_offsetˇ
14451        5. into_offsetˇensive
14452        6. to_offsetˇsuffix
14453        7. let to_offsetˇ //
14454        8. aato_offsetˇzz
14455        9. buf.to_offsetˇ
14456        10. buf.to_offsetˇsuffix
14457        11. to_offsetˇ
14458
14459        buf.to_offsetˇ  // newest cursor
14460    "};
14461    cx.set_state(initial_state);
14462    cx.update_editor(|editor, window, cx| {
14463        editor.show_completions(&ShowCompletions, window, cx);
14464    });
14465    handle_completion_request_with_insert_and_replace(
14466        &mut cx,
14467        completion_marked_buffer,
14468        vec![(completion_text, completion_text)],
14469        Arc::new(AtomicUsize::new(0)),
14470    )
14471    .await;
14472    cx.condition(|editor, _| editor.context_menu_visible())
14473        .await;
14474    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14475        editor
14476            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14477            .unwrap()
14478    });
14479    cx.assert_editor_state(expected);
14480    handle_resolve_completion_request(&mut cx, None).await;
14481    apply_additional_edits.await.unwrap();
14482
14483    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14484    let completion_text = "foo_and_bar";
14485    let initial_state = indoc! {"
14486        1. ooanbˇ
14487        2. zooanbˇ
14488        3. ooanbˇz
14489        4. zooanbˇz
14490        5. ooanˇ
14491        6. oanbˇ
14492
14493        ooanbˇ
14494    "};
14495    let completion_marked_buffer = indoc! {"
14496        1. ooanb
14497        2. zooanb
14498        3. ooanbz
14499        4. zooanbz
14500        5. ooan
14501        6. oanb
14502
14503        <ooanb|>
14504    "};
14505    let expected = indoc! {"
14506        1. foo_and_barˇ
14507        2. zfoo_and_barˇ
14508        3. foo_and_barˇz
14509        4. zfoo_and_barˇz
14510        5. ooanfoo_and_barˇ
14511        6. oanbfoo_and_barˇ
14512
14513        foo_and_barˇ
14514    "};
14515    cx.set_state(initial_state);
14516    cx.update_editor(|editor, window, cx| {
14517        editor.show_completions(&ShowCompletions, window, cx);
14518    });
14519    handle_completion_request_with_insert_and_replace(
14520        &mut cx,
14521        completion_marked_buffer,
14522        vec![(completion_text, completion_text)],
14523        Arc::new(AtomicUsize::new(0)),
14524    )
14525    .await;
14526    cx.condition(|editor, _| editor.context_menu_visible())
14527        .await;
14528    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14529        editor
14530            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14531            .unwrap()
14532    });
14533    cx.assert_editor_state(expected);
14534    handle_resolve_completion_request(&mut cx, None).await;
14535    apply_additional_edits.await.unwrap();
14536
14537    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14538    // (expects the same as if it was inserted at the end)
14539    let completion_text = "foo_and_bar";
14540    let initial_state = indoc! {"
14541        1. ooˇanb
14542        2. zooˇanb
14543        3. ooˇanbz
14544        4. zooˇanbz
14545
14546        ooˇanb
14547    "};
14548    let completion_marked_buffer = indoc! {"
14549        1. ooanb
14550        2. zooanb
14551        3. ooanbz
14552        4. zooanbz
14553
14554        <oo|anb>
14555    "};
14556    let expected = indoc! {"
14557        1. foo_and_barˇ
14558        2. zfoo_and_barˇ
14559        3. foo_and_barˇz
14560        4. zfoo_and_barˇz
14561
14562        foo_and_barˇ
14563    "};
14564    cx.set_state(initial_state);
14565    cx.update_editor(|editor, window, cx| {
14566        editor.show_completions(&ShowCompletions, window, cx);
14567    });
14568    handle_completion_request_with_insert_and_replace(
14569        &mut cx,
14570        completion_marked_buffer,
14571        vec![(completion_text, completion_text)],
14572        Arc::new(AtomicUsize::new(0)),
14573    )
14574    .await;
14575    cx.condition(|editor, _| editor.context_menu_visible())
14576        .await;
14577    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14578        editor
14579            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14580            .unwrap()
14581    });
14582    cx.assert_editor_state(expected);
14583    handle_resolve_completion_request(&mut cx, None).await;
14584    apply_additional_edits.await.unwrap();
14585}
14586
14587// This used to crash
14588#[gpui::test]
14589async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14590    init_test(cx, |_| {});
14591
14592    let buffer_text = indoc! {"
14593        fn main() {
14594            10.satu;
14595
14596            //
14597            // separate cursors so they open in different excerpts (manually reproducible)
14598            //
14599
14600            10.satu20;
14601        }
14602    "};
14603    let multibuffer_text_with_selections = indoc! {"
14604        fn main() {
14605            10.satuˇ;
14606
14607            //
14608
14609            //
14610
14611            10.satuˇ20;
14612        }
14613    "};
14614    let expected_multibuffer = indoc! {"
14615        fn main() {
14616            10.saturating_sub()ˇ;
14617
14618            //
14619
14620            //
14621
14622            10.saturating_sub()ˇ;
14623        }
14624    "};
14625
14626    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14627    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14628
14629    let fs = FakeFs::new(cx.executor());
14630    fs.insert_tree(
14631        path!("/a"),
14632        json!({
14633            "main.rs": buffer_text,
14634        }),
14635    )
14636    .await;
14637
14638    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14639    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14640    language_registry.add(rust_lang());
14641    let mut fake_servers = language_registry.register_fake_lsp(
14642        "Rust",
14643        FakeLspAdapter {
14644            capabilities: lsp::ServerCapabilities {
14645                completion_provider: Some(lsp::CompletionOptions {
14646                    resolve_provider: None,
14647                    ..lsp::CompletionOptions::default()
14648                }),
14649                ..lsp::ServerCapabilities::default()
14650            },
14651            ..FakeLspAdapter::default()
14652        },
14653    );
14654    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14655    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14656    let buffer = project
14657        .update(cx, |project, cx| {
14658            project.open_local_buffer(path!("/a/main.rs"), cx)
14659        })
14660        .await
14661        .unwrap();
14662
14663    let multi_buffer = cx.new(|cx| {
14664        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14665        multi_buffer.push_excerpts(
14666            buffer.clone(),
14667            [ExcerptRange::new(0..first_excerpt_end)],
14668            cx,
14669        );
14670        multi_buffer.push_excerpts(
14671            buffer.clone(),
14672            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14673            cx,
14674        );
14675        multi_buffer
14676    });
14677
14678    let editor = workspace
14679        .update(cx, |_, window, cx| {
14680            cx.new(|cx| {
14681                Editor::new(
14682                    EditorMode::Full {
14683                        scale_ui_elements_with_buffer_font_size: false,
14684                        show_active_line_background: false,
14685                        sizing_behavior: SizingBehavior::Default,
14686                    },
14687                    multi_buffer.clone(),
14688                    Some(project.clone()),
14689                    window,
14690                    cx,
14691                )
14692            })
14693        })
14694        .unwrap();
14695
14696    let pane = workspace
14697        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14698        .unwrap();
14699    pane.update_in(cx, |pane, window, cx| {
14700        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14701    });
14702
14703    let fake_server = fake_servers.next().await.unwrap();
14704
14705    editor.update_in(cx, |editor, window, cx| {
14706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14707            s.select_ranges([
14708                Point::new(1, 11)..Point::new(1, 11),
14709                Point::new(7, 11)..Point::new(7, 11),
14710            ])
14711        });
14712
14713        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14714    });
14715
14716    editor.update_in(cx, |editor, window, cx| {
14717        editor.show_completions(&ShowCompletions, window, cx);
14718    });
14719
14720    fake_server
14721        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14722            let completion_item = lsp::CompletionItem {
14723                label: "saturating_sub()".into(),
14724                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14725                    lsp::InsertReplaceEdit {
14726                        new_text: "saturating_sub()".to_owned(),
14727                        insert: lsp::Range::new(
14728                            lsp::Position::new(7, 7),
14729                            lsp::Position::new(7, 11),
14730                        ),
14731                        replace: lsp::Range::new(
14732                            lsp::Position::new(7, 7),
14733                            lsp::Position::new(7, 13),
14734                        ),
14735                    },
14736                )),
14737                ..lsp::CompletionItem::default()
14738            };
14739
14740            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14741        })
14742        .next()
14743        .await
14744        .unwrap();
14745
14746    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14747        .await;
14748
14749    editor
14750        .update_in(cx, |editor, window, cx| {
14751            editor
14752                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14753                .unwrap()
14754        })
14755        .await
14756        .unwrap();
14757
14758    editor.update(cx, |editor, cx| {
14759        assert_text_with_selections(editor, expected_multibuffer, cx);
14760    })
14761}
14762
14763#[gpui::test]
14764async fn test_completion(cx: &mut TestAppContext) {
14765    init_test(cx, |_| {});
14766
14767    let mut cx = EditorLspTestContext::new_rust(
14768        lsp::ServerCapabilities {
14769            completion_provider: Some(lsp::CompletionOptions {
14770                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14771                resolve_provider: Some(true),
14772                ..Default::default()
14773            }),
14774            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14775            ..Default::default()
14776        },
14777        cx,
14778    )
14779    .await;
14780    let counter = Arc::new(AtomicUsize::new(0));
14781
14782    cx.set_state(indoc! {"
14783        oneˇ
14784        two
14785        three
14786    "});
14787    cx.simulate_keystroke(".");
14788    handle_completion_request(
14789        indoc! {"
14790            one.|<>
14791            two
14792            three
14793        "},
14794        vec!["first_completion", "second_completion"],
14795        true,
14796        counter.clone(),
14797        &mut cx,
14798    )
14799    .await;
14800    cx.condition(|editor, _| editor.context_menu_visible())
14801        .await;
14802    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14803
14804    let _handler = handle_signature_help_request(
14805        &mut cx,
14806        lsp::SignatureHelp {
14807            signatures: vec![lsp::SignatureInformation {
14808                label: "test signature".to_string(),
14809                documentation: None,
14810                parameters: Some(vec![lsp::ParameterInformation {
14811                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14812                    documentation: None,
14813                }]),
14814                active_parameter: None,
14815            }],
14816            active_signature: None,
14817            active_parameter: None,
14818        },
14819    );
14820    cx.update_editor(|editor, window, cx| {
14821        assert!(
14822            !editor.signature_help_state.is_shown(),
14823            "No signature help was called for"
14824        );
14825        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14826    });
14827    cx.run_until_parked();
14828    cx.update_editor(|editor, _, _| {
14829        assert!(
14830            !editor.signature_help_state.is_shown(),
14831            "No signature help should be shown when completions menu is open"
14832        );
14833    });
14834
14835    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14836        editor.context_menu_next(&Default::default(), window, cx);
14837        editor
14838            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14839            .unwrap()
14840    });
14841    cx.assert_editor_state(indoc! {"
14842        one.second_completionˇ
14843        two
14844        three
14845    "});
14846
14847    handle_resolve_completion_request(
14848        &mut cx,
14849        Some(vec![
14850            (
14851                //This overlaps with the primary completion edit which is
14852                //misbehavior from the LSP spec, test that we filter it out
14853                indoc! {"
14854                    one.second_ˇcompletion
14855                    two
14856                    threeˇ
14857                "},
14858                "overlapping additional edit",
14859            ),
14860            (
14861                indoc! {"
14862                    one.second_completion
14863                    two
14864                    threeˇ
14865                "},
14866                "\nadditional edit",
14867            ),
14868        ]),
14869    )
14870    .await;
14871    apply_additional_edits.await.unwrap();
14872    cx.assert_editor_state(indoc! {"
14873        one.second_completionˇ
14874        two
14875        three
14876        additional edit
14877    "});
14878
14879    cx.set_state(indoc! {"
14880        one.second_completion
14881        twoˇ
14882        threeˇ
14883        additional edit
14884    "});
14885    cx.simulate_keystroke(" ");
14886    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14887    cx.simulate_keystroke("s");
14888    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14889
14890    cx.assert_editor_state(indoc! {"
14891        one.second_completion
14892        two sˇ
14893        three sˇ
14894        additional edit
14895    "});
14896    handle_completion_request(
14897        indoc! {"
14898            one.second_completion
14899            two s
14900            three <s|>
14901            additional edit
14902        "},
14903        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14904        true,
14905        counter.clone(),
14906        &mut cx,
14907    )
14908    .await;
14909    cx.condition(|editor, _| editor.context_menu_visible())
14910        .await;
14911    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14912
14913    cx.simulate_keystroke("i");
14914
14915    handle_completion_request(
14916        indoc! {"
14917            one.second_completion
14918            two si
14919            three <si|>
14920            additional edit
14921        "},
14922        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14923        true,
14924        counter.clone(),
14925        &mut cx,
14926    )
14927    .await;
14928    cx.condition(|editor, _| editor.context_menu_visible())
14929        .await;
14930    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14931
14932    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14933        editor
14934            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14935            .unwrap()
14936    });
14937    cx.assert_editor_state(indoc! {"
14938        one.second_completion
14939        two sixth_completionˇ
14940        three sixth_completionˇ
14941        additional edit
14942    "});
14943
14944    apply_additional_edits.await.unwrap();
14945
14946    update_test_language_settings(&mut cx, |settings| {
14947        settings.defaults.show_completions_on_input = Some(false);
14948    });
14949    cx.set_state("editorˇ");
14950    cx.simulate_keystroke(".");
14951    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14952    cx.simulate_keystrokes("c l o");
14953    cx.assert_editor_state("editor.cloˇ");
14954    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14955    cx.update_editor(|editor, window, cx| {
14956        editor.show_completions(&ShowCompletions, window, cx);
14957    });
14958    handle_completion_request(
14959        "editor.<clo|>",
14960        vec!["close", "clobber"],
14961        true,
14962        counter.clone(),
14963        &mut cx,
14964    )
14965    .await;
14966    cx.condition(|editor, _| editor.context_menu_visible())
14967        .await;
14968    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14969
14970    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14971        editor
14972            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14973            .unwrap()
14974    });
14975    cx.assert_editor_state("editor.clobberˇ");
14976    handle_resolve_completion_request(&mut cx, None).await;
14977    apply_additional_edits.await.unwrap();
14978}
14979
14980#[gpui::test]
14981async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14982    init_test(cx, |_| {});
14983
14984    let fs = FakeFs::new(cx.executor());
14985    fs.insert_tree(
14986        path!("/a"),
14987        json!({
14988            "main.rs": "",
14989        }),
14990    )
14991    .await;
14992
14993    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14994    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14995    language_registry.add(rust_lang());
14996    let command_calls = Arc::new(AtomicUsize::new(0));
14997    let registered_command = "_the/command";
14998
14999    let closure_command_calls = command_calls.clone();
15000    let mut fake_servers = language_registry.register_fake_lsp(
15001        "Rust",
15002        FakeLspAdapter {
15003            capabilities: lsp::ServerCapabilities {
15004                completion_provider: Some(lsp::CompletionOptions {
15005                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15006                    ..lsp::CompletionOptions::default()
15007                }),
15008                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15009                    commands: vec![registered_command.to_owned()],
15010                    ..lsp::ExecuteCommandOptions::default()
15011                }),
15012                ..lsp::ServerCapabilities::default()
15013            },
15014            initializer: Some(Box::new(move |fake_server| {
15015                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15016                    move |params, _| async move {
15017                        Ok(Some(lsp::CompletionResponse::Array(vec![
15018                            lsp::CompletionItem {
15019                                label: "registered_command".to_owned(),
15020                                text_edit: gen_text_edit(&params, ""),
15021                                command: Some(lsp::Command {
15022                                    title: registered_command.to_owned(),
15023                                    command: "_the/command".to_owned(),
15024                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15025                                }),
15026                                ..lsp::CompletionItem::default()
15027                            },
15028                            lsp::CompletionItem {
15029                                label: "unregistered_command".to_owned(),
15030                                text_edit: gen_text_edit(&params, ""),
15031                                command: Some(lsp::Command {
15032                                    title: "????????????".to_owned(),
15033                                    command: "????????????".to_owned(),
15034                                    arguments: Some(vec![serde_json::Value::Null]),
15035                                }),
15036                                ..lsp::CompletionItem::default()
15037                            },
15038                        ])))
15039                    },
15040                );
15041                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15042                    let command_calls = closure_command_calls.clone();
15043                    move |params, _| {
15044                        assert_eq!(params.command, registered_command);
15045                        let command_calls = command_calls.clone();
15046                        async move {
15047                            command_calls.fetch_add(1, atomic::Ordering::Release);
15048                            Ok(Some(json!(null)))
15049                        }
15050                    }
15051                });
15052            })),
15053            ..FakeLspAdapter::default()
15054        },
15055    );
15056    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15057    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15058    let editor = workspace
15059        .update(cx, |workspace, window, cx| {
15060            workspace.open_abs_path(
15061                PathBuf::from(path!("/a/main.rs")),
15062                OpenOptions::default(),
15063                window,
15064                cx,
15065            )
15066        })
15067        .unwrap()
15068        .await
15069        .unwrap()
15070        .downcast::<Editor>()
15071        .unwrap();
15072    let _fake_server = fake_servers.next().await.unwrap();
15073
15074    editor.update_in(cx, |editor, window, cx| {
15075        cx.focus_self(window);
15076        editor.move_to_end(&MoveToEnd, window, cx);
15077        editor.handle_input(".", window, cx);
15078    });
15079    cx.run_until_parked();
15080    editor.update(cx, |editor, _| {
15081        assert!(editor.context_menu_visible());
15082        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15083        {
15084            let completion_labels = menu
15085                .completions
15086                .borrow()
15087                .iter()
15088                .map(|c| c.label.text.clone())
15089                .collect::<Vec<_>>();
15090            assert_eq!(
15091                completion_labels,
15092                &["registered_command", "unregistered_command",],
15093            );
15094        } else {
15095            panic!("expected completion menu to be open");
15096        }
15097    });
15098
15099    editor
15100        .update_in(cx, |editor, window, cx| {
15101            editor
15102                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15103                .unwrap()
15104        })
15105        .await
15106        .unwrap();
15107    cx.run_until_parked();
15108    assert_eq!(
15109        command_calls.load(atomic::Ordering::Acquire),
15110        1,
15111        "For completion with a registered command, Zed should send a command execution request",
15112    );
15113
15114    editor.update_in(cx, |editor, window, cx| {
15115        cx.focus_self(window);
15116        editor.handle_input(".", window, cx);
15117    });
15118    cx.run_until_parked();
15119    editor.update(cx, |editor, _| {
15120        assert!(editor.context_menu_visible());
15121        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15122        {
15123            let completion_labels = menu
15124                .completions
15125                .borrow()
15126                .iter()
15127                .map(|c| c.label.text.clone())
15128                .collect::<Vec<_>>();
15129            assert_eq!(
15130                completion_labels,
15131                &["registered_command", "unregistered_command",],
15132            );
15133        } else {
15134            panic!("expected completion menu to be open");
15135        }
15136    });
15137    editor
15138        .update_in(cx, |editor, window, cx| {
15139            editor.context_menu_next(&Default::default(), window, cx);
15140            editor
15141                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15142                .unwrap()
15143        })
15144        .await
15145        .unwrap();
15146    cx.run_until_parked();
15147    assert_eq!(
15148        command_calls.load(atomic::Ordering::Acquire),
15149        1,
15150        "For completion with an unregistered command, Zed should not send a command execution request",
15151    );
15152}
15153
15154#[gpui::test]
15155async fn test_completion_reuse(cx: &mut TestAppContext) {
15156    init_test(cx, |_| {});
15157
15158    let mut cx = EditorLspTestContext::new_rust(
15159        lsp::ServerCapabilities {
15160            completion_provider: Some(lsp::CompletionOptions {
15161                trigger_characters: Some(vec![".".to_string()]),
15162                ..Default::default()
15163            }),
15164            ..Default::default()
15165        },
15166        cx,
15167    )
15168    .await;
15169
15170    let counter = Arc::new(AtomicUsize::new(0));
15171    cx.set_state("objˇ");
15172    cx.simulate_keystroke(".");
15173
15174    // Initial completion request returns complete results
15175    let is_incomplete = false;
15176    handle_completion_request(
15177        "obj.|<>",
15178        vec!["a", "ab", "abc"],
15179        is_incomplete,
15180        counter.clone(),
15181        &mut cx,
15182    )
15183    .await;
15184    cx.run_until_parked();
15185    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15186    cx.assert_editor_state("obj.ˇ");
15187    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15188
15189    // Type "a" - filters existing completions
15190    cx.simulate_keystroke("a");
15191    cx.run_until_parked();
15192    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15193    cx.assert_editor_state("obj.aˇ");
15194    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15195
15196    // Type "b" - filters existing completions
15197    cx.simulate_keystroke("b");
15198    cx.run_until_parked();
15199    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15200    cx.assert_editor_state("obj.abˇ");
15201    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15202
15203    // Type "c" - filters existing completions
15204    cx.simulate_keystroke("c");
15205    cx.run_until_parked();
15206    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15207    cx.assert_editor_state("obj.abcˇ");
15208    check_displayed_completions(vec!["abc"], &mut cx);
15209
15210    // Backspace to delete "c" - filters existing completions
15211    cx.update_editor(|editor, window, cx| {
15212        editor.backspace(&Backspace, window, cx);
15213    });
15214    cx.run_until_parked();
15215    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15216    cx.assert_editor_state("obj.abˇ");
15217    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15218
15219    // Moving cursor to the left dismisses menu.
15220    cx.update_editor(|editor, window, cx| {
15221        editor.move_left(&MoveLeft, window, cx);
15222    });
15223    cx.run_until_parked();
15224    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15225    cx.assert_editor_state("obj.aˇb");
15226    cx.update_editor(|editor, _, _| {
15227        assert_eq!(editor.context_menu_visible(), false);
15228    });
15229
15230    // Type "b" - new request
15231    cx.simulate_keystroke("b");
15232    let is_incomplete = false;
15233    handle_completion_request(
15234        "obj.<ab|>a",
15235        vec!["ab", "abc"],
15236        is_incomplete,
15237        counter.clone(),
15238        &mut cx,
15239    )
15240    .await;
15241    cx.run_until_parked();
15242    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15243    cx.assert_editor_state("obj.abˇb");
15244    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15245
15246    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15247    cx.update_editor(|editor, window, cx| {
15248        editor.backspace(&Backspace, window, cx);
15249    });
15250    let is_incomplete = false;
15251    handle_completion_request(
15252        "obj.<a|>b",
15253        vec!["a", "ab", "abc"],
15254        is_incomplete,
15255        counter.clone(),
15256        &mut cx,
15257    )
15258    .await;
15259    cx.run_until_parked();
15260    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15261    cx.assert_editor_state("obj.aˇb");
15262    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15263
15264    // Backspace to delete "a" - dismisses menu.
15265    cx.update_editor(|editor, window, cx| {
15266        editor.backspace(&Backspace, window, cx);
15267    });
15268    cx.run_until_parked();
15269    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15270    cx.assert_editor_state("obj.ˇb");
15271    cx.update_editor(|editor, _, _| {
15272        assert_eq!(editor.context_menu_visible(), false);
15273    });
15274}
15275
15276#[gpui::test]
15277async fn test_word_completion(cx: &mut TestAppContext) {
15278    let lsp_fetch_timeout_ms = 10;
15279    init_test(cx, |language_settings| {
15280        language_settings.defaults.completions = Some(CompletionSettingsContent {
15281            words_min_length: Some(0),
15282            lsp_fetch_timeout_ms: Some(10),
15283            lsp_insert_mode: Some(LspInsertMode::Insert),
15284            ..Default::default()
15285        });
15286    });
15287
15288    let mut cx = EditorLspTestContext::new_rust(
15289        lsp::ServerCapabilities {
15290            completion_provider: Some(lsp::CompletionOptions {
15291                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15292                ..lsp::CompletionOptions::default()
15293            }),
15294            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15295            ..lsp::ServerCapabilities::default()
15296        },
15297        cx,
15298    )
15299    .await;
15300
15301    let throttle_completions = Arc::new(AtomicBool::new(false));
15302
15303    let lsp_throttle_completions = throttle_completions.clone();
15304    let _completion_requests_handler =
15305        cx.lsp
15306            .server
15307            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15308                let lsp_throttle_completions = lsp_throttle_completions.clone();
15309                let cx = cx.clone();
15310                async move {
15311                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15312                        cx.background_executor()
15313                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15314                            .await;
15315                    }
15316                    Ok(Some(lsp::CompletionResponse::Array(vec![
15317                        lsp::CompletionItem {
15318                            label: "first".into(),
15319                            ..lsp::CompletionItem::default()
15320                        },
15321                        lsp::CompletionItem {
15322                            label: "last".into(),
15323                            ..lsp::CompletionItem::default()
15324                        },
15325                    ])))
15326                }
15327            });
15328
15329    cx.set_state(indoc! {"
15330        oneˇ
15331        two
15332        three
15333    "});
15334    cx.simulate_keystroke(".");
15335    cx.executor().run_until_parked();
15336    cx.condition(|editor, _| editor.context_menu_visible())
15337        .await;
15338    cx.update_editor(|editor, window, cx| {
15339        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15340        {
15341            assert_eq!(
15342                completion_menu_entries(menu),
15343                &["first", "last"],
15344                "When LSP server is fast to reply, no fallback word completions are used"
15345            );
15346        } else {
15347            panic!("expected completion menu to be open");
15348        }
15349        editor.cancel(&Cancel, window, cx);
15350    });
15351    cx.executor().run_until_parked();
15352    cx.condition(|editor, _| !editor.context_menu_visible())
15353        .await;
15354
15355    throttle_completions.store(true, atomic::Ordering::Release);
15356    cx.simulate_keystroke(".");
15357    cx.executor()
15358        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15359    cx.executor().run_until_parked();
15360    cx.condition(|editor, _| editor.context_menu_visible())
15361        .await;
15362    cx.update_editor(|editor, _, _| {
15363        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15364        {
15365            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15366                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15367        } else {
15368            panic!("expected completion menu to be open");
15369        }
15370    });
15371}
15372
15373#[gpui::test]
15374async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15375    init_test(cx, |language_settings| {
15376        language_settings.defaults.completions = Some(CompletionSettingsContent {
15377            words: Some(WordsCompletionMode::Enabled),
15378            words_min_length: Some(0),
15379            lsp_insert_mode: Some(LspInsertMode::Insert),
15380            ..Default::default()
15381        });
15382    });
15383
15384    let mut cx = EditorLspTestContext::new_rust(
15385        lsp::ServerCapabilities {
15386            completion_provider: Some(lsp::CompletionOptions {
15387                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15388                ..lsp::CompletionOptions::default()
15389            }),
15390            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15391            ..lsp::ServerCapabilities::default()
15392        },
15393        cx,
15394    )
15395    .await;
15396
15397    let _completion_requests_handler =
15398        cx.lsp
15399            .server
15400            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15401                Ok(Some(lsp::CompletionResponse::Array(vec![
15402                    lsp::CompletionItem {
15403                        label: "first".into(),
15404                        ..lsp::CompletionItem::default()
15405                    },
15406                    lsp::CompletionItem {
15407                        label: "last".into(),
15408                        ..lsp::CompletionItem::default()
15409                    },
15410                ])))
15411            });
15412
15413    cx.set_state(indoc! {"ˇ
15414        first
15415        last
15416        second
15417    "});
15418    cx.simulate_keystroke(".");
15419    cx.executor().run_until_parked();
15420    cx.condition(|editor, _| editor.context_menu_visible())
15421        .await;
15422    cx.update_editor(|editor, _, _| {
15423        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15424        {
15425            assert_eq!(
15426                completion_menu_entries(menu),
15427                &["first", "last", "second"],
15428                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15429            );
15430        } else {
15431            panic!("expected completion menu to be open");
15432        }
15433    });
15434}
15435
15436#[gpui::test]
15437async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15438    init_test(cx, |language_settings| {
15439        language_settings.defaults.completions = Some(CompletionSettingsContent {
15440            words: Some(WordsCompletionMode::Disabled),
15441            words_min_length: Some(0),
15442            lsp_insert_mode: Some(LspInsertMode::Insert),
15443            ..Default::default()
15444        });
15445    });
15446
15447    let mut cx = EditorLspTestContext::new_rust(
15448        lsp::ServerCapabilities {
15449            completion_provider: Some(lsp::CompletionOptions {
15450                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15451                ..lsp::CompletionOptions::default()
15452            }),
15453            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15454            ..lsp::ServerCapabilities::default()
15455        },
15456        cx,
15457    )
15458    .await;
15459
15460    let _completion_requests_handler =
15461        cx.lsp
15462            .server
15463            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15464                panic!("LSP completions should not be queried when dealing with word completions")
15465            });
15466
15467    cx.set_state(indoc! {"ˇ
15468        first
15469        last
15470        second
15471    "});
15472    cx.update_editor(|editor, window, cx| {
15473        editor.show_word_completions(&ShowWordCompletions, window, cx);
15474    });
15475    cx.executor().run_until_parked();
15476    cx.condition(|editor, _| editor.context_menu_visible())
15477        .await;
15478    cx.update_editor(|editor, _, _| {
15479        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15480        {
15481            assert_eq!(
15482                completion_menu_entries(menu),
15483                &["first", "last", "second"],
15484                "`ShowWordCompletions` action should show word completions"
15485            );
15486        } else {
15487            panic!("expected completion menu to be open");
15488        }
15489    });
15490
15491    cx.simulate_keystroke("l");
15492    cx.executor().run_until_parked();
15493    cx.condition(|editor, _| editor.context_menu_visible())
15494        .await;
15495    cx.update_editor(|editor, _, _| {
15496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15497        {
15498            assert_eq!(
15499                completion_menu_entries(menu),
15500                &["last"],
15501                "After showing word completions, further editing should filter them and not query the LSP"
15502            );
15503        } else {
15504            panic!("expected completion menu to be open");
15505        }
15506    });
15507}
15508
15509#[gpui::test]
15510async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15511    init_test(cx, |language_settings| {
15512        language_settings.defaults.completions = Some(CompletionSettingsContent {
15513            words_min_length: Some(0),
15514            lsp: Some(false),
15515            lsp_insert_mode: Some(LspInsertMode::Insert),
15516            ..Default::default()
15517        });
15518    });
15519
15520    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15521
15522    cx.set_state(indoc! {"ˇ
15523        0_usize
15524        let
15525        33
15526        4.5f32
15527    "});
15528    cx.update_editor(|editor, window, cx| {
15529        editor.show_completions(&ShowCompletions, window, cx);
15530    });
15531    cx.executor().run_until_parked();
15532    cx.condition(|editor, _| editor.context_menu_visible())
15533        .await;
15534    cx.update_editor(|editor, window, cx| {
15535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15536        {
15537            assert_eq!(
15538                completion_menu_entries(menu),
15539                &["let"],
15540                "With no digits in the completion query, no digits should be in the word completions"
15541            );
15542        } else {
15543            panic!("expected completion menu to be open");
15544        }
15545        editor.cancel(&Cancel, window, cx);
15546    });
15547
15548    cx.set_state(indoc! {"15549        0_usize
15550        let
15551        3
15552        33.35f32
15553    "});
15554    cx.update_editor(|editor, window, cx| {
15555        editor.show_completions(&ShowCompletions, window, cx);
15556    });
15557    cx.executor().run_until_parked();
15558    cx.condition(|editor, _| editor.context_menu_visible())
15559        .await;
15560    cx.update_editor(|editor, _, _| {
15561        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15562        {
15563            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15564                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15565        } else {
15566            panic!("expected completion menu to be open");
15567        }
15568    });
15569}
15570
15571#[gpui::test]
15572async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15573    init_test(cx, |language_settings| {
15574        language_settings.defaults.completions = Some(CompletionSettingsContent {
15575            words: Some(WordsCompletionMode::Enabled),
15576            words_min_length: Some(3),
15577            lsp_insert_mode: Some(LspInsertMode::Insert),
15578            ..Default::default()
15579        });
15580    });
15581
15582    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15583    cx.set_state(indoc! {"ˇ
15584        wow
15585        wowen
15586        wowser
15587    "});
15588    cx.simulate_keystroke("w");
15589    cx.executor().run_until_parked();
15590    cx.update_editor(|editor, _, _| {
15591        if editor.context_menu.borrow_mut().is_some() {
15592            panic!(
15593                "expected completion menu to be hidden, as words completion threshold is not met"
15594            );
15595        }
15596    });
15597
15598    cx.update_editor(|editor, window, cx| {
15599        editor.show_word_completions(&ShowWordCompletions, window, cx);
15600    });
15601    cx.executor().run_until_parked();
15602    cx.update_editor(|editor, window, cx| {
15603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15604        {
15605            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
15606        } else {
15607            panic!("expected completion menu to be open after the word completions are called with an action");
15608        }
15609
15610        editor.cancel(&Cancel, window, cx);
15611    });
15612    cx.update_editor(|editor, _, _| {
15613        if editor.context_menu.borrow_mut().is_some() {
15614            panic!("expected completion menu to be hidden after canceling");
15615        }
15616    });
15617
15618    cx.simulate_keystroke("o");
15619    cx.executor().run_until_parked();
15620    cx.update_editor(|editor, _, _| {
15621        if editor.context_menu.borrow_mut().is_some() {
15622            panic!(
15623                "expected completion menu to be hidden, as words completion threshold is not met still"
15624            );
15625        }
15626    });
15627
15628    cx.simulate_keystroke("w");
15629    cx.executor().run_until_parked();
15630    cx.update_editor(|editor, _, _| {
15631        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15632        {
15633            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15634        } else {
15635            panic!("expected completion menu to be open after the word completions threshold is met");
15636        }
15637    });
15638}
15639
15640#[gpui::test]
15641async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15642    init_test(cx, |language_settings| {
15643        language_settings.defaults.completions = Some(CompletionSettingsContent {
15644            words: Some(WordsCompletionMode::Enabled),
15645            words_min_length: Some(0),
15646            lsp_insert_mode: Some(LspInsertMode::Insert),
15647            ..Default::default()
15648        });
15649    });
15650
15651    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15652    cx.update_editor(|editor, _, _| {
15653        editor.disable_word_completions();
15654    });
15655    cx.set_state(indoc! {"ˇ
15656        wow
15657        wowen
15658        wowser
15659    "});
15660    cx.simulate_keystroke("w");
15661    cx.executor().run_until_parked();
15662    cx.update_editor(|editor, _, _| {
15663        if editor.context_menu.borrow_mut().is_some() {
15664            panic!(
15665                "expected completion menu to be hidden, as words completion are disabled for this editor"
15666            );
15667        }
15668    });
15669
15670    cx.update_editor(|editor, window, cx| {
15671        editor.show_word_completions(&ShowWordCompletions, window, cx);
15672    });
15673    cx.executor().run_until_parked();
15674    cx.update_editor(|editor, _, _| {
15675        if editor.context_menu.borrow_mut().is_some() {
15676            panic!(
15677                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15678            );
15679        }
15680    });
15681}
15682
15683#[gpui::test]
15684async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15685    init_test(cx, |language_settings| {
15686        language_settings.defaults.completions = Some(CompletionSettingsContent {
15687            words: Some(WordsCompletionMode::Disabled),
15688            words_min_length: Some(0),
15689            lsp_insert_mode: Some(LspInsertMode::Insert),
15690            ..Default::default()
15691        });
15692    });
15693
15694    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15695    cx.update_editor(|editor, _, _| {
15696        editor.set_completion_provider(None);
15697    });
15698    cx.set_state(indoc! {"ˇ
15699        wow
15700        wowen
15701        wowser
15702    "});
15703    cx.simulate_keystroke("w");
15704    cx.executor().run_until_parked();
15705    cx.update_editor(|editor, _, _| {
15706        if editor.context_menu.borrow_mut().is_some() {
15707            panic!("expected completion menu to be hidden, as disabled in settings");
15708        }
15709    });
15710}
15711
15712fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15713    let position = || lsp::Position {
15714        line: params.text_document_position.position.line,
15715        character: params.text_document_position.position.character,
15716    };
15717    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15718        range: lsp::Range {
15719            start: position(),
15720            end: position(),
15721        },
15722        new_text: text.to_string(),
15723    }))
15724}
15725
15726#[gpui::test]
15727async fn test_multiline_completion(cx: &mut TestAppContext) {
15728    init_test(cx, |_| {});
15729
15730    let fs = FakeFs::new(cx.executor());
15731    fs.insert_tree(
15732        path!("/a"),
15733        json!({
15734            "main.ts": "a",
15735        }),
15736    )
15737    .await;
15738
15739    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15740    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15741    let typescript_language = Arc::new(Language::new(
15742        LanguageConfig {
15743            name: "TypeScript".into(),
15744            matcher: LanguageMatcher {
15745                path_suffixes: vec!["ts".to_string()],
15746                ..LanguageMatcher::default()
15747            },
15748            line_comments: vec!["// ".into()],
15749            ..LanguageConfig::default()
15750        },
15751        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15752    ));
15753    language_registry.add(typescript_language.clone());
15754    let mut fake_servers = language_registry.register_fake_lsp(
15755        "TypeScript",
15756        FakeLspAdapter {
15757            capabilities: lsp::ServerCapabilities {
15758                completion_provider: Some(lsp::CompletionOptions {
15759                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15760                    ..lsp::CompletionOptions::default()
15761                }),
15762                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15763                ..lsp::ServerCapabilities::default()
15764            },
15765            // Emulate vtsls label generation
15766            label_for_completion: Some(Box::new(|item, _| {
15767                let text = if let Some(description) = item
15768                    .label_details
15769                    .as_ref()
15770                    .and_then(|label_details| label_details.description.as_ref())
15771                {
15772                    format!("{} {}", item.label, description)
15773                } else if let Some(detail) = &item.detail {
15774                    format!("{} {}", item.label, detail)
15775                } else {
15776                    item.label.clone()
15777                };
15778                Some(language::CodeLabel::plain(text, None))
15779            })),
15780            ..FakeLspAdapter::default()
15781        },
15782    );
15783    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15784    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15785    let worktree_id = workspace
15786        .update(cx, |workspace, _window, cx| {
15787            workspace.project().update(cx, |project, cx| {
15788                project.worktrees(cx).next().unwrap().read(cx).id()
15789            })
15790        })
15791        .unwrap();
15792    let _buffer = project
15793        .update(cx, |project, cx| {
15794            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15795        })
15796        .await
15797        .unwrap();
15798    let editor = workspace
15799        .update(cx, |workspace, window, cx| {
15800            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15801        })
15802        .unwrap()
15803        .await
15804        .unwrap()
15805        .downcast::<Editor>()
15806        .unwrap();
15807    let fake_server = fake_servers.next().await.unwrap();
15808
15809    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15810    let multiline_label_2 = "a\nb\nc\n";
15811    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15812    let multiline_description = "d\ne\nf\n";
15813    let multiline_detail_2 = "g\nh\ni\n";
15814
15815    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15816        move |params, _| async move {
15817            Ok(Some(lsp::CompletionResponse::Array(vec![
15818                lsp::CompletionItem {
15819                    label: multiline_label.to_string(),
15820                    text_edit: gen_text_edit(&params, "new_text_1"),
15821                    ..lsp::CompletionItem::default()
15822                },
15823                lsp::CompletionItem {
15824                    label: "single line label 1".to_string(),
15825                    detail: Some(multiline_detail.to_string()),
15826                    text_edit: gen_text_edit(&params, "new_text_2"),
15827                    ..lsp::CompletionItem::default()
15828                },
15829                lsp::CompletionItem {
15830                    label: "single line label 2".to_string(),
15831                    label_details: Some(lsp::CompletionItemLabelDetails {
15832                        description: Some(multiline_description.to_string()),
15833                        detail: None,
15834                    }),
15835                    text_edit: gen_text_edit(&params, "new_text_2"),
15836                    ..lsp::CompletionItem::default()
15837                },
15838                lsp::CompletionItem {
15839                    label: multiline_label_2.to_string(),
15840                    detail: Some(multiline_detail_2.to_string()),
15841                    text_edit: gen_text_edit(&params, "new_text_3"),
15842                    ..lsp::CompletionItem::default()
15843                },
15844                lsp::CompletionItem {
15845                    label: "Label with many     spaces and \t but without newlines".to_string(),
15846                    detail: Some(
15847                        "Details with many     spaces and \t but without newlines".to_string(),
15848                    ),
15849                    text_edit: gen_text_edit(&params, "new_text_4"),
15850                    ..lsp::CompletionItem::default()
15851                },
15852            ])))
15853        },
15854    );
15855
15856    editor.update_in(cx, |editor, window, cx| {
15857        cx.focus_self(window);
15858        editor.move_to_end(&MoveToEnd, window, cx);
15859        editor.handle_input(".", window, cx);
15860    });
15861    cx.run_until_parked();
15862    completion_handle.next().await.unwrap();
15863
15864    editor.update(cx, |editor, _| {
15865        assert!(editor.context_menu_visible());
15866        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15867        {
15868            let completion_labels = menu
15869                .completions
15870                .borrow()
15871                .iter()
15872                .map(|c| c.label.text.clone())
15873                .collect::<Vec<_>>();
15874            assert_eq!(
15875                completion_labels,
15876                &[
15877                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15878                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15879                    "single line label 2 d e f ",
15880                    "a b c g h i ",
15881                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15882                ],
15883                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15884            );
15885
15886            for completion in menu
15887                .completions
15888                .borrow()
15889                .iter() {
15890                    assert_eq!(
15891                        completion.label.filter_range,
15892                        0..completion.label.text.len(),
15893                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15894                    );
15895                }
15896        } else {
15897            panic!("expected completion menu to be open");
15898        }
15899    });
15900}
15901
15902#[gpui::test]
15903async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15904    init_test(cx, |_| {});
15905    let mut cx = EditorLspTestContext::new_rust(
15906        lsp::ServerCapabilities {
15907            completion_provider: Some(lsp::CompletionOptions {
15908                trigger_characters: Some(vec![".".to_string()]),
15909                ..Default::default()
15910            }),
15911            ..Default::default()
15912        },
15913        cx,
15914    )
15915    .await;
15916    cx.lsp
15917        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15918            Ok(Some(lsp::CompletionResponse::Array(vec![
15919                lsp::CompletionItem {
15920                    label: "first".into(),
15921                    ..Default::default()
15922                },
15923                lsp::CompletionItem {
15924                    label: "last".into(),
15925                    ..Default::default()
15926                },
15927            ])))
15928        });
15929    cx.set_state("variableˇ");
15930    cx.simulate_keystroke(".");
15931    cx.executor().run_until_parked();
15932
15933    cx.update_editor(|editor, _, _| {
15934        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15935        {
15936            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15937        } else {
15938            panic!("expected completion menu to be open");
15939        }
15940    });
15941
15942    cx.update_editor(|editor, window, cx| {
15943        editor.move_page_down(&MovePageDown::default(), window, cx);
15944        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15945        {
15946            assert!(
15947                menu.selected_item == 1,
15948                "expected PageDown to select the last item from the context menu"
15949            );
15950        } else {
15951            panic!("expected completion menu to stay open after PageDown");
15952        }
15953    });
15954
15955    cx.update_editor(|editor, window, cx| {
15956        editor.move_page_up(&MovePageUp::default(), window, cx);
15957        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15958        {
15959            assert!(
15960                menu.selected_item == 0,
15961                "expected PageUp to select the first item from the context menu"
15962            );
15963        } else {
15964            panic!("expected completion menu to stay open after PageUp");
15965        }
15966    });
15967}
15968
15969#[gpui::test]
15970async fn test_as_is_completions(cx: &mut TestAppContext) {
15971    init_test(cx, |_| {});
15972    let mut cx = EditorLspTestContext::new_rust(
15973        lsp::ServerCapabilities {
15974            completion_provider: Some(lsp::CompletionOptions {
15975                ..Default::default()
15976            }),
15977            ..Default::default()
15978        },
15979        cx,
15980    )
15981    .await;
15982    cx.lsp
15983        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15984            Ok(Some(lsp::CompletionResponse::Array(vec![
15985                lsp::CompletionItem {
15986                    label: "unsafe".into(),
15987                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15988                        range: lsp::Range {
15989                            start: lsp::Position {
15990                                line: 1,
15991                                character: 2,
15992                            },
15993                            end: lsp::Position {
15994                                line: 1,
15995                                character: 3,
15996                            },
15997                        },
15998                        new_text: "unsafe".to_string(),
15999                    })),
16000                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16001                    ..Default::default()
16002                },
16003            ])))
16004        });
16005    cx.set_state("fn a() {}\n");
16006    cx.executor().run_until_parked();
16007    cx.update_editor(|editor, window, cx| {
16008        editor.trigger_completion_on_input("n", true, window, cx)
16009    });
16010    cx.executor().run_until_parked();
16011
16012    cx.update_editor(|editor, window, cx| {
16013        editor.confirm_completion(&Default::default(), window, cx)
16014    });
16015    cx.executor().run_until_parked();
16016    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16017}
16018
16019#[gpui::test]
16020async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16021    init_test(cx, |_| {});
16022    let language =
16023        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16024    let mut cx = EditorLspTestContext::new(
16025        language,
16026        lsp::ServerCapabilities {
16027            completion_provider: Some(lsp::CompletionOptions {
16028                ..lsp::CompletionOptions::default()
16029            }),
16030            ..lsp::ServerCapabilities::default()
16031        },
16032        cx,
16033    )
16034    .await;
16035
16036    cx.set_state(
16037        "#ifndef BAR_H
16038#define BAR_H
16039
16040#include <stdbool.h>
16041
16042int fn_branch(bool do_branch1, bool do_branch2);
16043
16044#endif // BAR_H
16045ˇ",
16046    );
16047    cx.executor().run_until_parked();
16048    cx.update_editor(|editor, window, cx| {
16049        editor.handle_input("#", window, cx);
16050    });
16051    cx.executor().run_until_parked();
16052    cx.update_editor(|editor, window, cx| {
16053        editor.handle_input("i", window, cx);
16054    });
16055    cx.executor().run_until_parked();
16056    cx.update_editor(|editor, window, cx| {
16057        editor.handle_input("n", window, cx);
16058    });
16059    cx.executor().run_until_parked();
16060    cx.assert_editor_state(
16061        "#ifndef BAR_H
16062#define BAR_H
16063
16064#include <stdbool.h>
16065
16066int fn_branch(bool do_branch1, bool do_branch2);
16067
16068#endif // BAR_H
16069#inˇ",
16070    );
16071
16072    cx.lsp
16073        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16074            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16075                is_incomplete: false,
16076                item_defaults: None,
16077                items: vec![lsp::CompletionItem {
16078                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16079                    label_details: Some(lsp::CompletionItemLabelDetails {
16080                        detail: Some("header".to_string()),
16081                        description: None,
16082                    }),
16083                    label: " include".to_string(),
16084                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16085                        range: lsp::Range {
16086                            start: lsp::Position {
16087                                line: 8,
16088                                character: 1,
16089                            },
16090                            end: lsp::Position {
16091                                line: 8,
16092                                character: 1,
16093                            },
16094                        },
16095                        new_text: "include \"$0\"".to_string(),
16096                    })),
16097                    sort_text: Some("40b67681include".to_string()),
16098                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16099                    filter_text: Some("include".to_string()),
16100                    insert_text: Some("include \"$0\"".to_string()),
16101                    ..lsp::CompletionItem::default()
16102                }],
16103            })))
16104        });
16105    cx.update_editor(|editor, window, cx| {
16106        editor.show_completions(&ShowCompletions, window, cx);
16107    });
16108    cx.executor().run_until_parked();
16109    cx.update_editor(|editor, window, cx| {
16110        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16111    });
16112    cx.executor().run_until_parked();
16113    cx.assert_editor_state(
16114        "#ifndef BAR_H
16115#define BAR_H
16116
16117#include <stdbool.h>
16118
16119int fn_branch(bool do_branch1, bool do_branch2);
16120
16121#endif // BAR_H
16122#include \"ˇ\"",
16123    );
16124
16125    cx.lsp
16126        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16127            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16128                is_incomplete: true,
16129                item_defaults: None,
16130                items: vec![lsp::CompletionItem {
16131                    kind: Some(lsp::CompletionItemKind::FILE),
16132                    label: "AGL/".to_string(),
16133                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16134                        range: lsp::Range {
16135                            start: lsp::Position {
16136                                line: 8,
16137                                character: 10,
16138                            },
16139                            end: lsp::Position {
16140                                line: 8,
16141                                character: 11,
16142                            },
16143                        },
16144                        new_text: "AGL/".to_string(),
16145                    })),
16146                    sort_text: Some("40b67681AGL/".to_string()),
16147                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16148                    filter_text: Some("AGL/".to_string()),
16149                    insert_text: Some("AGL/".to_string()),
16150                    ..lsp::CompletionItem::default()
16151                }],
16152            })))
16153        });
16154    cx.update_editor(|editor, window, cx| {
16155        editor.show_completions(&ShowCompletions, window, cx);
16156    });
16157    cx.executor().run_until_parked();
16158    cx.update_editor(|editor, window, cx| {
16159        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16160    });
16161    cx.executor().run_until_parked();
16162    cx.assert_editor_state(
16163        r##"#ifndef BAR_H
16164#define BAR_H
16165
16166#include <stdbool.h>
16167
16168int fn_branch(bool do_branch1, bool do_branch2);
16169
16170#endif // BAR_H
16171#include "AGL/ˇ"##,
16172    );
16173
16174    cx.update_editor(|editor, window, cx| {
16175        editor.handle_input("\"", window, cx);
16176    });
16177    cx.executor().run_until_parked();
16178    cx.assert_editor_state(
16179        r##"#ifndef BAR_H
16180#define BAR_H
16181
16182#include <stdbool.h>
16183
16184int fn_branch(bool do_branch1, bool do_branch2);
16185
16186#endif // BAR_H
16187#include "AGL/"ˇ"##,
16188    );
16189}
16190
16191#[gpui::test]
16192async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16193    init_test(cx, |_| {});
16194
16195    let mut cx = EditorLspTestContext::new_rust(
16196        lsp::ServerCapabilities {
16197            completion_provider: Some(lsp::CompletionOptions {
16198                trigger_characters: Some(vec![".".to_string()]),
16199                resolve_provider: Some(true),
16200                ..Default::default()
16201            }),
16202            ..Default::default()
16203        },
16204        cx,
16205    )
16206    .await;
16207
16208    cx.set_state("fn main() { let a = 2ˇ; }");
16209    cx.simulate_keystroke(".");
16210    let completion_item = lsp::CompletionItem {
16211        label: "Some".into(),
16212        kind: Some(lsp::CompletionItemKind::SNIPPET),
16213        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16214        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16215            kind: lsp::MarkupKind::Markdown,
16216            value: "```rust\nSome(2)\n```".to_string(),
16217        })),
16218        deprecated: Some(false),
16219        sort_text: Some("Some".to_string()),
16220        filter_text: Some("Some".to_string()),
16221        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16222        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16223            range: lsp::Range {
16224                start: lsp::Position {
16225                    line: 0,
16226                    character: 22,
16227                },
16228                end: lsp::Position {
16229                    line: 0,
16230                    character: 22,
16231                },
16232            },
16233            new_text: "Some(2)".to_string(),
16234        })),
16235        additional_text_edits: Some(vec![lsp::TextEdit {
16236            range: lsp::Range {
16237                start: lsp::Position {
16238                    line: 0,
16239                    character: 20,
16240                },
16241                end: lsp::Position {
16242                    line: 0,
16243                    character: 22,
16244                },
16245            },
16246            new_text: "".to_string(),
16247        }]),
16248        ..Default::default()
16249    };
16250
16251    let closure_completion_item = completion_item.clone();
16252    let counter = Arc::new(AtomicUsize::new(0));
16253    let counter_clone = counter.clone();
16254    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16255        let task_completion_item = closure_completion_item.clone();
16256        counter_clone.fetch_add(1, atomic::Ordering::Release);
16257        async move {
16258            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16259                is_incomplete: true,
16260                item_defaults: None,
16261                items: vec![task_completion_item],
16262            })))
16263        }
16264    });
16265
16266    cx.condition(|editor, _| editor.context_menu_visible())
16267        .await;
16268    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16269    assert!(request.next().await.is_some());
16270    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16271
16272    cx.simulate_keystrokes("S o m");
16273    cx.condition(|editor, _| editor.context_menu_visible())
16274        .await;
16275    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16276    assert!(request.next().await.is_some());
16277    assert!(request.next().await.is_some());
16278    assert!(request.next().await.is_some());
16279    request.close();
16280    assert!(request.next().await.is_none());
16281    assert_eq!(
16282        counter.load(atomic::Ordering::Acquire),
16283        4,
16284        "With the completions menu open, only one LSP request should happen per input"
16285    );
16286}
16287
16288#[gpui::test]
16289async fn test_toggle_comment(cx: &mut TestAppContext) {
16290    init_test(cx, |_| {});
16291    let mut cx = EditorTestContext::new(cx).await;
16292    let language = Arc::new(Language::new(
16293        LanguageConfig {
16294            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16295            ..Default::default()
16296        },
16297        Some(tree_sitter_rust::LANGUAGE.into()),
16298    ));
16299    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16300
16301    // If multiple selections intersect a line, the line is only toggled once.
16302    cx.set_state(indoc! {"
16303        fn a() {
16304            «//b();
16305            ˇ»// «c();
16306            //ˇ»  d();
16307        }
16308    "});
16309
16310    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16311
16312    cx.assert_editor_state(indoc! {"
16313        fn a() {
16314            «b();
16315            ˇ»«c();
16316            ˇ» d();
16317        }
16318    "});
16319
16320    // The comment prefix is inserted at the same column for every line in a
16321    // selection.
16322    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16323
16324    cx.assert_editor_state(indoc! {"
16325        fn a() {
16326            // «b();
16327            ˇ»// «c();
16328            ˇ» // d();
16329        }
16330    "});
16331
16332    // If a selection ends at the beginning of a line, that line is not toggled.
16333    cx.set_selections_state(indoc! {"
16334        fn a() {
16335            // b();
16336            «// c();
16337        ˇ»     // d();
16338        }
16339    "});
16340
16341    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16342
16343    cx.assert_editor_state(indoc! {"
16344        fn a() {
16345            // b();
16346            «c();
16347        ˇ»     // d();
16348        }
16349    "});
16350
16351    // If a selection span a single line and is empty, the line is toggled.
16352    cx.set_state(indoc! {"
16353        fn a() {
16354            a();
16355            b();
16356        ˇ
16357        }
16358    "});
16359
16360    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16361
16362    cx.assert_editor_state(indoc! {"
16363        fn a() {
16364            a();
16365            b();
16366        //•ˇ
16367        }
16368    "});
16369
16370    // If a selection span multiple lines, empty lines are not toggled.
16371    cx.set_state(indoc! {"
16372        fn a() {
16373            «a();
16374
16375            c();ˇ»
16376        }
16377    "});
16378
16379    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16380
16381    cx.assert_editor_state(indoc! {"
16382        fn a() {
16383            // «a();
16384
16385            // c();ˇ»
16386        }
16387    "});
16388
16389    // If a selection includes multiple comment prefixes, all lines are uncommented.
16390    cx.set_state(indoc! {"
16391        fn a() {
16392            «// a();
16393            /// b();
16394            //! c();ˇ»
16395        }
16396    "});
16397
16398    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16399
16400    cx.assert_editor_state(indoc! {"
16401        fn a() {
16402            «a();
16403            b();
16404            c();ˇ»
16405        }
16406    "});
16407}
16408
16409#[gpui::test]
16410async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16411    init_test(cx, |_| {});
16412    let mut cx = EditorTestContext::new(cx).await;
16413    let language = Arc::new(Language::new(
16414        LanguageConfig {
16415            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16416            ..Default::default()
16417        },
16418        Some(tree_sitter_rust::LANGUAGE.into()),
16419    ));
16420    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16421
16422    let toggle_comments = &ToggleComments {
16423        advance_downwards: false,
16424        ignore_indent: true,
16425    };
16426
16427    // If multiple selections intersect a line, the line is only toggled once.
16428    cx.set_state(indoc! {"
16429        fn a() {
16430        //    «b();
16431        //    c();
16432        //    ˇ» d();
16433        }
16434    "});
16435
16436    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16437
16438    cx.assert_editor_state(indoc! {"
16439        fn a() {
16440            «b();
16441            c();
16442            ˇ» d();
16443        }
16444    "});
16445
16446    // The comment prefix is inserted at the beginning of each line
16447    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16448
16449    cx.assert_editor_state(indoc! {"
16450        fn a() {
16451        //    «b();
16452        //    c();
16453        //    ˇ» d();
16454        }
16455    "});
16456
16457    // If a selection ends at the beginning of a line, that line is not toggled.
16458    cx.set_selections_state(indoc! {"
16459        fn a() {
16460        //    b();
16461        //    «c();
16462        ˇ»//     d();
16463        }
16464    "});
16465
16466    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16467
16468    cx.assert_editor_state(indoc! {"
16469        fn a() {
16470        //    b();
16471            «c();
16472        ˇ»//     d();
16473        }
16474    "});
16475
16476    // If a selection span a single line and is empty, the line is toggled.
16477    cx.set_state(indoc! {"
16478        fn a() {
16479            a();
16480            b();
16481        ˇ
16482        }
16483    "});
16484
16485    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16486
16487    cx.assert_editor_state(indoc! {"
16488        fn a() {
16489            a();
16490            b();
16491        //ˇ
16492        }
16493    "});
16494
16495    // If a selection span multiple lines, empty lines are not toggled.
16496    cx.set_state(indoc! {"
16497        fn a() {
16498            «a();
16499
16500            c();ˇ»
16501        }
16502    "});
16503
16504    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16505
16506    cx.assert_editor_state(indoc! {"
16507        fn a() {
16508        //    «a();
16509
16510        //    c();ˇ»
16511        }
16512    "});
16513
16514    // If a selection includes multiple comment prefixes, all lines are uncommented.
16515    cx.set_state(indoc! {"
16516        fn a() {
16517        //    «a();
16518        ///    b();
16519        //!    c();ˇ»
16520        }
16521    "});
16522
16523    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16524
16525    cx.assert_editor_state(indoc! {"
16526        fn a() {
16527            «a();
16528            b();
16529            c();ˇ»
16530        }
16531    "});
16532}
16533
16534#[gpui::test]
16535async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16536    init_test(cx, |_| {});
16537
16538    let language = Arc::new(Language::new(
16539        LanguageConfig {
16540            line_comments: vec!["// ".into()],
16541            ..Default::default()
16542        },
16543        Some(tree_sitter_rust::LANGUAGE.into()),
16544    ));
16545
16546    let mut cx = EditorTestContext::new(cx).await;
16547
16548    cx.language_registry().add(language.clone());
16549    cx.update_buffer(|buffer, cx| {
16550        buffer.set_language(Some(language), cx);
16551    });
16552
16553    let toggle_comments = &ToggleComments {
16554        advance_downwards: true,
16555        ignore_indent: false,
16556    };
16557
16558    // Single cursor on one line -> advance
16559    // Cursor moves horizontally 3 characters as well on non-blank line
16560    cx.set_state(indoc!(
16561        "fn a() {
16562             ˇdog();
16563             cat();
16564        }"
16565    ));
16566    cx.update_editor(|editor, window, cx| {
16567        editor.toggle_comments(toggle_comments, window, cx);
16568    });
16569    cx.assert_editor_state(indoc!(
16570        "fn a() {
16571             // dog();
16572             catˇ();
16573        }"
16574    ));
16575
16576    // Single selection on one line -> don't advance
16577    cx.set_state(indoc!(
16578        "fn a() {
16579             «dog()ˇ»;
16580             cat();
16581        }"
16582    ));
16583    cx.update_editor(|editor, window, cx| {
16584        editor.toggle_comments(toggle_comments, window, cx);
16585    });
16586    cx.assert_editor_state(indoc!(
16587        "fn a() {
16588             // «dog()ˇ»;
16589             cat();
16590        }"
16591    ));
16592
16593    // Multiple cursors on one line -> advance
16594    cx.set_state(indoc!(
16595        "fn a() {
16596             ˇdˇog();
16597             cat();
16598        }"
16599    ));
16600    cx.update_editor(|editor, window, cx| {
16601        editor.toggle_comments(toggle_comments, window, cx);
16602    });
16603    cx.assert_editor_state(indoc!(
16604        "fn a() {
16605             // dog();
16606             catˇ(ˇ);
16607        }"
16608    ));
16609
16610    // Multiple cursors on one line, with selection -> don't advance
16611    cx.set_state(indoc!(
16612        "fn a() {
16613             ˇdˇog«()ˇ»;
16614             cat();
16615        }"
16616    ));
16617    cx.update_editor(|editor, window, cx| {
16618        editor.toggle_comments(toggle_comments, window, cx);
16619    });
16620    cx.assert_editor_state(indoc!(
16621        "fn a() {
16622             // ˇdˇog«()ˇ»;
16623             cat();
16624        }"
16625    ));
16626
16627    // Single cursor on one line -> advance
16628    // Cursor moves to column 0 on blank line
16629    cx.set_state(indoc!(
16630        "fn a() {
16631             ˇdog();
16632
16633             cat();
16634        }"
16635    ));
16636    cx.update_editor(|editor, window, cx| {
16637        editor.toggle_comments(toggle_comments, window, cx);
16638    });
16639    cx.assert_editor_state(indoc!(
16640        "fn a() {
16641             // dog();
16642        ˇ
16643             cat();
16644        }"
16645    ));
16646
16647    // Single cursor on one line -> advance
16648    // Cursor starts and ends at column 0
16649    cx.set_state(indoc!(
16650        "fn a() {
16651         ˇ    dog();
16652             cat();
16653        }"
16654    ));
16655    cx.update_editor(|editor, window, cx| {
16656        editor.toggle_comments(toggle_comments, window, cx);
16657    });
16658    cx.assert_editor_state(indoc!(
16659        "fn a() {
16660             // dog();
16661         ˇ    cat();
16662        }"
16663    ));
16664}
16665
16666#[gpui::test]
16667async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16668    init_test(cx, |_| {});
16669
16670    let mut cx = EditorTestContext::new(cx).await;
16671
16672    let html_language = Arc::new(
16673        Language::new(
16674            LanguageConfig {
16675                name: "HTML".into(),
16676                block_comment: Some(BlockCommentConfig {
16677                    start: "<!-- ".into(),
16678                    prefix: "".into(),
16679                    end: " -->".into(),
16680                    tab_size: 0,
16681                }),
16682                ..Default::default()
16683            },
16684            Some(tree_sitter_html::LANGUAGE.into()),
16685        )
16686        .with_injection_query(
16687            r#"
16688            (script_element
16689                (raw_text) @injection.content
16690                (#set! injection.language "javascript"))
16691            "#,
16692        )
16693        .unwrap(),
16694    );
16695
16696    let javascript_language = Arc::new(Language::new(
16697        LanguageConfig {
16698            name: "JavaScript".into(),
16699            line_comments: vec!["// ".into()],
16700            ..Default::default()
16701        },
16702        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16703    ));
16704
16705    cx.language_registry().add(html_language.clone());
16706    cx.language_registry().add(javascript_language);
16707    cx.update_buffer(|buffer, cx| {
16708        buffer.set_language(Some(html_language), cx);
16709    });
16710
16711    // Toggle comments for empty selections
16712    cx.set_state(
16713        &r#"
16714            <p>A</p>ˇ
16715            <p>B</p>ˇ
16716            <p>C</p>ˇ
16717        "#
16718        .unindent(),
16719    );
16720    cx.update_editor(|editor, window, cx| {
16721        editor.toggle_comments(&ToggleComments::default(), window, cx)
16722    });
16723    cx.assert_editor_state(
16724        &r#"
16725            <!-- <p>A</p>ˇ -->
16726            <!-- <p>B</p>ˇ -->
16727            <!-- <p>C</p>ˇ -->
16728        "#
16729        .unindent(),
16730    );
16731    cx.update_editor(|editor, window, cx| {
16732        editor.toggle_comments(&ToggleComments::default(), window, cx)
16733    });
16734    cx.assert_editor_state(
16735        &r#"
16736            <p>A</p>ˇ
16737            <p>B</p>ˇ
16738            <p>C</p>ˇ
16739        "#
16740        .unindent(),
16741    );
16742
16743    // Toggle comments for mixture of empty and non-empty selections, where
16744    // multiple selections occupy a given line.
16745    cx.set_state(
16746        &r#"
16747            <p>A«</p>
16748            <p>ˇ»B</p>ˇ
16749            <p>C«</p>
16750            <p>ˇ»D</p>ˇ
16751        "#
16752        .unindent(),
16753    );
16754
16755    cx.update_editor(|editor, window, cx| {
16756        editor.toggle_comments(&ToggleComments::default(), window, cx)
16757    });
16758    cx.assert_editor_state(
16759        &r#"
16760            <!-- <p>A«</p>
16761            <p>ˇ»B</p>ˇ -->
16762            <!-- <p>C«</p>
16763            <p>ˇ»D</p>ˇ -->
16764        "#
16765        .unindent(),
16766    );
16767    cx.update_editor(|editor, window, cx| {
16768        editor.toggle_comments(&ToggleComments::default(), window, cx)
16769    });
16770    cx.assert_editor_state(
16771        &r#"
16772            <p>A«</p>
16773            <p>ˇ»B</p>ˇ
16774            <p>C«</p>
16775            <p>ˇ»D</p>ˇ
16776        "#
16777        .unindent(),
16778    );
16779
16780    // Toggle comments when different languages are active for different
16781    // selections.
16782    cx.set_state(
16783        &r#"
16784            ˇ<script>
16785                ˇvar x = new Y();
16786            ˇ</script>
16787        "#
16788        .unindent(),
16789    );
16790    cx.executor().run_until_parked();
16791    cx.update_editor(|editor, window, cx| {
16792        editor.toggle_comments(&ToggleComments::default(), window, cx)
16793    });
16794    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16795    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16796    cx.assert_editor_state(
16797        &r#"
16798            <!-- ˇ<script> -->
16799                // ˇvar x = new Y();
16800            <!-- ˇ</script> -->
16801        "#
16802        .unindent(),
16803    );
16804}
16805
16806#[gpui::test]
16807fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16808    init_test(cx, |_| {});
16809
16810    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16811    let multibuffer = cx.new(|cx| {
16812        let mut multibuffer = MultiBuffer::new(ReadWrite);
16813        multibuffer.push_excerpts(
16814            buffer.clone(),
16815            [
16816                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16817                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16818            ],
16819            cx,
16820        );
16821        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16822        multibuffer
16823    });
16824
16825    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16826    editor.update_in(cx, |editor, window, cx| {
16827        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16829            s.select_ranges([
16830                Point::new(0, 0)..Point::new(0, 0),
16831                Point::new(1, 0)..Point::new(1, 0),
16832            ])
16833        });
16834
16835        editor.handle_input("X", window, cx);
16836        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16837        assert_eq!(
16838            editor.selections.ranges(&editor.display_snapshot(cx)),
16839            [
16840                Point::new(0, 1)..Point::new(0, 1),
16841                Point::new(1, 1)..Point::new(1, 1),
16842            ]
16843        );
16844
16845        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16847            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16848        });
16849        editor.backspace(&Default::default(), window, cx);
16850        assert_eq!(editor.text(cx), "Xa\nbbb");
16851        assert_eq!(
16852            editor.selections.ranges(&editor.display_snapshot(cx)),
16853            [Point::new(1, 0)..Point::new(1, 0)]
16854        );
16855
16856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16857            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16858        });
16859        editor.backspace(&Default::default(), window, cx);
16860        assert_eq!(editor.text(cx), "X\nbb");
16861        assert_eq!(
16862            editor.selections.ranges(&editor.display_snapshot(cx)),
16863            [Point::new(0, 1)..Point::new(0, 1)]
16864        );
16865    });
16866}
16867
16868#[gpui::test]
16869fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16870    init_test(cx, |_| {});
16871
16872    let markers = vec![('[', ']').into(), ('(', ')').into()];
16873    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16874        indoc! {"
16875            [aaaa
16876            (bbbb]
16877            cccc)",
16878        },
16879        markers.clone(),
16880    );
16881    let excerpt_ranges = markers.into_iter().map(|marker| {
16882        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16883        ExcerptRange::new(context)
16884    });
16885    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16886    let multibuffer = cx.new(|cx| {
16887        let mut multibuffer = MultiBuffer::new(ReadWrite);
16888        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16889        multibuffer
16890    });
16891
16892    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16893    editor.update_in(cx, |editor, window, cx| {
16894        let (expected_text, selection_ranges) = marked_text_ranges(
16895            indoc! {"
16896                aaaa
16897                bˇbbb
16898                bˇbbˇb
16899                cccc"
16900            },
16901            true,
16902        );
16903        assert_eq!(editor.text(cx), expected_text);
16904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16905            s.select_ranges(
16906                selection_ranges
16907                    .iter()
16908                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16909            )
16910        });
16911
16912        editor.handle_input("X", window, cx);
16913
16914        let (expected_text, expected_selections) = marked_text_ranges(
16915            indoc! {"
16916                aaaa
16917                bXˇbbXb
16918                bXˇbbXˇb
16919                cccc"
16920            },
16921            false,
16922        );
16923        assert_eq!(editor.text(cx), expected_text);
16924        assert_eq!(
16925            editor.selections.ranges(&editor.display_snapshot(cx)),
16926            expected_selections
16927                .iter()
16928                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16929                .collect::<Vec<_>>()
16930        );
16931
16932        editor.newline(&Newline, window, cx);
16933        let (expected_text, expected_selections) = marked_text_ranges(
16934            indoc! {"
16935                aaaa
16936                bX
16937                ˇbbX
16938                b
16939                bX
16940                ˇbbX
16941                ˇb
16942                cccc"
16943            },
16944            false,
16945        );
16946        assert_eq!(editor.text(cx), expected_text);
16947        assert_eq!(
16948            editor.selections.ranges(&editor.display_snapshot(cx)),
16949            expected_selections
16950                .iter()
16951                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16952                .collect::<Vec<_>>()
16953        );
16954    });
16955}
16956
16957#[gpui::test]
16958fn test_refresh_selections(cx: &mut TestAppContext) {
16959    init_test(cx, |_| {});
16960
16961    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16962    let mut excerpt1_id = None;
16963    let multibuffer = cx.new(|cx| {
16964        let mut multibuffer = MultiBuffer::new(ReadWrite);
16965        excerpt1_id = multibuffer
16966            .push_excerpts(
16967                buffer.clone(),
16968                [
16969                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16970                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16971                ],
16972                cx,
16973            )
16974            .into_iter()
16975            .next();
16976        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16977        multibuffer
16978    });
16979
16980    let editor = cx.add_window(|window, cx| {
16981        let mut editor = build_editor(multibuffer.clone(), window, cx);
16982        let snapshot = editor.snapshot(window, cx);
16983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16984            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16985        });
16986        editor.begin_selection(
16987            Point::new(2, 1).to_display_point(&snapshot),
16988            true,
16989            1,
16990            window,
16991            cx,
16992        );
16993        assert_eq!(
16994            editor.selections.ranges(&editor.display_snapshot(cx)),
16995            [
16996                Point::new(1, 3)..Point::new(1, 3),
16997                Point::new(2, 1)..Point::new(2, 1),
16998            ]
16999        );
17000        editor
17001    });
17002
17003    // Refreshing selections is a no-op when excerpts haven't changed.
17004    _ = editor.update(cx, |editor, window, cx| {
17005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17006        assert_eq!(
17007            editor.selections.ranges(&editor.display_snapshot(cx)),
17008            [
17009                Point::new(1, 3)..Point::new(1, 3),
17010                Point::new(2, 1)..Point::new(2, 1),
17011            ]
17012        );
17013    });
17014
17015    multibuffer.update(cx, |multibuffer, cx| {
17016        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17017    });
17018    _ = editor.update(cx, |editor, window, cx| {
17019        // Removing an excerpt causes the first selection to become degenerate.
17020        assert_eq!(
17021            editor.selections.ranges(&editor.display_snapshot(cx)),
17022            [
17023                Point::new(0, 0)..Point::new(0, 0),
17024                Point::new(0, 1)..Point::new(0, 1)
17025            ]
17026        );
17027
17028        // Refreshing selections will relocate the first selection to the original buffer
17029        // location.
17030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17031        assert_eq!(
17032            editor.selections.ranges(&editor.display_snapshot(cx)),
17033            [
17034                Point::new(0, 1)..Point::new(0, 1),
17035                Point::new(0, 3)..Point::new(0, 3)
17036            ]
17037        );
17038        assert!(editor.selections.pending_anchor().is_some());
17039    });
17040}
17041
17042#[gpui::test]
17043fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17044    init_test(cx, |_| {});
17045
17046    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17047    let mut excerpt1_id = None;
17048    let multibuffer = cx.new(|cx| {
17049        let mut multibuffer = MultiBuffer::new(ReadWrite);
17050        excerpt1_id = multibuffer
17051            .push_excerpts(
17052                buffer.clone(),
17053                [
17054                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17055                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17056                ],
17057                cx,
17058            )
17059            .into_iter()
17060            .next();
17061        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17062        multibuffer
17063    });
17064
17065    let editor = cx.add_window(|window, cx| {
17066        let mut editor = build_editor(multibuffer.clone(), window, cx);
17067        let snapshot = editor.snapshot(window, cx);
17068        editor.begin_selection(
17069            Point::new(1, 3).to_display_point(&snapshot),
17070            false,
17071            1,
17072            window,
17073            cx,
17074        );
17075        assert_eq!(
17076            editor.selections.ranges(&editor.display_snapshot(cx)),
17077            [Point::new(1, 3)..Point::new(1, 3)]
17078        );
17079        editor
17080    });
17081
17082    multibuffer.update(cx, |multibuffer, cx| {
17083        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17084    });
17085    _ = editor.update(cx, |editor, window, cx| {
17086        assert_eq!(
17087            editor.selections.ranges(&editor.display_snapshot(cx)),
17088            [Point::new(0, 0)..Point::new(0, 0)]
17089        );
17090
17091        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17093        assert_eq!(
17094            editor.selections.ranges(&editor.display_snapshot(cx)),
17095            [Point::new(0, 3)..Point::new(0, 3)]
17096        );
17097        assert!(editor.selections.pending_anchor().is_some());
17098    });
17099}
17100
17101#[gpui::test]
17102async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17103    init_test(cx, |_| {});
17104
17105    let language = Arc::new(
17106        Language::new(
17107            LanguageConfig {
17108                brackets: BracketPairConfig {
17109                    pairs: vec![
17110                        BracketPair {
17111                            start: "{".to_string(),
17112                            end: "}".to_string(),
17113                            close: true,
17114                            surround: true,
17115                            newline: true,
17116                        },
17117                        BracketPair {
17118                            start: "/* ".to_string(),
17119                            end: " */".to_string(),
17120                            close: true,
17121                            surround: true,
17122                            newline: true,
17123                        },
17124                    ],
17125                    ..Default::default()
17126                },
17127                ..Default::default()
17128            },
17129            Some(tree_sitter_rust::LANGUAGE.into()),
17130        )
17131        .with_indents_query("")
17132        .unwrap(),
17133    );
17134
17135    let text = concat!(
17136        "{   }\n",     //
17137        "  x\n",       //
17138        "  /*   */\n", //
17139        "x\n",         //
17140        "{{} }\n",     //
17141    );
17142
17143    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17146    editor
17147        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17148        .await;
17149
17150    editor.update_in(cx, |editor, window, cx| {
17151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17152            s.select_display_ranges([
17153                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17154                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17155                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17156            ])
17157        });
17158        editor.newline(&Newline, window, cx);
17159
17160        assert_eq!(
17161            editor.buffer().read(cx).read(cx).text(),
17162            concat!(
17163                "{ \n",    // Suppress rustfmt
17164                "\n",      //
17165                "}\n",     //
17166                "  x\n",   //
17167                "  /* \n", //
17168                "  \n",    //
17169                "  */\n",  //
17170                "x\n",     //
17171                "{{} \n",  //
17172                "}\n",     //
17173            )
17174        );
17175    });
17176}
17177
17178#[gpui::test]
17179fn test_highlighted_ranges(cx: &mut TestAppContext) {
17180    init_test(cx, |_| {});
17181
17182    let editor = cx.add_window(|window, cx| {
17183        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17184        build_editor(buffer, window, cx)
17185    });
17186
17187    _ = editor.update(cx, |editor, window, cx| {
17188        struct Type1;
17189        struct Type2;
17190
17191        let buffer = editor.buffer.read(cx).snapshot(cx);
17192
17193        let anchor_range =
17194            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17195
17196        editor.highlight_background::<Type1>(
17197            &[
17198                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17199                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17200                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17201                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17202            ],
17203            |_, _| Hsla::red(),
17204            cx,
17205        );
17206        editor.highlight_background::<Type2>(
17207            &[
17208                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17209                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17210                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17211                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17212            ],
17213            |_, _| Hsla::green(),
17214            cx,
17215        );
17216
17217        let snapshot = editor.snapshot(window, cx);
17218        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17219            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17220            &snapshot,
17221            cx.theme(),
17222        );
17223        assert_eq!(
17224            highlighted_ranges,
17225            &[
17226                (
17227                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17228                    Hsla::green(),
17229                ),
17230                (
17231                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17232                    Hsla::red(),
17233                ),
17234                (
17235                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17236                    Hsla::green(),
17237                ),
17238                (
17239                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17240                    Hsla::red(),
17241                ),
17242            ]
17243        );
17244        assert_eq!(
17245            editor.sorted_background_highlights_in_range(
17246                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17247                &snapshot,
17248                cx.theme(),
17249            ),
17250            &[(
17251                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17252                Hsla::red(),
17253            )]
17254        );
17255    });
17256}
17257
17258#[gpui::test]
17259async fn test_following(cx: &mut TestAppContext) {
17260    init_test(cx, |_| {});
17261
17262    let fs = FakeFs::new(cx.executor());
17263    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17264
17265    let buffer = project.update(cx, |project, cx| {
17266        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17267        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17268    });
17269    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17270    let follower = cx.update(|cx| {
17271        cx.open_window(
17272            WindowOptions {
17273                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17274                    gpui::Point::new(px(0.), px(0.)),
17275                    gpui::Point::new(px(10.), px(80.)),
17276                ))),
17277                ..Default::default()
17278            },
17279            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17280        )
17281        .unwrap()
17282    });
17283
17284    let is_still_following = Rc::new(RefCell::new(true));
17285    let follower_edit_event_count = Rc::new(RefCell::new(0));
17286    let pending_update = Rc::new(RefCell::new(None));
17287    let leader_entity = leader.root(cx).unwrap();
17288    let follower_entity = follower.root(cx).unwrap();
17289    _ = follower.update(cx, {
17290        let update = pending_update.clone();
17291        let is_still_following = is_still_following.clone();
17292        let follower_edit_event_count = follower_edit_event_count.clone();
17293        |_, window, cx| {
17294            cx.subscribe_in(
17295                &leader_entity,
17296                window,
17297                move |_, leader, event, window, cx| {
17298                    leader.read(cx).add_event_to_update_proto(
17299                        event,
17300                        &mut update.borrow_mut(),
17301                        window,
17302                        cx,
17303                    );
17304                },
17305            )
17306            .detach();
17307
17308            cx.subscribe_in(
17309                &follower_entity,
17310                window,
17311                move |_, _, event: &EditorEvent, _window, _cx| {
17312                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17313                        *is_still_following.borrow_mut() = false;
17314                    }
17315
17316                    if let EditorEvent::BufferEdited = event {
17317                        *follower_edit_event_count.borrow_mut() += 1;
17318                    }
17319                },
17320            )
17321            .detach();
17322        }
17323    });
17324
17325    // Update the selections only
17326    _ = leader.update(cx, |leader, window, cx| {
17327        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17328            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17329        });
17330    });
17331    follower
17332        .update(cx, |follower, window, cx| {
17333            follower.apply_update_proto(
17334                &project,
17335                pending_update.borrow_mut().take().unwrap(),
17336                window,
17337                cx,
17338            )
17339        })
17340        .unwrap()
17341        .await
17342        .unwrap();
17343    _ = follower.update(cx, |follower, _, cx| {
17344        assert_eq!(
17345            follower.selections.ranges(&follower.display_snapshot(cx)),
17346            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17347        );
17348    });
17349    assert!(*is_still_following.borrow());
17350    assert_eq!(*follower_edit_event_count.borrow(), 0);
17351
17352    // Update the scroll position only
17353    _ = leader.update(cx, |leader, window, cx| {
17354        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17355    });
17356    follower
17357        .update(cx, |follower, window, cx| {
17358            follower.apply_update_proto(
17359                &project,
17360                pending_update.borrow_mut().take().unwrap(),
17361                window,
17362                cx,
17363            )
17364        })
17365        .unwrap()
17366        .await
17367        .unwrap();
17368    assert_eq!(
17369        follower
17370            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17371            .unwrap(),
17372        gpui::Point::new(1.5, 3.5)
17373    );
17374    assert!(*is_still_following.borrow());
17375    assert_eq!(*follower_edit_event_count.borrow(), 0);
17376
17377    // Update the selections and scroll position. The follower's scroll position is updated
17378    // via autoscroll, not via the leader's exact scroll position.
17379    _ = leader.update(cx, |leader, window, cx| {
17380        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17381            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17382        });
17383        leader.request_autoscroll(Autoscroll::newest(), cx);
17384        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17385    });
17386    follower
17387        .update(cx, |follower, window, cx| {
17388            follower.apply_update_proto(
17389                &project,
17390                pending_update.borrow_mut().take().unwrap(),
17391                window,
17392                cx,
17393            )
17394        })
17395        .unwrap()
17396        .await
17397        .unwrap();
17398    _ = follower.update(cx, |follower, _, cx| {
17399        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17400        assert_eq!(
17401            follower.selections.ranges(&follower.display_snapshot(cx)),
17402            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17403        );
17404    });
17405    assert!(*is_still_following.borrow());
17406
17407    // Creating a pending selection that precedes another selection
17408    _ = leader.update(cx, |leader, window, cx| {
17409        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17410            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17411        });
17412        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17413    });
17414    follower
17415        .update(cx, |follower, window, cx| {
17416            follower.apply_update_proto(
17417                &project,
17418                pending_update.borrow_mut().take().unwrap(),
17419                window,
17420                cx,
17421            )
17422        })
17423        .unwrap()
17424        .await
17425        .unwrap();
17426    _ = follower.update(cx, |follower, _, cx| {
17427        assert_eq!(
17428            follower.selections.ranges(&follower.display_snapshot(cx)),
17429            vec![
17430                MultiBufferOffset(0)..MultiBufferOffset(0),
17431                MultiBufferOffset(1)..MultiBufferOffset(1)
17432            ]
17433        );
17434    });
17435    assert!(*is_still_following.borrow());
17436
17437    // Extend the pending selection so that it surrounds another selection
17438    _ = leader.update(cx, |leader, window, cx| {
17439        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17440    });
17441    follower
17442        .update(cx, |follower, window, cx| {
17443            follower.apply_update_proto(
17444                &project,
17445                pending_update.borrow_mut().take().unwrap(),
17446                window,
17447                cx,
17448            )
17449        })
17450        .unwrap()
17451        .await
17452        .unwrap();
17453    _ = follower.update(cx, |follower, _, cx| {
17454        assert_eq!(
17455            follower.selections.ranges(&follower.display_snapshot(cx)),
17456            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17457        );
17458    });
17459
17460    // Scrolling locally breaks the follow
17461    _ = follower.update(cx, |follower, window, cx| {
17462        let top_anchor = follower
17463            .buffer()
17464            .read(cx)
17465            .read(cx)
17466            .anchor_after(MultiBufferOffset(0));
17467        follower.set_scroll_anchor(
17468            ScrollAnchor {
17469                anchor: top_anchor,
17470                offset: gpui::Point::new(0.0, 0.5),
17471            },
17472            window,
17473            cx,
17474        );
17475    });
17476    assert!(!(*is_still_following.borrow()));
17477}
17478
17479#[gpui::test]
17480async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17481    init_test(cx, |_| {});
17482
17483    let fs = FakeFs::new(cx.executor());
17484    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17485    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17486    let pane = workspace
17487        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17488        .unwrap();
17489
17490    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17491
17492    let leader = pane.update_in(cx, |_, window, cx| {
17493        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17494        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17495    });
17496
17497    // Start following the editor when it has no excerpts.
17498    let mut state_message =
17499        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17500    let workspace_entity = workspace.root(cx).unwrap();
17501    let follower_1 = cx
17502        .update_window(*workspace.deref(), |_, window, cx| {
17503            Editor::from_state_proto(
17504                workspace_entity,
17505                ViewId {
17506                    creator: CollaboratorId::PeerId(PeerId::default()),
17507                    id: 0,
17508                },
17509                &mut state_message,
17510                window,
17511                cx,
17512            )
17513        })
17514        .unwrap()
17515        .unwrap()
17516        .await
17517        .unwrap();
17518
17519    let update_message = Rc::new(RefCell::new(None));
17520    follower_1.update_in(cx, {
17521        let update = update_message.clone();
17522        |_, window, cx| {
17523            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17524                leader.read(cx).add_event_to_update_proto(
17525                    event,
17526                    &mut update.borrow_mut(),
17527                    window,
17528                    cx,
17529                );
17530            })
17531            .detach();
17532        }
17533    });
17534
17535    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17536        (
17537            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17538            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17539        )
17540    });
17541
17542    // Insert some excerpts.
17543    leader.update(cx, |leader, cx| {
17544        leader.buffer.update(cx, |multibuffer, cx| {
17545            multibuffer.set_excerpts_for_path(
17546                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17547                buffer_1.clone(),
17548                vec![
17549                    Point::row_range(0..3),
17550                    Point::row_range(1..6),
17551                    Point::row_range(12..15),
17552                ],
17553                0,
17554                cx,
17555            );
17556            multibuffer.set_excerpts_for_path(
17557                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17558                buffer_2.clone(),
17559                vec![Point::row_range(0..6), Point::row_range(8..12)],
17560                0,
17561                cx,
17562            );
17563        });
17564    });
17565
17566    // Apply the update of adding the excerpts.
17567    follower_1
17568        .update_in(cx, |follower, window, cx| {
17569            follower.apply_update_proto(
17570                &project,
17571                update_message.borrow().clone().unwrap(),
17572                window,
17573                cx,
17574            )
17575        })
17576        .await
17577        .unwrap();
17578    assert_eq!(
17579        follower_1.update(cx, |editor, cx| editor.text(cx)),
17580        leader.update(cx, |editor, cx| editor.text(cx))
17581    );
17582    update_message.borrow_mut().take();
17583
17584    // Start following separately after it already has excerpts.
17585    let mut state_message =
17586        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17587    let workspace_entity = workspace.root(cx).unwrap();
17588    let follower_2 = cx
17589        .update_window(*workspace.deref(), |_, window, cx| {
17590            Editor::from_state_proto(
17591                workspace_entity,
17592                ViewId {
17593                    creator: CollaboratorId::PeerId(PeerId::default()),
17594                    id: 0,
17595                },
17596                &mut state_message,
17597                window,
17598                cx,
17599            )
17600        })
17601        .unwrap()
17602        .unwrap()
17603        .await
17604        .unwrap();
17605    assert_eq!(
17606        follower_2.update(cx, |editor, cx| editor.text(cx)),
17607        leader.update(cx, |editor, cx| editor.text(cx))
17608    );
17609
17610    // Remove some excerpts.
17611    leader.update(cx, |leader, cx| {
17612        leader.buffer.update(cx, |multibuffer, cx| {
17613            let excerpt_ids = multibuffer.excerpt_ids();
17614            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17615            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17616        });
17617    });
17618
17619    // Apply the update of removing the excerpts.
17620    follower_1
17621        .update_in(cx, |follower, window, cx| {
17622            follower.apply_update_proto(
17623                &project,
17624                update_message.borrow().clone().unwrap(),
17625                window,
17626                cx,
17627            )
17628        })
17629        .await
17630        .unwrap();
17631    follower_2
17632        .update_in(cx, |follower, window, cx| {
17633            follower.apply_update_proto(
17634                &project,
17635                update_message.borrow().clone().unwrap(),
17636                window,
17637                cx,
17638            )
17639        })
17640        .await
17641        .unwrap();
17642    update_message.borrow_mut().take();
17643    assert_eq!(
17644        follower_1.update(cx, |editor, cx| editor.text(cx)),
17645        leader.update(cx, |editor, cx| editor.text(cx))
17646    );
17647}
17648
17649#[gpui::test]
17650async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17651    init_test(cx, |_| {});
17652
17653    let mut cx = EditorTestContext::new(cx).await;
17654    let lsp_store =
17655        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17656
17657    cx.set_state(indoc! {"
17658        ˇfn func(abc def: i32) -> u32 {
17659        }
17660    "});
17661
17662    cx.update(|_, cx| {
17663        lsp_store.update(cx, |lsp_store, cx| {
17664            lsp_store
17665                .update_diagnostics(
17666                    LanguageServerId(0),
17667                    lsp::PublishDiagnosticsParams {
17668                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17669                        version: None,
17670                        diagnostics: vec![
17671                            lsp::Diagnostic {
17672                                range: lsp::Range::new(
17673                                    lsp::Position::new(0, 11),
17674                                    lsp::Position::new(0, 12),
17675                                ),
17676                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17677                                ..Default::default()
17678                            },
17679                            lsp::Diagnostic {
17680                                range: lsp::Range::new(
17681                                    lsp::Position::new(0, 12),
17682                                    lsp::Position::new(0, 15),
17683                                ),
17684                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17685                                ..Default::default()
17686                            },
17687                            lsp::Diagnostic {
17688                                range: lsp::Range::new(
17689                                    lsp::Position::new(0, 25),
17690                                    lsp::Position::new(0, 28),
17691                                ),
17692                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17693                                ..Default::default()
17694                            },
17695                        ],
17696                    },
17697                    None,
17698                    DiagnosticSourceKind::Pushed,
17699                    &[],
17700                    cx,
17701                )
17702                .unwrap()
17703        });
17704    });
17705
17706    executor.run_until_parked();
17707
17708    cx.update_editor(|editor, window, cx| {
17709        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17710    });
17711
17712    cx.assert_editor_state(indoc! {"
17713        fn func(abc def: i32) -> ˇu32 {
17714        }
17715    "});
17716
17717    cx.update_editor(|editor, window, cx| {
17718        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17719    });
17720
17721    cx.assert_editor_state(indoc! {"
17722        fn func(abc ˇdef: i32) -> u32 {
17723        }
17724    "});
17725
17726    cx.update_editor(|editor, window, cx| {
17727        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17728    });
17729
17730    cx.assert_editor_state(indoc! {"
17731        fn func(abcˇ def: i32) -> u32 {
17732        }
17733    "});
17734
17735    cx.update_editor(|editor, window, cx| {
17736        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17737    });
17738
17739    cx.assert_editor_state(indoc! {"
17740        fn func(abc def: i32) -> ˇu32 {
17741        }
17742    "});
17743}
17744
17745#[gpui::test]
17746async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17747    init_test(cx, |_| {});
17748
17749    let mut cx = EditorTestContext::new(cx).await;
17750
17751    let diff_base = r#"
17752        use some::mod;
17753
17754        const A: u32 = 42;
17755
17756        fn main() {
17757            println!("hello");
17758
17759            println!("world");
17760        }
17761        "#
17762    .unindent();
17763
17764    // Edits are modified, removed, modified, added
17765    cx.set_state(
17766        &r#"
17767        use some::modified;
17768
17769        ˇ
17770        fn main() {
17771            println!("hello there");
17772
17773            println!("around the");
17774            println!("world");
17775        }
17776        "#
17777        .unindent(),
17778    );
17779
17780    cx.set_head_text(&diff_base);
17781    executor.run_until_parked();
17782
17783    cx.update_editor(|editor, window, cx| {
17784        //Wrap around the bottom of the buffer
17785        for _ in 0..3 {
17786            editor.go_to_next_hunk(&GoToHunk, window, cx);
17787        }
17788    });
17789
17790    cx.assert_editor_state(
17791        &r#"
17792        ˇuse some::modified;
17793
17794
17795        fn main() {
17796            println!("hello there");
17797
17798            println!("around the");
17799            println!("world");
17800        }
17801        "#
17802        .unindent(),
17803    );
17804
17805    cx.update_editor(|editor, window, cx| {
17806        //Wrap around the top of the buffer
17807        for _ in 0..2 {
17808            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17809        }
17810    });
17811
17812    cx.assert_editor_state(
17813        &r#"
17814        use some::modified;
17815
17816
17817        fn main() {
17818        ˇ    println!("hello there");
17819
17820            println!("around the");
17821            println!("world");
17822        }
17823        "#
17824        .unindent(),
17825    );
17826
17827    cx.update_editor(|editor, window, cx| {
17828        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17829    });
17830
17831    cx.assert_editor_state(
17832        &r#"
17833        use some::modified;
17834
17835        ˇ
17836        fn main() {
17837            println!("hello there");
17838
17839            println!("around the");
17840            println!("world");
17841        }
17842        "#
17843        .unindent(),
17844    );
17845
17846    cx.update_editor(|editor, window, cx| {
17847        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17848    });
17849
17850    cx.assert_editor_state(
17851        &r#"
17852        ˇuse some::modified;
17853
17854
17855        fn main() {
17856            println!("hello there");
17857
17858            println!("around the");
17859            println!("world");
17860        }
17861        "#
17862        .unindent(),
17863    );
17864
17865    cx.update_editor(|editor, window, cx| {
17866        for _ in 0..2 {
17867            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17868        }
17869    });
17870
17871    cx.assert_editor_state(
17872        &r#"
17873        use some::modified;
17874
17875
17876        fn main() {
17877        ˇ    println!("hello there");
17878
17879            println!("around the");
17880            println!("world");
17881        }
17882        "#
17883        .unindent(),
17884    );
17885
17886    cx.update_editor(|editor, window, cx| {
17887        editor.fold(&Fold, window, cx);
17888    });
17889
17890    cx.update_editor(|editor, window, cx| {
17891        editor.go_to_next_hunk(&GoToHunk, window, cx);
17892    });
17893
17894    cx.assert_editor_state(
17895        &r#"
17896        ˇuse some::modified;
17897
17898
17899        fn main() {
17900            println!("hello there");
17901
17902            println!("around the");
17903            println!("world");
17904        }
17905        "#
17906        .unindent(),
17907    );
17908}
17909
17910#[test]
17911fn test_split_words() {
17912    fn split(text: &str) -> Vec<&str> {
17913        split_words(text).collect()
17914    }
17915
17916    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17917    assert_eq!(split("hello_world"), &["hello_", "world"]);
17918    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17919    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17920    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17921    assert_eq!(split("helloworld"), &["helloworld"]);
17922
17923    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17924}
17925
17926#[test]
17927fn test_split_words_for_snippet_prefix() {
17928    fn split(text: &str) -> Vec<&str> {
17929        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17930    }
17931
17932    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17933    assert_eq!(split("hello_world"), &["hello_world"]);
17934    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17935    assert_eq!(split("Hello_World"), &["Hello_World"]);
17936    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17937    assert_eq!(split("helloworld"), &["helloworld"]);
17938    assert_eq!(
17939        split("this@is!@#$^many   . symbols"),
17940        &[
17941            "symbols",
17942            " symbols",
17943            ". symbols",
17944            " . symbols",
17945            "  . symbols",
17946            "   . symbols",
17947            "many   . symbols",
17948            "^many   . symbols",
17949            "$^many   . symbols",
17950            "#$^many   . symbols",
17951            "@#$^many   . symbols",
17952            "!@#$^many   . symbols",
17953            "is!@#$^many   . symbols",
17954            "@is!@#$^many   . symbols",
17955            "this@is!@#$^many   . symbols",
17956        ],
17957    );
17958    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17959}
17960
17961#[gpui::test]
17962async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17963    init_test(cx, |_| {});
17964
17965    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17966
17967    #[track_caller]
17968    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17969        let _state_context = cx.set_state(before);
17970        cx.run_until_parked();
17971        cx.update_editor(|editor, window, cx| {
17972            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17973        });
17974        cx.run_until_parked();
17975        cx.assert_editor_state(after);
17976    }
17977
17978    // Outside bracket jumps to outside of matching bracket
17979    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17980    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17981
17982    // Inside bracket jumps to inside of matching bracket
17983    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17984    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17985
17986    // When outside a bracket and inside, favor jumping to the inside bracket
17987    assert(
17988        "console.log('foo', [1, 2, 3]ˇ);",
17989        "console.log('foo', ˇ[1, 2, 3]);",
17990        &mut cx,
17991    );
17992    assert(
17993        "console.log(ˇ'foo', [1, 2, 3]);",
17994        "console.log('foo'ˇ, [1, 2, 3]);",
17995        &mut cx,
17996    );
17997
17998    // Bias forward if two options are equally likely
17999    assert(
18000        "let result = curried_fun()ˇ();",
18001        "let result = curried_fun()()ˇ;",
18002        &mut cx,
18003    );
18004
18005    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18006    assert(
18007        indoc! {"
18008            function test() {
18009                console.log('test')ˇ
18010            }"},
18011        indoc! {"
18012            function test() {
18013                console.logˇ('test')
18014            }"},
18015        &mut cx,
18016    );
18017}
18018
18019#[gpui::test]
18020async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18021    init_test(cx, |_| {});
18022    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18023    language_registry.add(markdown_lang());
18024    language_registry.add(rust_lang());
18025    let buffer = cx.new(|cx| {
18026        let mut buffer = language::Buffer::local(
18027            indoc! {"
18028            ```rs
18029            impl Worktree {
18030                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18031                }
18032            }
18033            ```
18034        "},
18035            cx,
18036        );
18037        buffer.set_language_registry(language_registry.clone());
18038        buffer.set_language(Some(markdown_lang()), cx);
18039        buffer
18040    });
18041    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18042    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18043    cx.executor().run_until_parked();
18044    _ = editor.update(cx, |editor, window, cx| {
18045        // Case 1: Test outer enclosing brackets
18046        select_ranges(
18047            editor,
18048            &indoc! {"
18049                ```rs
18050                impl Worktree {
18051                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18052                    }
1805318054                ```
18055            "},
18056            window,
18057            cx,
18058        );
18059        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18060        assert_text_with_selections(
18061            editor,
18062            &indoc! {"
18063                ```rs
18064                impl Worktree ˇ{
18065                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18066                    }
18067                }
18068                ```
18069            "},
18070            cx,
18071        );
18072        // Case 2: Test inner enclosing brackets
18073        select_ranges(
18074            editor,
18075            &indoc! {"
18076                ```rs
18077                impl Worktree {
18078                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1807918080                }
18081                ```
18082            "},
18083            window,
18084            cx,
18085        );
18086        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18087        assert_text_with_selections(
18088            editor,
18089            &indoc! {"
18090                ```rs
18091                impl Worktree {
18092                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18093                    }
18094                }
18095                ```
18096            "},
18097            cx,
18098        );
18099    });
18100}
18101
18102#[gpui::test]
18103async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18104    init_test(cx, |_| {});
18105
18106    let fs = FakeFs::new(cx.executor());
18107    fs.insert_tree(
18108        path!("/a"),
18109        json!({
18110            "main.rs": "fn main() { let a = 5; }",
18111            "other.rs": "// Test file",
18112        }),
18113    )
18114    .await;
18115    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18116
18117    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18118    language_registry.add(Arc::new(Language::new(
18119        LanguageConfig {
18120            name: "Rust".into(),
18121            matcher: LanguageMatcher {
18122                path_suffixes: vec!["rs".to_string()],
18123                ..Default::default()
18124            },
18125            brackets: BracketPairConfig {
18126                pairs: vec![BracketPair {
18127                    start: "{".to_string(),
18128                    end: "}".to_string(),
18129                    close: true,
18130                    surround: true,
18131                    newline: true,
18132                }],
18133                disabled_scopes_by_bracket_ix: Vec::new(),
18134            },
18135            ..Default::default()
18136        },
18137        Some(tree_sitter_rust::LANGUAGE.into()),
18138    )));
18139    let mut fake_servers = language_registry.register_fake_lsp(
18140        "Rust",
18141        FakeLspAdapter {
18142            capabilities: lsp::ServerCapabilities {
18143                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18144                    first_trigger_character: "{".to_string(),
18145                    more_trigger_character: None,
18146                }),
18147                ..Default::default()
18148            },
18149            ..Default::default()
18150        },
18151    );
18152
18153    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18154
18155    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18156
18157    let worktree_id = workspace
18158        .update(cx, |workspace, _, cx| {
18159            workspace.project().update(cx, |project, cx| {
18160                project.worktrees(cx).next().unwrap().read(cx).id()
18161            })
18162        })
18163        .unwrap();
18164
18165    let buffer = project
18166        .update(cx, |project, cx| {
18167            project.open_local_buffer(path!("/a/main.rs"), cx)
18168        })
18169        .await
18170        .unwrap();
18171    let editor_handle = workspace
18172        .update(cx, |workspace, window, cx| {
18173            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18174        })
18175        .unwrap()
18176        .await
18177        .unwrap()
18178        .downcast::<Editor>()
18179        .unwrap();
18180
18181    cx.executor().start_waiting();
18182    let fake_server = fake_servers.next().await.unwrap();
18183
18184    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18185        |params, _| async move {
18186            assert_eq!(
18187                params.text_document_position.text_document.uri,
18188                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18189            );
18190            assert_eq!(
18191                params.text_document_position.position,
18192                lsp::Position::new(0, 21),
18193            );
18194
18195            Ok(Some(vec![lsp::TextEdit {
18196                new_text: "]".to_string(),
18197                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18198            }]))
18199        },
18200    );
18201
18202    editor_handle.update_in(cx, |editor, window, cx| {
18203        window.focus(&editor.focus_handle(cx), cx);
18204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18205            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18206        });
18207        editor.handle_input("{", window, cx);
18208    });
18209
18210    cx.executor().run_until_parked();
18211
18212    buffer.update(cx, |buffer, _| {
18213        assert_eq!(
18214            buffer.text(),
18215            "fn main() { let a = {5}; }",
18216            "No extra braces from on type formatting should appear in the buffer"
18217        )
18218    });
18219}
18220
18221#[gpui::test(iterations = 20, seeds(31))]
18222async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18223    init_test(cx, |_| {});
18224
18225    let mut cx = EditorLspTestContext::new_rust(
18226        lsp::ServerCapabilities {
18227            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18228                first_trigger_character: ".".to_string(),
18229                more_trigger_character: None,
18230            }),
18231            ..Default::default()
18232        },
18233        cx,
18234    )
18235    .await;
18236
18237    cx.update_buffer(|buffer, _| {
18238        // This causes autoindent to be async.
18239        buffer.set_sync_parse_timeout(Duration::ZERO)
18240    });
18241
18242    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18243    cx.simulate_keystroke("\n");
18244    cx.run_until_parked();
18245
18246    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18247    let mut request =
18248        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18249            let buffer_cloned = buffer_cloned.clone();
18250            async move {
18251                buffer_cloned.update(&mut cx, |buffer, _| {
18252                    assert_eq!(
18253                        buffer.text(),
18254                        "fn c() {\n    d()\n        .\n}\n",
18255                        "OnTypeFormatting should triggered after autoindent applied"
18256                    )
18257                })?;
18258
18259                Ok(Some(vec![]))
18260            }
18261        });
18262
18263    cx.simulate_keystroke(".");
18264    cx.run_until_parked();
18265
18266    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18267    assert!(request.next().await.is_some());
18268    request.close();
18269    assert!(request.next().await.is_none());
18270}
18271
18272#[gpui::test]
18273async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18274    init_test(cx, |_| {});
18275
18276    let fs = FakeFs::new(cx.executor());
18277    fs.insert_tree(
18278        path!("/a"),
18279        json!({
18280            "main.rs": "fn main() { let a = 5; }",
18281            "other.rs": "// Test file",
18282        }),
18283    )
18284    .await;
18285
18286    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18287
18288    let server_restarts = Arc::new(AtomicUsize::new(0));
18289    let closure_restarts = Arc::clone(&server_restarts);
18290    let language_server_name = "test language server";
18291    let language_name: LanguageName = "Rust".into();
18292
18293    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18294    language_registry.add(Arc::new(Language::new(
18295        LanguageConfig {
18296            name: language_name.clone(),
18297            matcher: LanguageMatcher {
18298                path_suffixes: vec!["rs".to_string()],
18299                ..Default::default()
18300            },
18301            ..Default::default()
18302        },
18303        Some(tree_sitter_rust::LANGUAGE.into()),
18304    )));
18305    let mut fake_servers = language_registry.register_fake_lsp(
18306        "Rust",
18307        FakeLspAdapter {
18308            name: language_server_name,
18309            initialization_options: Some(json!({
18310                "testOptionValue": true
18311            })),
18312            initializer: Some(Box::new(move |fake_server| {
18313                let task_restarts = Arc::clone(&closure_restarts);
18314                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18315                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18316                    futures::future::ready(Ok(()))
18317                });
18318            })),
18319            ..Default::default()
18320        },
18321    );
18322
18323    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18324    let _buffer = project
18325        .update(cx, |project, cx| {
18326            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18327        })
18328        .await
18329        .unwrap();
18330    let _fake_server = fake_servers.next().await.unwrap();
18331    update_test_language_settings(cx, |language_settings| {
18332        language_settings.languages.0.insert(
18333            language_name.clone().0,
18334            LanguageSettingsContent {
18335                tab_size: NonZeroU32::new(8),
18336                ..Default::default()
18337            },
18338        );
18339    });
18340    cx.executor().run_until_parked();
18341    assert_eq!(
18342        server_restarts.load(atomic::Ordering::Acquire),
18343        0,
18344        "Should not restart LSP server on an unrelated change"
18345    );
18346
18347    update_test_project_settings(cx, |project_settings| {
18348        project_settings.lsp.insert(
18349            "Some other server name".into(),
18350            LspSettings {
18351                binary: None,
18352                settings: None,
18353                initialization_options: Some(json!({
18354                    "some other init value": false
18355                })),
18356                enable_lsp_tasks: false,
18357                fetch: None,
18358            },
18359        );
18360    });
18361    cx.executor().run_until_parked();
18362    assert_eq!(
18363        server_restarts.load(atomic::Ordering::Acquire),
18364        0,
18365        "Should not restart LSP server on an unrelated LSP settings change"
18366    );
18367
18368    update_test_project_settings(cx, |project_settings| {
18369        project_settings.lsp.insert(
18370            language_server_name.into(),
18371            LspSettings {
18372                binary: None,
18373                settings: None,
18374                initialization_options: Some(json!({
18375                    "anotherInitValue": false
18376                })),
18377                enable_lsp_tasks: false,
18378                fetch: None,
18379            },
18380        );
18381    });
18382    cx.executor().run_until_parked();
18383    assert_eq!(
18384        server_restarts.load(atomic::Ordering::Acquire),
18385        1,
18386        "Should restart LSP server on a related LSP settings change"
18387    );
18388
18389    update_test_project_settings(cx, |project_settings| {
18390        project_settings.lsp.insert(
18391            language_server_name.into(),
18392            LspSettings {
18393                binary: None,
18394                settings: None,
18395                initialization_options: Some(json!({
18396                    "anotherInitValue": false
18397                })),
18398                enable_lsp_tasks: false,
18399                fetch: None,
18400            },
18401        );
18402    });
18403    cx.executor().run_until_parked();
18404    assert_eq!(
18405        server_restarts.load(atomic::Ordering::Acquire),
18406        1,
18407        "Should not restart LSP server on a related LSP settings change that is the same"
18408    );
18409
18410    update_test_project_settings(cx, |project_settings| {
18411        project_settings.lsp.insert(
18412            language_server_name.into(),
18413            LspSettings {
18414                binary: None,
18415                settings: None,
18416                initialization_options: None,
18417                enable_lsp_tasks: false,
18418                fetch: None,
18419            },
18420        );
18421    });
18422    cx.executor().run_until_parked();
18423    assert_eq!(
18424        server_restarts.load(atomic::Ordering::Acquire),
18425        2,
18426        "Should restart LSP server on another related LSP settings change"
18427    );
18428}
18429
18430#[gpui::test]
18431async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18432    init_test(cx, |_| {});
18433
18434    let mut cx = EditorLspTestContext::new_rust(
18435        lsp::ServerCapabilities {
18436            completion_provider: Some(lsp::CompletionOptions {
18437                trigger_characters: Some(vec![".".to_string()]),
18438                resolve_provider: Some(true),
18439                ..Default::default()
18440            }),
18441            ..Default::default()
18442        },
18443        cx,
18444    )
18445    .await;
18446
18447    cx.set_state("fn main() { let a = 2ˇ; }");
18448    cx.simulate_keystroke(".");
18449    let completion_item = lsp::CompletionItem {
18450        label: "some".into(),
18451        kind: Some(lsp::CompletionItemKind::SNIPPET),
18452        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18453        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18454            kind: lsp::MarkupKind::Markdown,
18455            value: "```rust\nSome(2)\n```".to_string(),
18456        })),
18457        deprecated: Some(false),
18458        sort_text: Some("fffffff2".to_string()),
18459        filter_text: Some("some".to_string()),
18460        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18461        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18462            range: lsp::Range {
18463                start: lsp::Position {
18464                    line: 0,
18465                    character: 22,
18466                },
18467                end: lsp::Position {
18468                    line: 0,
18469                    character: 22,
18470                },
18471            },
18472            new_text: "Some(2)".to_string(),
18473        })),
18474        additional_text_edits: Some(vec![lsp::TextEdit {
18475            range: lsp::Range {
18476                start: lsp::Position {
18477                    line: 0,
18478                    character: 20,
18479                },
18480                end: lsp::Position {
18481                    line: 0,
18482                    character: 22,
18483                },
18484            },
18485            new_text: "".to_string(),
18486        }]),
18487        ..Default::default()
18488    };
18489
18490    let closure_completion_item = completion_item.clone();
18491    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18492        let task_completion_item = closure_completion_item.clone();
18493        async move {
18494            Ok(Some(lsp::CompletionResponse::Array(vec![
18495                task_completion_item,
18496            ])))
18497        }
18498    });
18499
18500    request.next().await;
18501
18502    cx.condition(|editor, _| editor.context_menu_visible())
18503        .await;
18504    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18505        editor
18506            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18507            .unwrap()
18508    });
18509    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18510
18511    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18512        let task_completion_item = completion_item.clone();
18513        async move { Ok(task_completion_item) }
18514    })
18515    .next()
18516    .await
18517    .unwrap();
18518    apply_additional_edits.await.unwrap();
18519    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18520}
18521
18522#[gpui::test]
18523async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18524    init_test(cx, |_| {});
18525
18526    let mut cx = EditorLspTestContext::new_rust(
18527        lsp::ServerCapabilities {
18528            completion_provider: Some(lsp::CompletionOptions {
18529                trigger_characters: Some(vec![".".to_string()]),
18530                resolve_provider: Some(true),
18531                ..Default::default()
18532            }),
18533            ..Default::default()
18534        },
18535        cx,
18536    )
18537    .await;
18538
18539    cx.set_state("fn main() { let a = 2ˇ; }");
18540    cx.simulate_keystroke(".");
18541
18542    let item1 = lsp::CompletionItem {
18543        label: "method id()".to_string(),
18544        filter_text: Some("id".to_string()),
18545        detail: None,
18546        documentation: None,
18547        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18548            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18549            new_text: ".id".to_string(),
18550        })),
18551        ..lsp::CompletionItem::default()
18552    };
18553
18554    let item2 = lsp::CompletionItem {
18555        label: "other".to_string(),
18556        filter_text: Some("other".to_string()),
18557        detail: None,
18558        documentation: None,
18559        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18560            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18561            new_text: ".other".to_string(),
18562        })),
18563        ..lsp::CompletionItem::default()
18564    };
18565
18566    let item1 = item1.clone();
18567    cx.set_request_handler::<lsp::request::Completion, _, _>({
18568        let item1 = item1.clone();
18569        move |_, _, _| {
18570            let item1 = item1.clone();
18571            let item2 = item2.clone();
18572            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18573        }
18574    })
18575    .next()
18576    .await;
18577
18578    cx.condition(|editor, _| editor.context_menu_visible())
18579        .await;
18580    cx.update_editor(|editor, _, _| {
18581        let context_menu = editor.context_menu.borrow_mut();
18582        let context_menu = context_menu
18583            .as_ref()
18584            .expect("Should have the context menu deployed");
18585        match context_menu {
18586            CodeContextMenu::Completions(completions_menu) => {
18587                let completions = completions_menu.completions.borrow_mut();
18588                assert_eq!(
18589                    completions
18590                        .iter()
18591                        .map(|completion| &completion.label.text)
18592                        .collect::<Vec<_>>(),
18593                    vec!["method id()", "other"]
18594                )
18595            }
18596            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18597        }
18598    });
18599
18600    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18601        let item1 = item1.clone();
18602        move |_, item_to_resolve, _| {
18603            let item1 = item1.clone();
18604            async move {
18605                if item1 == item_to_resolve {
18606                    Ok(lsp::CompletionItem {
18607                        label: "method id()".to_string(),
18608                        filter_text: Some("id".to_string()),
18609                        detail: Some("Now resolved!".to_string()),
18610                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18611                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18612                            range: lsp::Range::new(
18613                                lsp::Position::new(0, 22),
18614                                lsp::Position::new(0, 22),
18615                            ),
18616                            new_text: ".id".to_string(),
18617                        })),
18618                        ..lsp::CompletionItem::default()
18619                    })
18620                } else {
18621                    Ok(item_to_resolve)
18622                }
18623            }
18624        }
18625    })
18626    .next()
18627    .await
18628    .unwrap();
18629    cx.run_until_parked();
18630
18631    cx.update_editor(|editor, window, cx| {
18632        editor.context_menu_next(&Default::default(), window, cx);
18633    });
18634
18635    cx.update_editor(|editor, _, _| {
18636        let context_menu = editor.context_menu.borrow_mut();
18637        let context_menu = context_menu
18638            .as_ref()
18639            .expect("Should have the context menu deployed");
18640        match context_menu {
18641            CodeContextMenu::Completions(completions_menu) => {
18642                let completions = completions_menu.completions.borrow_mut();
18643                assert_eq!(
18644                    completions
18645                        .iter()
18646                        .map(|completion| &completion.label.text)
18647                        .collect::<Vec<_>>(),
18648                    vec!["method id() Now resolved!", "other"],
18649                    "Should update first completion label, but not second as the filter text did not match."
18650                );
18651            }
18652            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18653        }
18654    });
18655}
18656
18657#[gpui::test]
18658async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18659    init_test(cx, |_| {});
18660    let mut cx = EditorLspTestContext::new_rust(
18661        lsp::ServerCapabilities {
18662            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18663            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18664            completion_provider: Some(lsp::CompletionOptions {
18665                resolve_provider: Some(true),
18666                ..Default::default()
18667            }),
18668            ..Default::default()
18669        },
18670        cx,
18671    )
18672    .await;
18673    cx.set_state(indoc! {"
18674        struct TestStruct {
18675            field: i32
18676        }
18677
18678        fn mainˇ() {
18679            let unused_var = 42;
18680            let test_struct = TestStruct { field: 42 };
18681        }
18682    "});
18683    let symbol_range = cx.lsp_range(indoc! {"
18684        struct TestStruct {
18685            field: i32
18686        }
18687
18688        «fn main»() {
18689            let unused_var = 42;
18690            let test_struct = TestStruct { field: 42 };
18691        }
18692    "});
18693    let mut hover_requests =
18694        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18695            Ok(Some(lsp::Hover {
18696                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18697                    kind: lsp::MarkupKind::Markdown,
18698                    value: "Function documentation".to_string(),
18699                }),
18700                range: Some(symbol_range),
18701            }))
18702        });
18703
18704    // Case 1: Test that code action menu hide hover popover
18705    cx.dispatch_action(Hover);
18706    hover_requests.next().await;
18707    cx.condition(|editor, _| editor.hover_state.visible()).await;
18708    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18709        move |_, _, _| async move {
18710            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18711                lsp::CodeAction {
18712                    title: "Remove unused variable".to_string(),
18713                    kind: Some(CodeActionKind::QUICKFIX),
18714                    edit: Some(lsp::WorkspaceEdit {
18715                        changes: Some(
18716                            [(
18717                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18718                                vec![lsp::TextEdit {
18719                                    range: lsp::Range::new(
18720                                        lsp::Position::new(5, 4),
18721                                        lsp::Position::new(5, 27),
18722                                    ),
18723                                    new_text: "".to_string(),
18724                                }],
18725                            )]
18726                            .into_iter()
18727                            .collect(),
18728                        ),
18729                        ..Default::default()
18730                    }),
18731                    ..Default::default()
18732                },
18733            )]))
18734        },
18735    );
18736    cx.update_editor(|editor, window, cx| {
18737        editor.toggle_code_actions(
18738            &ToggleCodeActions {
18739                deployed_from: None,
18740                quick_launch: false,
18741            },
18742            window,
18743            cx,
18744        );
18745    });
18746    code_action_requests.next().await;
18747    cx.run_until_parked();
18748    cx.condition(|editor, _| editor.context_menu_visible())
18749        .await;
18750    cx.update_editor(|editor, _, _| {
18751        assert!(
18752            !editor.hover_state.visible(),
18753            "Hover popover should be hidden when code action menu is shown"
18754        );
18755        // Hide code actions
18756        editor.context_menu.take();
18757    });
18758
18759    // Case 2: Test that code completions hide hover popover
18760    cx.dispatch_action(Hover);
18761    hover_requests.next().await;
18762    cx.condition(|editor, _| editor.hover_state.visible()).await;
18763    let counter = Arc::new(AtomicUsize::new(0));
18764    let mut completion_requests =
18765        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18766            let counter = counter.clone();
18767            async move {
18768                counter.fetch_add(1, atomic::Ordering::Release);
18769                Ok(Some(lsp::CompletionResponse::Array(vec![
18770                    lsp::CompletionItem {
18771                        label: "main".into(),
18772                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18773                        detail: Some("() -> ()".to_string()),
18774                        ..Default::default()
18775                    },
18776                    lsp::CompletionItem {
18777                        label: "TestStruct".into(),
18778                        kind: Some(lsp::CompletionItemKind::STRUCT),
18779                        detail: Some("struct TestStruct".to_string()),
18780                        ..Default::default()
18781                    },
18782                ])))
18783            }
18784        });
18785    cx.update_editor(|editor, window, cx| {
18786        editor.show_completions(&ShowCompletions, window, cx);
18787    });
18788    completion_requests.next().await;
18789    cx.condition(|editor, _| editor.context_menu_visible())
18790        .await;
18791    cx.update_editor(|editor, _, _| {
18792        assert!(
18793            !editor.hover_state.visible(),
18794            "Hover popover should be hidden when completion menu is shown"
18795        );
18796    });
18797}
18798
18799#[gpui::test]
18800async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18801    init_test(cx, |_| {});
18802
18803    let mut cx = EditorLspTestContext::new_rust(
18804        lsp::ServerCapabilities {
18805            completion_provider: Some(lsp::CompletionOptions {
18806                trigger_characters: Some(vec![".".to_string()]),
18807                resolve_provider: Some(true),
18808                ..Default::default()
18809            }),
18810            ..Default::default()
18811        },
18812        cx,
18813    )
18814    .await;
18815
18816    cx.set_state("fn main() { let a = 2ˇ; }");
18817    cx.simulate_keystroke(".");
18818
18819    let unresolved_item_1 = lsp::CompletionItem {
18820        label: "id".to_string(),
18821        filter_text: Some("id".to_string()),
18822        detail: None,
18823        documentation: None,
18824        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18825            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18826            new_text: ".id".to_string(),
18827        })),
18828        ..lsp::CompletionItem::default()
18829    };
18830    let resolved_item_1 = lsp::CompletionItem {
18831        additional_text_edits: Some(vec![lsp::TextEdit {
18832            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18833            new_text: "!!".to_string(),
18834        }]),
18835        ..unresolved_item_1.clone()
18836    };
18837    let unresolved_item_2 = lsp::CompletionItem {
18838        label: "other".to_string(),
18839        filter_text: Some("other".to_string()),
18840        detail: None,
18841        documentation: None,
18842        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18843            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18844            new_text: ".other".to_string(),
18845        })),
18846        ..lsp::CompletionItem::default()
18847    };
18848    let resolved_item_2 = lsp::CompletionItem {
18849        additional_text_edits: Some(vec![lsp::TextEdit {
18850            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18851            new_text: "??".to_string(),
18852        }]),
18853        ..unresolved_item_2.clone()
18854    };
18855
18856    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18857    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18858    cx.lsp
18859        .server
18860        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18861            let unresolved_item_1 = unresolved_item_1.clone();
18862            let resolved_item_1 = resolved_item_1.clone();
18863            let unresolved_item_2 = unresolved_item_2.clone();
18864            let resolved_item_2 = resolved_item_2.clone();
18865            let resolve_requests_1 = resolve_requests_1.clone();
18866            let resolve_requests_2 = resolve_requests_2.clone();
18867            move |unresolved_request, _| {
18868                let unresolved_item_1 = unresolved_item_1.clone();
18869                let resolved_item_1 = resolved_item_1.clone();
18870                let unresolved_item_2 = unresolved_item_2.clone();
18871                let resolved_item_2 = resolved_item_2.clone();
18872                let resolve_requests_1 = resolve_requests_1.clone();
18873                let resolve_requests_2 = resolve_requests_2.clone();
18874                async move {
18875                    if unresolved_request == unresolved_item_1 {
18876                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18877                        Ok(resolved_item_1.clone())
18878                    } else if unresolved_request == unresolved_item_2 {
18879                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18880                        Ok(resolved_item_2.clone())
18881                    } else {
18882                        panic!("Unexpected completion item {unresolved_request:?}")
18883                    }
18884                }
18885            }
18886        })
18887        .detach();
18888
18889    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18890        let unresolved_item_1 = unresolved_item_1.clone();
18891        let unresolved_item_2 = unresolved_item_2.clone();
18892        async move {
18893            Ok(Some(lsp::CompletionResponse::Array(vec![
18894                unresolved_item_1,
18895                unresolved_item_2,
18896            ])))
18897        }
18898    })
18899    .next()
18900    .await;
18901
18902    cx.condition(|editor, _| editor.context_menu_visible())
18903        .await;
18904    cx.update_editor(|editor, _, _| {
18905        let context_menu = editor.context_menu.borrow_mut();
18906        let context_menu = context_menu
18907            .as_ref()
18908            .expect("Should have the context menu deployed");
18909        match context_menu {
18910            CodeContextMenu::Completions(completions_menu) => {
18911                let completions = completions_menu.completions.borrow_mut();
18912                assert_eq!(
18913                    completions
18914                        .iter()
18915                        .map(|completion| &completion.label.text)
18916                        .collect::<Vec<_>>(),
18917                    vec!["id", "other"]
18918                )
18919            }
18920            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18921        }
18922    });
18923    cx.run_until_parked();
18924
18925    cx.update_editor(|editor, window, cx| {
18926        editor.context_menu_next(&ContextMenuNext, window, cx);
18927    });
18928    cx.run_until_parked();
18929    cx.update_editor(|editor, window, cx| {
18930        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18931    });
18932    cx.run_until_parked();
18933    cx.update_editor(|editor, window, cx| {
18934        editor.context_menu_next(&ContextMenuNext, window, cx);
18935    });
18936    cx.run_until_parked();
18937    cx.update_editor(|editor, window, cx| {
18938        editor
18939            .compose_completion(&ComposeCompletion::default(), window, cx)
18940            .expect("No task returned")
18941    })
18942    .await
18943    .expect("Completion failed");
18944    cx.run_until_parked();
18945
18946    cx.update_editor(|editor, _, cx| {
18947        assert_eq!(
18948            resolve_requests_1.load(atomic::Ordering::Acquire),
18949            1,
18950            "Should always resolve once despite multiple selections"
18951        );
18952        assert_eq!(
18953            resolve_requests_2.load(atomic::Ordering::Acquire),
18954            1,
18955            "Should always resolve once after multiple selections and applying the completion"
18956        );
18957        assert_eq!(
18958            editor.text(cx),
18959            "fn main() { let a = ??.other; }",
18960            "Should use resolved data when applying the completion"
18961        );
18962    });
18963}
18964
18965#[gpui::test]
18966async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18967    init_test(cx, |_| {});
18968
18969    let item_0 = lsp::CompletionItem {
18970        label: "abs".into(),
18971        insert_text: Some("abs".into()),
18972        data: Some(json!({ "very": "special"})),
18973        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18974        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18975            lsp::InsertReplaceEdit {
18976                new_text: "abs".to_string(),
18977                insert: lsp::Range::default(),
18978                replace: lsp::Range::default(),
18979            },
18980        )),
18981        ..lsp::CompletionItem::default()
18982    };
18983    let items = iter::once(item_0.clone())
18984        .chain((11..51).map(|i| lsp::CompletionItem {
18985            label: format!("item_{}", i),
18986            insert_text: Some(format!("item_{}", i)),
18987            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18988            ..lsp::CompletionItem::default()
18989        }))
18990        .collect::<Vec<_>>();
18991
18992    let default_commit_characters = vec!["?".to_string()];
18993    let default_data = json!({ "default": "data"});
18994    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18995    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18996    let default_edit_range = lsp::Range {
18997        start: lsp::Position {
18998            line: 0,
18999            character: 5,
19000        },
19001        end: lsp::Position {
19002            line: 0,
19003            character: 5,
19004        },
19005    };
19006
19007    let mut cx = EditorLspTestContext::new_rust(
19008        lsp::ServerCapabilities {
19009            completion_provider: Some(lsp::CompletionOptions {
19010                trigger_characters: Some(vec![".".to_string()]),
19011                resolve_provider: Some(true),
19012                ..Default::default()
19013            }),
19014            ..Default::default()
19015        },
19016        cx,
19017    )
19018    .await;
19019
19020    cx.set_state("fn main() { let a = 2ˇ; }");
19021    cx.simulate_keystroke(".");
19022
19023    let completion_data = default_data.clone();
19024    let completion_characters = default_commit_characters.clone();
19025    let completion_items = items.clone();
19026    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19027        let default_data = completion_data.clone();
19028        let default_commit_characters = completion_characters.clone();
19029        let items = completion_items.clone();
19030        async move {
19031            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19032                items,
19033                item_defaults: Some(lsp::CompletionListItemDefaults {
19034                    data: Some(default_data.clone()),
19035                    commit_characters: Some(default_commit_characters.clone()),
19036                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19037                        default_edit_range,
19038                    )),
19039                    insert_text_format: Some(default_insert_text_format),
19040                    insert_text_mode: Some(default_insert_text_mode),
19041                }),
19042                ..lsp::CompletionList::default()
19043            })))
19044        }
19045    })
19046    .next()
19047    .await;
19048
19049    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19050    cx.lsp
19051        .server
19052        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19053            let closure_resolved_items = resolved_items.clone();
19054            move |item_to_resolve, _| {
19055                let closure_resolved_items = closure_resolved_items.clone();
19056                async move {
19057                    closure_resolved_items.lock().push(item_to_resolve.clone());
19058                    Ok(item_to_resolve)
19059                }
19060            }
19061        })
19062        .detach();
19063
19064    cx.condition(|editor, _| editor.context_menu_visible())
19065        .await;
19066    cx.run_until_parked();
19067    cx.update_editor(|editor, _, _| {
19068        let menu = editor.context_menu.borrow_mut();
19069        match menu.as_ref().expect("should have the completions menu") {
19070            CodeContextMenu::Completions(completions_menu) => {
19071                assert_eq!(
19072                    completions_menu
19073                        .entries
19074                        .borrow()
19075                        .iter()
19076                        .map(|mat| mat.string.clone())
19077                        .collect::<Vec<String>>(),
19078                    items
19079                        .iter()
19080                        .map(|completion| completion.label.clone())
19081                        .collect::<Vec<String>>()
19082                );
19083            }
19084            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19085        }
19086    });
19087    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19088    // with 4 from the end.
19089    assert_eq!(
19090        *resolved_items.lock(),
19091        [&items[0..16], &items[items.len() - 4..items.len()]]
19092            .concat()
19093            .iter()
19094            .cloned()
19095            .map(|mut item| {
19096                if item.data.is_none() {
19097                    item.data = Some(default_data.clone());
19098                }
19099                item
19100            })
19101            .collect::<Vec<lsp::CompletionItem>>(),
19102        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19103    );
19104    resolved_items.lock().clear();
19105
19106    cx.update_editor(|editor, window, cx| {
19107        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19108    });
19109    cx.run_until_parked();
19110    // Completions that have already been resolved are skipped.
19111    assert_eq!(
19112        *resolved_items.lock(),
19113        items[items.len() - 17..items.len() - 4]
19114            .iter()
19115            .cloned()
19116            .map(|mut item| {
19117                if item.data.is_none() {
19118                    item.data = Some(default_data.clone());
19119                }
19120                item
19121            })
19122            .collect::<Vec<lsp::CompletionItem>>()
19123    );
19124    resolved_items.lock().clear();
19125}
19126
19127#[gpui::test]
19128async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19129    init_test(cx, |_| {});
19130
19131    let mut cx = EditorLspTestContext::new(
19132        Language::new(
19133            LanguageConfig {
19134                matcher: LanguageMatcher {
19135                    path_suffixes: vec!["jsx".into()],
19136                    ..Default::default()
19137                },
19138                overrides: [(
19139                    "element".into(),
19140                    LanguageConfigOverride {
19141                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19142                        ..Default::default()
19143                    },
19144                )]
19145                .into_iter()
19146                .collect(),
19147                ..Default::default()
19148            },
19149            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19150        )
19151        .with_override_query("(jsx_self_closing_element) @element")
19152        .unwrap(),
19153        lsp::ServerCapabilities {
19154            completion_provider: Some(lsp::CompletionOptions {
19155                trigger_characters: Some(vec![":".to_string()]),
19156                ..Default::default()
19157            }),
19158            ..Default::default()
19159        },
19160        cx,
19161    )
19162    .await;
19163
19164    cx.lsp
19165        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19166            Ok(Some(lsp::CompletionResponse::Array(vec![
19167                lsp::CompletionItem {
19168                    label: "bg-blue".into(),
19169                    ..Default::default()
19170                },
19171                lsp::CompletionItem {
19172                    label: "bg-red".into(),
19173                    ..Default::default()
19174                },
19175                lsp::CompletionItem {
19176                    label: "bg-yellow".into(),
19177                    ..Default::default()
19178                },
19179            ])))
19180        });
19181
19182    cx.set_state(r#"<p class="bgˇ" />"#);
19183
19184    // Trigger completion when typing a dash, because the dash is an extra
19185    // word character in the 'element' scope, which contains the cursor.
19186    cx.simulate_keystroke("-");
19187    cx.executor().run_until_parked();
19188    cx.update_editor(|editor, _, _| {
19189        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19190        {
19191            assert_eq!(
19192                completion_menu_entries(menu),
19193                &["bg-blue", "bg-red", "bg-yellow"]
19194            );
19195        } else {
19196            panic!("expected completion menu to be open");
19197        }
19198    });
19199
19200    cx.simulate_keystroke("l");
19201    cx.executor().run_until_parked();
19202    cx.update_editor(|editor, _, _| {
19203        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19204        {
19205            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19206        } else {
19207            panic!("expected completion menu to be open");
19208        }
19209    });
19210
19211    // When filtering completions, consider the character after the '-' to
19212    // be the start of a subword.
19213    cx.set_state(r#"<p class="yelˇ" />"#);
19214    cx.simulate_keystroke("l");
19215    cx.executor().run_until_parked();
19216    cx.update_editor(|editor, _, _| {
19217        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19218        {
19219            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19220        } else {
19221            panic!("expected completion menu to be open");
19222        }
19223    });
19224}
19225
19226fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19227    let entries = menu.entries.borrow();
19228    entries.iter().map(|mat| mat.string.clone()).collect()
19229}
19230
19231#[gpui::test]
19232async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19233    init_test(cx, |settings| {
19234        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19235    });
19236
19237    let fs = FakeFs::new(cx.executor());
19238    fs.insert_file(path!("/file.ts"), Default::default()).await;
19239
19240    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19241    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19242
19243    language_registry.add(Arc::new(Language::new(
19244        LanguageConfig {
19245            name: "TypeScript".into(),
19246            matcher: LanguageMatcher {
19247                path_suffixes: vec!["ts".to_string()],
19248                ..Default::default()
19249            },
19250            ..Default::default()
19251        },
19252        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19253    )));
19254    update_test_language_settings(cx, |settings| {
19255        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19256    });
19257
19258    let test_plugin = "test_plugin";
19259    let _ = language_registry.register_fake_lsp(
19260        "TypeScript",
19261        FakeLspAdapter {
19262            prettier_plugins: vec![test_plugin],
19263            ..Default::default()
19264        },
19265    );
19266
19267    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19268    let buffer = project
19269        .update(cx, |project, cx| {
19270            project.open_local_buffer(path!("/file.ts"), cx)
19271        })
19272        .await
19273        .unwrap();
19274
19275    let buffer_text = "one\ntwo\nthree\n";
19276    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19277    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19278    editor.update_in(cx, |editor, window, cx| {
19279        editor.set_text(buffer_text, window, cx)
19280    });
19281
19282    editor
19283        .update_in(cx, |editor, window, cx| {
19284            editor.perform_format(
19285                project.clone(),
19286                FormatTrigger::Manual,
19287                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19288                window,
19289                cx,
19290            )
19291        })
19292        .unwrap()
19293        .await;
19294    assert_eq!(
19295        editor.update(cx, |editor, cx| editor.text(cx)),
19296        buffer_text.to_string() + prettier_format_suffix,
19297        "Test prettier formatting was not applied to the original buffer text",
19298    );
19299
19300    update_test_language_settings(cx, |settings| {
19301        settings.defaults.formatter = Some(FormatterList::default())
19302    });
19303    let format = editor.update_in(cx, |editor, window, cx| {
19304        editor.perform_format(
19305            project.clone(),
19306            FormatTrigger::Manual,
19307            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19308            window,
19309            cx,
19310        )
19311    });
19312    format.await.unwrap();
19313    assert_eq!(
19314        editor.update(cx, |editor, cx| editor.text(cx)),
19315        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19316        "Autoformatting (via test prettier) was not applied to the original buffer text",
19317    );
19318}
19319
19320#[gpui::test]
19321async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19322    init_test(cx, |settings| {
19323        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19324    });
19325
19326    let fs = FakeFs::new(cx.executor());
19327    fs.insert_file(path!("/file.settings"), Default::default())
19328        .await;
19329
19330    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19331    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19332
19333    let ts_lang = Arc::new(Language::new(
19334        LanguageConfig {
19335            name: "TypeScript".into(),
19336            matcher: LanguageMatcher {
19337                path_suffixes: vec!["ts".to_string()],
19338                ..LanguageMatcher::default()
19339            },
19340            prettier_parser_name: Some("typescript".to_string()),
19341            ..LanguageConfig::default()
19342        },
19343        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19344    ));
19345
19346    language_registry.add(ts_lang.clone());
19347
19348    update_test_language_settings(cx, |settings| {
19349        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19350    });
19351
19352    let test_plugin = "test_plugin";
19353    let _ = language_registry.register_fake_lsp(
19354        "TypeScript",
19355        FakeLspAdapter {
19356            prettier_plugins: vec![test_plugin],
19357            ..Default::default()
19358        },
19359    );
19360
19361    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19362    let buffer = project
19363        .update(cx, |project, cx| {
19364            project.open_local_buffer(path!("/file.settings"), cx)
19365        })
19366        .await
19367        .unwrap();
19368
19369    project.update(cx, |project, cx| {
19370        project.set_language_for_buffer(&buffer, ts_lang, cx)
19371    });
19372
19373    let buffer_text = "one\ntwo\nthree\n";
19374    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19375    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19376    editor.update_in(cx, |editor, window, cx| {
19377        editor.set_text(buffer_text, window, cx)
19378    });
19379
19380    editor
19381        .update_in(cx, |editor, window, cx| {
19382            editor.perform_format(
19383                project.clone(),
19384                FormatTrigger::Manual,
19385                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19386                window,
19387                cx,
19388            )
19389        })
19390        .unwrap()
19391        .await;
19392    assert_eq!(
19393        editor.update(cx, |editor, cx| editor.text(cx)),
19394        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19395        "Test prettier formatting was not applied to the original buffer text",
19396    );
19397
19398    update_test_language_settings(cx, |settings| {
19399        settings.defaults.formatter = Some(FormatterList::default())
19400    });
19401    let format = editor.update_in(cx, |editor, window, cx| {
19402        editor.perform_format(
19403            project.clone(),
19404            FormatTrigger::Manual,
19405            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19406            window,
19407            cx,
19408        )
19409    });
19410    format.await.unwrap();
19411
19412    assert_eq!(
19413        editor.update(cx, |editor, cx| editor.text(cx)),
19414        buffer_text.to_string()
19415            + prettier_format_suffix
19416            + "\ntypescript\n"
19417            + prettier_format_suffix
19418            + "\ntypescript",
19419        "Autoformatting (via test prettier) was not applied to the original buffer text",
19420    );
19421}
19422
19423#[gpui::test]
19424async fn test_addition_reverts(cx: &mut TestAppContext) {
19425    init_test(cx, |_| {});
19426    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19427    let base_text = indoc! {r#"
19428        struct Row;
19429        struct Row1;
19430        struct Row2;
19431
19432        struct Row4;
19433        struct Row5;
19434        struct Row6;
19435
19436        struct Row8;
19437        struct Row9;
19438        struct Row10;"#};
19439
19440    // When addition hunks are not adjacent to carets, no hunk revert is performed
19441    assert_hunk_revert(
19442        indoc! {r#"struct Row;
19443                   struct Row1;
19444                   struct Row1.1;
19445                   struct Row1.2;
19446                   struct Row2;ˇ
19447
19448                   struct Row4;
19449                   struct Row5;
19450                   struct Row6;
19451
19452                   struct Row8;
19453                   ˇstruct Row9;
19454                   struct Row9.1;
19455                   struct Row9.2;
19456                   struct Row9.3;
19457                   struct Row10;"#},
19458        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19459        indoc! {r#"struct Row;
19460                   struct Row1;
19461                   struct Row1.1;
19462                   struct Row1.2;
19463                   struct Row2;ˇ
19464
19465                   struct Row4;
19466                   struct Row5;
19467                   struct Row6;
19468
19469                   struct Row8;
19470                   ˇstruct Row9;
19471                   struct Row9.1;
19472                   struct Row9.2;
19473                   struct Row9.3;
19474                   struct Row10;"#},
19475        base_text,
19476        &mut cx,
19477    );
19478    // Same for selections
19479    assert_hunk_revert(
19480        indoc! {r#"struct Row;
19481                   struct Row1;
19482                   struct Row2;
19483                   struct Row2.1;
19484                   struct Row2.2;
19485                   «ˇ
19486                   struct Row4;
19487                   struct» Row5;
19488                   «struct Row6;
19489                   ˇ»
19490                   struct Row9.1;
19491                   struct Row9.2;
19492                   struct Row9.3;
19493                   struct Row8;
19494                   struct Row9;
19495                   struct Row10;"#},
19496        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19497        indoc! {r#"struct Row;
19498                   struct Row1;
19499                   struct Row2;
19500                   struct Row2.1;
19501                   struct Row2.2;
19502                   «ˇ
19503                   struct Row4;
19504                   struct» Row5;
19505                   «struct Row6;
19506                   ˇ»
19507                   struct Row9.1;
19508                   struct Row9.2;
19509                   struct Row9.3;
19510                   struct Row8;
19511                   struct Row9;
19512                   struct Row10;"#},
19513        base_text,
19514        &mut cx,
19515    );
19516
19517    // When carets and selections intersect the addition hunks, those are reverted.
19518    // Adjacent carets got merged.
19519    assert_hunk_revert(
19520        indoc! {r#"struct Row;
19521                   ˇ// something on the top
19522                   struct Row1;
19523                   struct Row2;
19524                   struct Roˇw3.1;
19525                   struct Row2.2;
19526                   struct Row2.3;ˇ
19527
19528                   struct Row4;
19529                   struct ˇRow5.1;
19530                   struct Row5.2;
19531                   struct «Rowˇ»5.3;
19532                   struct Row5;
19533                   struct Row6;
19534                   ˇ
19535                   struct Row9.1;
19536                   struct «Rowˇ»9.2;
19537                   struct «ˇRow»9.3;
19538                   struct Row8;
19539                   struct Row9;
19540                   «ˇ// something on bottom»
19541                   struct Row10;"#},
19542        vec![
19543            DiffHunkStatusKind::Added,
19544            DiffHunkStatusKind::Added,
19545            DiffHunkStatusKind::Added,
19546            DiffHunkStatusKind::Added,
19547            DiffHunkStatusKind::Added,
19548        ],
19549        indoc! {r#"struct Row;
19550                   ˇstruct Row1;
19551                   struct Row2;
19552                   ˇ
19553                   struct Row4;
19554                   ˇstruct Row5;
19555                   struct Row6;
19556                   ˇ
19557                   ˇstruct Row8;
19558                   struct Row9;
19559                   ˇstruct Row10;"#},
19560        base_text,
19561        &mut cx,
19562    );
19563}
19564
19565#[gpui::test]
19566async fn test_modification_reverts(cx: &mut TestAppContext) {
19567    init_test(cx, |_| {});
19568    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19569    let base_text = indoc! {r#"
19570        struct Row;
19571        struct Row1;
19572        struct Row2;
19573
19574        struct Row4;
19575        struct Row5;
19576        struct Row6;
19577
19578        struct Row8;
19579        struct Row9;
19580        struct Row10;"#};
19581
19582    // Modification hunks behave the same as the addition ones.
19583    assert_hunk_revert(
19584        indoc! {r#"struct Row;
19585                   struct Row1;
19586                   struct Row33;
19587                   ˇ
19588                   struct Row4;
19589                   struct Row5;
19590                   struct Row6;
19591                   ˇ
19592                   struct Row99;
19593                   struct Row9;
19594                   struct Row10;"#},
19595        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19596        indoc! {r#"struct Row;
19597                   struct Row1;
19598                   struct Row33;
19599                   ˇ
19600                   struct Row4;
19601                   struct Row5;
19602                   struct Row6;
19603                   ˇ
19604                   struct Row99;
19605                   struct Row9;
19606                   struct Row10;"#},
19607        base_text,
19608        &mut cx,
19609    );
19610    assert_hunk_revert(
19611        indoc! {r#"struct Row;
19612                   struct Row1;
19613                   struct Row33;
19614                   «ˇ
19615                   struct Row4;
19616                   struct» Row5;
19617                   «struct Row6;
19618                   ˇ»
19619                   struct Row99;
19620                   struct Row9;
19621                   struct Row10;"#},
19622        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19623        indoc! {r#"struct Row;
19624                   struct Row1;
19625                   struct Row33;
19626                   «ˇ
19627                   struct Row4;
19628                   struct» Row5;
19629                   «struct Row6;
19630                   ˇ»
19631                   struct Row99;
19632                   struct Row9;
19633                   struct Row10;"#},
19634        base_text,
19635        &mut cx,
19636    );
19637
19638    assert_hunk_revert(
19639        indoc! {r#"ˇstruct Row1.1;
19640                   struct Row1;
19641                   «ˇstr»uct Row22;
19642
19643                   struct ˇRow44;
19644                   struct Row5;
19645                   struct «Rˇ»ow66;ˇ
19646
19647                   «struˇ»ct Row88;
19648                   struct Row9;
19649                   struct Row1011;ˇ"#},
19650        vec![
19651            DiffHunkStatusKind::Modified,
19652            DiffHunkStatusKind::Modified,
19653            DiffHunkStatusKind::Modified,
19654            DiffHunkStatusKind::Modified,
19655            DiffHunkStatusKind::Modified,
19656            DiffHunkStatusKind::Modified,
19657        ],
19658        indoc! {r#"struct Row;
19659                   ˇstruct Row1;
19660                   struct Row2;
19661                   ˇ
19662                   struct Row4;
19663                   ˇstruct Row5;
19664                   struct Row6;
19665                   ˇ
19666                   struct Row8;
19667                   ˇstruct Row9;
19668                   struct Row10;ˇ"#},
19669        base_text,
19670        &mut cx,
19671    );
19672}
19673
19674#[gpui::test]
19675async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19676    init_test(cx, |_| {});
19677    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19678    let base_text = indoc! {r#"
19679        one
19680
19681        two
19682        three
19683        "#};
19684
19685    cx.set_head_text(base_text);
19686    cx.set_state("\nˇ\n");
19687    cx.executor().run_until_parked();
19688    cx.update_editor(|editor, _window, cx| {
19689        editor.expand_selected_diff_hunks(cx);
19690    });
19691    cx.executor().run_until_parked();
19692    cx.update_editor(|editor, window, cx| {
19693        editor.backspace(&Default::default(), window, cx);
19694    });
19695    cx.run_until_parked();
19696    cx.assert_state_with_diff(
19697        indoc! {r#"
19698
19699        - two
19700        - threeˇ
19701        +
19702        "#}
19703        .to_string(),
19704    );
19705}
19706
19707#[gpui::test]
19708async fn test_deletion_reverts(cx: &mut TestAppContext) {
19709    init_test(cx, |_| {});
19710    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19711    let base_text = indoc! {r#"struct Row;
19712struct Row1;
19713struct Row2;
19714
19715struct Row4;
19716struct Row5;
19717struct Row6;
19718
19719struct Row8;
19720struct Row9;
19721struct Row10;"#};
19722
19723    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19724    assert_hunk_revert(
19725        indoc! {r#"struct Row;
19726                   struct Row2;
19727
19728                   ˇstruct Row4;
19729                   struct Row5;
19730                   struct Row6;
19731                   ˇ
19732                   struct Row8;
19733                   struct Row10;"#},
19734        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19735        indoc! {r#"struct Row;
19736                   struct Row2;
19737
19738                   ˇstruct Row4;
19739                   struct Row5;
19740                   struct Row6;
19741                   ˇ
19742                   struct Row8;
19743                   struct Row10;"#},
19744        base_text,
19745        &mut cx,
19746    );
19747    assert_hunk_revert(
19748        indoc! {r#"struct Row;
19749                   struct Row2;
19750
19751                   «ˇstruct Row4;
19752                   struct» Row5;
19753                   «struct Row6;
19754                   ˇ»
19755                   struct Row8;
19756                   struct Row10;"#},
19757        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19758        indoc! {r#"struct Row;
19759                   struct Row2;
19760
19761                   «ˇstruct Row4;
19762                   struct» Row5;
19763                   «struct Row6;
19764                   ˇ»
19765                   struct Row8;
19766                   struct Row10;"#},
19767        base_text,
19768        &mut cx,
19769    );
19770
19771    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19772    assert_hunk_revert(
19773        indoc! {r#"struct Row;
19774                   ˇstruct Row2;
19775
19776                   struct Row4;
19777                   struct Row5;
19778                   struct Row6;
19779
19780                   struct Row8;ˇ
19781                   struct Row10;"#},
19782        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19783        indoc! {r#"struct Row;
19784                   struct Row1;
19785                   ˇstruct Row2;
19786
19787                   struct Row4;
19788                   struct Row5;
19789                   struct Row6;
19790
19791                   struct Row8;ˇ
19792                   struct Row9;
19793                   struct Row10;"#},
19794        base_text,
19795        &mut cx,
19796    );
19797    assert_hunk_revert(
19798        indoc! {r#"struct Row;
19799                   struct Row2«ˇ;
19800                   struct Row4;
19801                   struct» Row5;
19802                   «struct Row6;
19803
19804                   struct Row8;ˇ»
19805                   struct Row10;"#},
19806        vec![
19807            DiffHunkStatusKind::Deleted,
19808            DiffHunkStatusKind::Deleted,
19809            DiffHunkStatusKind::Deleted,
19810        ],
19811        indoc! {r#"struct Row;
19812                   struct Row1;
19813                   struct Row2«ˇ;
19814
19815                   struct Row4;
19816                   struct» Row5;
19817                   «struct Row6;
19818
19819                   struct Row8;ˇ»
19820                   struct Row9;
19821                   struct Row10;"#},
19822        base_text,
19823        &mut cx,
19824    );
19825}
19826
19827#[gpui::test]
19828async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19829    init_test(cx, |_| {});
19830
19831    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19832    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19833    let base_text_3 =
19834        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19835
19836    let text_1 = edit_first_char_of_every_line(base_text_1);
19837    let text_2 = edit_first_char_of_every_line(base_text_2);
19838    let text_3 = edit_first_char_of_every_line(base_text_3);
19839
19840    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19841    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19842    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19843
19844    let multibuffer = cx.new(|cx| {
19845        let mut multibuffer = MultiBuffer::new(ReadWrite);
19846        multibuffer.push_excerpts(
19847            buffer_1.clone(),
19848            [
19849                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19850                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19851                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19852            ],
19853            cx,
19854        );
19855        multibuffer.push_excerpts(
19856            buffer_2.clone(),
19857            [
19858                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19859                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19860                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19861            ],
19862            cx,
19863        );
19864        multibuffer.push_excerpts(
19865            buffer_3.clone(),
19866            [
19867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19870            ],
19871            cx,
19872        );
19873        multibuffer
19874    });
19875
19876    let fs = FakeFs::new(cx.executor());
19877    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19878    let (editor, cx) = cx
19879        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19880    editor.update_in(cx, |editor, _window, cx| {
19881        for (buffer, diff_base) in [
19882            (buffer_1.clone(), base_text_1),
19883            (buffer_2.clone(), base_text_2),
19884            (buffer_3.clone(), base_text_3),
19885        ] {
19886            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19887            editor
19888                .buffer
19889                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19890        }
19891    });
19892    cx.executor().run_until_parked();
19893
19894    editor.update_in(cx, |editor, window, cx| {
19895        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
19896        editor.select_all(&SelectAll, window, cx);
19897        editor.git_restore(&Default::default(), window, cx);
19898    });
19899    cx.executor().run_until_parked();
19900
19901    // When all ranges are selected, all buffer hunks are reverted.
19902    editor.update(cx, |editor, cx| {
19903        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
19904    });
19905    buffer_1.update(cx, |buffer, _| {
19906        assert_eq!(buffer.text(), base_text_1);
19907    });
19908    buffer_2.update(cx, |buffer, _| {
19909        assert_eq!(buffer.text(), base_text_2);
19910    });
19911    buffer_3.update(cx, |buffer, _| {
19912        assert_eq!(buffer.text(), base_text_3);
19913    });
19914
19915    editor.update_in(cx, |editor, window, cx| {
19916        editor.undo(&Default::default(), window, cx);
19917    });
19918
19919    editor.update_in(cx, |editor, window, cx| {
19920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19921            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19922        });
19923        editor.git_restore(&Default::default(), window, cx);
19924    });
19925
19926    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19927    // but not affect buffer_2 and its related excerpts.
19928    editor.update(cx, |editor, cx| {
19929        assert_eq!(
19930            editor.text(cx),
19931            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
19932        );
19933    });
19934    buffer_1.update(cx, |buffer, _| {
19935        assert_eq!(buffer.text(), base_text_1);
19936    });
19937    buffer_2.update(cx, |buffer, _| {
19938        assert_eq!(
19939            buffer.text(),
19940            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19941        );
19942    });
19943    buffer_3.update(cx, |buffer, _| {
19944        assert_eq!(
19945            buffer.text(),
19946            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19947        );
19948    });
19949
19950    fn edit_first_char_of_every_line(text: &str) -> String {
19951        text.split('\n')
19952            .map(|line| format!("X{}", &line[1..]))
19953            .collect::<Vec<_>>()
19954            .join("\n")
19955    }
19956}
19957
19958#[gpui::test]
19959async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19960    init_test(cx, |_| {});
19961
19962    let cols = 4;
19963    let rows = 10;
19964    let sample_text_1 = sample_text(rows, cols, 'a');
19965    assert_eq!(
19966        sample_text_1,
19967        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19968    );
19969    let sample_text_2 = sample_text(rows, cols, 'l');
19970    assert_eq!(
19971        sample_text_2,
19972        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19973    );
19974    let sample_text_3 = sample_text(rows, cols, 'v');
19975    assert_eq!(
19976        sample_text_3,
19977        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19978    );
19979
19980    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19981    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19982    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19983
19984    let multi_buffer = cx.new(|cx| {
19985        let mut multibuffer = MultiBuffer::new(ReadWrite);
19986        multibuffer.push_excerpts(
19987            buffer_1.clone(),
19988            [
19989                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19990                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19991                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19992            ],
19993            cx,
19994        );
19995        multibuffer.push_excerpts(
19996            buffer_2.clone(),
19997            [
19998                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19999                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20000                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20001            ],
20002            cx,
20003        );
20004        multibuffer.push_excerpts(
20005            buffer_3.clone(),
20006            [
20007                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20008                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20009                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20010            ],
20011            cx,
20012        );
20013        multibuffer
20014    });
20015
20016    let fs = FakeFs::new(cx.executor());
20017    fs.insert_tree(
20018        "/a",
20019        json!({
20020            "main.rs": sample_text_1,
20021            "other.rs": sample_text_2,
20022            "lib.rs": sample_text_3,
20023        }),
20024    )
20025    .await;
20026    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20027    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20028    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20029    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20030        Editor::new(
20031            EditorMode::full(),
20032            multi_buffer,
20033            Some(project.clone()),
20034            window,
20035            cx,
20036        )
20037    });
20038    let multibuffer_item_id = workspace
20039        .update(cx, |workspace, window, cx| {
20040            assert!(
20041                workspace.active_item(cx).is_none(),
20042                "active item should be None before the first item is added"
20043            );
20044            workspace.add_item_to_active_pane(
20045                Box::new(multi_buffer_editor.clone()),
20046                None,
20047                true,
20048                window,
20049                cx,
20050            );
20051            let active_item = workspace
20052                .active_item(cx)
20053                .expect("should have an active item after adding the multi buffer");
20054            assert_eq!(
20055                active_item.buffer_kind(cx),
20056                ItemBufferKind::Multibuffer,
20057                "A multi buffer was expected to active after adding"
20058            );
20059            active_item.item_id()
20060        })
20061        .unwrap();
20062    cx.executor().run_until_parked();
20063
20064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20065        editor.change_selections(
20066            SelectionEffects::scroll(Autoscroll::Next),
20067            window,
20068            cx,
20069            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20070        );
20071        editor.open_excerpts(&OpenExcerpts, window, cx);
20072    });
20073    cx.executor().run_until_parked();
20074    let first_item_id = workspace
20075        .update(cx, |workspace, window, cx| {
20076            let active_item = workspace
20077                .active_item(cx)
20078                .expect("should have an active item after navigating into the 1st buffer");
20079            let first_item_id = active_item.item_id();
20080            assert_ne!(
20081                first_item_id, multibuffer_item_id,
20082                "Should navigate into the 1st buffer and activate it"
20083            );
20084            assert_eq!(
20085                active_item.buffer_kind(cx),
20086                ItemBufferKind::Singleton,
20087                "New active item should be a singleton buffer"
20088            );
20089            assert_eq!(
20090                active_item
20091                    .act_as::<Editor>(cx)
20092                    .expect("should have navigated into an editor for the 1st buffer")
20093                    .read(cx)
20094                    .text(cx),
20095                sample_text_1
20096            );
20097
20098            workspace
20099                .go_back(workspace.active_pane().downgrade(), window, cx)
20100                .detach_and_log_err(cx);
20101
20102            first_item_id
20103        })
20104        .unwrap();
20105    cx.executor().run_until_parked();
20106    workspace
20107        .update(cx, |workspace, _, cx| {
20108            let active_item = workspace
20109                .active_item(cx)
20110                .expect("should have an active item after navigating back");
20111            assert_eq!(
20112                active_item.item_id(),
20113                multibuffer_item_id,
20114                "Should navigate back to the multi buffer"
20115            );
20116            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20117        })
20118        .unwrap();
20119
20120    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20121        editor.change_selections(
20122            SelectionEffects::scroll(Autoscroll::Next),
20123            window,
20124            cx,
20125            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20126        );
20127        editor.open_excerpts(&OpenExcerpts, window, cx);
20128    });
20129    cx.executor().run_until_parked();
20130    let second_item_id = workspace
20131        .update(cx, |workspace, window, cx| {
20132            let active_item = workspace
20133                .active_item(cx)
20134                .expect("should have an active item after navigating into the 2nd buffer");
20135            let second_item_id = active_item.item_id();
20136            assert_ne!(
20137                second_item_id, multibuffer_item_id,
20138                "Should navigate away from the multibuffer"
20139            );
20140            assert_ne!(
20141                second_item_id, first_item_id,
20142                "Should navigate into the 2nd buffer and activate it"
20143            );
20144            assert_eq!(
20145                active_item.buffer_kind(cx),
20146                ItemBufferKind::Singleton,
20147                "New active item should be a singleton buffer"
20148            );
20149            assert_eq!(
20150                active_item
20151                    .act_as::<Editor>(cx)
20152                    .expect("should have navigated into an editor")
20153                    .read(cx)
20154                    .text(cx),
20155                sample_text_2
20156            );
20157
20158            workspace
20159                .go_back(workspace.active_pane().downgrade(), window, cx)
20160                .detach_and_log_err(cx);
20161
20162            second_item_id
20163        })
20164        .unwrap();
20165    cx.executor().run_until_parked();
20166    workspace
20167        .update(cx, |workspace, _, cx| {
20168            let active_item = workspace
20169                .active_item(cx)
20170                .expect("should have an active item after navigating back from the 2nd buffer");
20171            assert_eq!(
20172                active_item.item_id(),
20173                multibuffer_item_id,
20174                "Should navigate back from the 2nd buffer to the multi buffer"
20175            );
20176            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20177        })
20178        .unwrap();
20179
20180    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20181        editor.change_selections(
20182            SelectionEffects::scroll(Autoscroll::Next),
20183            window,
20184            cx,
20185            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20186        );
20187        editor.open_excerpts(&OpenExcerpts, window, cx);
20188    });
20189    cx.executor().run_until_parked();
20190    workspace
20191        .update(cx, |workspace, window, cx| {
20192            let active_item = workspace
20193                .active_item(cx)
20194                .expect("should have an active item after navigating into the 3rd buffer");
20195            let third_item_id = active_item.item_id();
20196            assert_ne!(
20197                third_item_id, multibuffer_item_id,
20198                "Should navigate into the 3rd buffer and activate it"
20199            );
20200            assert_ne!(third_item_id, first_item_id);
20201            assert_ne!(third_item_id, second_item_id);
20202            assert_eq!(
20203                active_item.buffer_kind(cx),
20204                ItemBufferKind::Singleton,
20205                "New active item should be a singleton buffer"
20206            );
20207            assert_eq!(
20208                active_item
20209                    .act_as::<Editor>(cx)
20210                    .expect("should have navigated into an editor")
20211                    .read(cx)
20212                    .text(cx),
20213                sample_text_3
20214            );
20215
20216            workspace
20217                .go_back(workspace.active_pane().downgrade(), window, cx)
20218                .detach_and_log_err(cx);
20219        })
20220        .unwrap();
20221    cx.executor().run_until_parked();
20222    workspace
20223        .update(cx, |workspace, _, cx| {
20224            let active_item = workspace
20225                .active_item(cx)
20226                .expect("should have an active item after navigating back from the 3rd buffer");
20227            assert_eq!(
20228                active_item.item_id(),
20229                multibuffer_item_id,
20230                "Should navigate back from the 3rd buffer to the multi buffer"
20231            );
20232            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20233        })
20234        .unwrap();
20235}
20236
20237#[gpui::test]
20238async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20239    init_test(cx, |_| {});
20240
20241    let mut cx = EditorTestContext::new(cx).await;
20242
20243    let diff_base = r#"
20244        use some::mod;
20245
20246        const A: u32 = 42;
20247
20248        fn main() {
20249            println!("hello");
20250
20251            println!("world");
20252        }
20253        "#
20254    .unindent();
20255
20256    cx.set_state(
20257        &r#"
20258        use some::modified;
20259
20260        ˇ
20261        fn main() {
20262            println!("hello there");
20263
20264            println!("around the");
20265            println!("world");
20266        }
20267        "#
20268        .unindent(),
20269    );
20270
20271    cx.set_head_text(&diff_base);
20272    executor.run_until_parked();
20273
20274    cx.update_editor(|editor, window, cx| {
20275        editor.go_to_next_hunk(&GoToHunk, window, cx);
20276        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20277    });
20278    executor.run_until_parked();
20279    cx.assert_state_with_diff(
20280        r#"
20281          use some::modified;
20282
20283
20284          fn main() {
20285        -     println!("hello");
20286        + ˇ    println!("hello there");
20287
20288              println!("around the");
20289              println!("world");
20290          }
20291        "#
20292        .unindent(),
20293    );
20294
20295    cx.update_editor(|editor, window, cx| {
20296        for _ in 0..2 {
20297            editor.go_to_next_hunk(&GoToHunk, window, cx);
20298            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20299        }
20300    });
20301    executor.run_until_parked();
20302    cx.assert_state_with_diff(
20303        r#"
20304        - use some::mod;
20305        + ˇuse some::modified;
20306
20307
20308          fn main() {
20309        -     println!("hello");
20310        +     println!("hello there");
20311
20312        +     println!("around the");
20313              println!("world");
20314          }
20315        "#
20316        .unindent(),
20317    );
20318
20319    cx.update_editor(|editor, window, cx| {
20320        editor.go_to_next_hunk(&GoToHunk, window, cx);
20321        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20322    });
20323    executor.run_until_parked();
20324    cx.assert_state_with_diff(
20325        r#"
20326        - use some::mod;
20327        + use some::modified;
20328
20329        - const A: u32 = 42;
20330          ˇ
20331          fn main() {
20332        -     println!("hello");
20333        +     println!("hello there");
20334
20335        +     println!("around the");
20336              println!("world");
20337          }
20338        "#
20339        .unindent(),
20340    );
20341
20342    cx.update_editor(|editor, window, cx| {
20343        editor.cancel(&Cancel, window, cx);
20344    });
20345
20346    cx.assert_state_with_diff(
20347        r#"
20348          use some::modified;
20349
20350          ˇ
20351          fn main() {
20352              println!("hello there");
20353
20354              println!("around the");
20355              println!("world");
20356          }
20357        "#
20358        .unindent(),
20359    );
20360}
20361
20362#[gpui::test]
20363async fn test_diff_base_change_with_expanded_diff_hunks(
20364    executor: BackgroundExecutor,
20365    cx: &mut TestAppContext,
20366) {
20367    init_test(cx, |_| {});
20368
20369    let mut cx = EditorTestContext::new(cx).await;
20370
20371    let diff_base = r#"
20372        use some::mod1;
20373        use some::mod2;
20374
20375        const A: u32 = 42;
20376        const B: u32 = 42;
20377        const C: u32 = 42;
20378
20379        fn main() {
20380            println!("hello");
20381
20382            println!("world");
20383        }
20384        "#
20385    .unindent();
20386
20387    cx.set_state(
20388        &r#"
20389        use some::mod2;
20390
20391        const A: u32 = 42;
20392        const C: u32 = 42;
20393
20394        fn main(ˇ) {
20395            //println!("hello");
20396
20397            println!("world");
20398            //
20399            //
20400        }
20401        "#
20402        .unindent(),
20403    );
20404
20405    cx.set_head_text(&diff_base);
20406    executor.run_until_parked();
20407
20408    cx.update_editor(|editor, window, cx| {
20409        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20410    });
20411    executor.run_until_parked();
20412    cx.assert_state_with_diff(
20413        r#"
20414        - use some::mod1;
20415          use some::mod2;
20416
20417          const A: u32 = 42;
20418        - const B: u32 = 42;
20419          const C: u32 = 42;
20420
20421          fn main(ˇ) {
20422        -     println!("hello");
20423        +     //println!("hello");
20424
20425              println!("world");
20426        +     //
20427        +     //
20428          }
20429        "#
20430        .unindent(),
20431    );
20432
20433    cx.set_head_text("new diff base!");
20434    executor.run_until_parked();
20435    cx.assert_state_with_diff(
20436        r#"
20437        - new diff base!
20438        + use some::mod2;
20439        +
20440        + const A: u32 = 42;
20441        + const C: u32 = 42;
20442        +
20443        + fn main(ˇ) {
20444        +     //println!("hello");
20445        +
20446        +     println!("world");
20447        +     //
20448        +     //
20449        + }
20450        "#
20451        .unindent(),
20452    );
20453}
20454
20455#[gpui::test]
20456async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20457    init_test(cx, |_| {});
20458
20459    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20460    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20461    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20462    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20463    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20464    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20465
20466    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20467    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20468    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20469
20470    let multi_buffer = cx.new(|cx| {
20471        let mut multibuffer = MultiBuffer::new(ReadWrite);
20472        multibuffer.push_excerpts(
20473            buffer_1.clone(),
20474            [
20475                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20476                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20477                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20478            ],
20479            cx,
20480        );
20481        multibuffer.push_excerpts(
20482            buffer_2.clone(),
20483            [
20484                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20485                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20486                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20487            ],
20488            cx,
20489        );
20490        multibuffer.push_excerpts(
20491            buffer_3.clone(),
20492            [
20493                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20494                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20495                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20496            ],
20497            cx,
20498        );
20499        multibuffer
20500    });
20501
20502    let editor =
20503        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20504    editor
20505        .update(cx, |editor, _window, cx| {
20506            for (buffer, diff_base) in [
20507                (buffer_1.clone(), file_1_old),
20508                (buffer_2.clone(), file_2_old),
20509                (buffer_3.clone(), file_3_old),
20510            ] {
20511                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20512                editor
20513                    .buffer
20514                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20515            }
20516        })
20517        .unwrap();
20518
20519    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20520    cx.run_until_parked();
20521
20522    cx.assert_editor_state(
20523        &"
20524            ˇaaa
20525            ccc
20526            ddd
20527
20528            ggg
20529            hhh
20530
20531
20532            lll
20533            mmm
20534            NNN
20535
20536            qqq
20537            rrr
20538
20539            uuu
20540            111
20541            222
20542            333
20543
20544            666
20545            777
20546
20547            000
20548            !!!"
20549        .unindent(),
20550    );
20551
20552    cx.update_editor(|editor, window, cx| {
20553        editor.select_all(&SelectAll, window, cx);
20554        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20555    });
20556    cx.executor().run_until_parked();
20557
20558    cx.assert_state_with_diff(
20559        "
20560            «aaa
20561          - bbb
20562            ccc
20563            ddd
20564
20565            ggg
20566            hhh
20567
20568
20569            lll
20570            mmm
20571          - nnn
20572          + NNN
20573
20574            qqq
20575            rrr
20576
20577            uuu
20578            111
20579            222
20580            333
20581
20582          + 666
20583            777
20584
20585            000
20586            !!!ˇ»"
20587            .unindent(),
20588    );
20589}
20590
20591#[gpui::test]
20592async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20593    init_test(cx, |_| {});
20594
20595    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20596    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20597
20598    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20599    let multi_buffer = cx.new(|cx| {
20600        let mut multibuffer = MultiBuffer::new(ReadWrite);
20601        multibuffer.push_excerpts(
20602            buffer.clone(),
20603            [
20604                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20605                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20606                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20607            ],
20608            cx,
20609        );
20610        multibuffer
20611    });
20612
20613    let editor =
20614        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20615    editor
20616        .update(cx, |editor, _window, cx| {
20617            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20618            editor
20619                .buffer
20620                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20621        })
20622        .unwrap();
20623
20624    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20625    cx.run_until_parked();
20626
20627    cx.update_editor(|editor, window, cx| {
20628        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20629    });
20630    cx.executor().run_until_parked();
20631
20632    // When the start of a hunk coincides with the start of its excerpt,
20633    // the hunk is expanded. When the start of a hunk is earlier than
20634    // the start of its excerpt, the hunk is not expanded.
20635    cx.assert_state_with_diff(
20636        "
20637            ˇaaa
20638          - bbb
20639          + BBB
20640
20641          - ddd
20642          - eee
20643          + DDD
20644          + EEE
20645            fff
20646
20647            iii
20648        "
20649        .unindent(),
20650    );
20651}
20652
20653#[gpui::test]
20654async fn test_edits_around_expanded_insertion_hunks(
20655    executor: BackgroundExecutor,
20656    cx: &mut TestAppContext,
20657) {
20658    init_test(cx, |_| {});
20659
20660    let mut cx = EditorTestContext::new(cx).await;
20661
20662    let diff_base = r#"
20663        use some::mod1;
20664        use some::mod2;
20665
20666        const A: u32 = 42;
20667
20668        fn main() {
20669            println!("hello");
20670
20671            println!("world");
20672        }
20673        "#
20674    .unindent();
20675    executor.run_until_parked();
20676    cx.set_state(
20677        &r#"
20678        use some::mod1;
20679        use some::mod2;
20680
20681        const A: u32 = 42;
20682        const B: u32 = 42;
20683        const C: u32 = 42;
20684        ˇ
20685
20686        fn main() {
20687            println!("hello");
20688
20689            println!("world");
20690        }
20691        "#
20692        .unindent(),
20693    );
20694
20695    cx.set_head_text(&diff_base);
20696    executor.run_until_parked();
20697
20698    cx.update_editor(|editor, window, cx| {
20699        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20700    });
20701    executor.run_until_parked();
20702
20703    cx.assert_state_with_diff(
20704        r#"
20705        use some::mod1;
20706        use some::mod2;
20707
20708        const A: u32 = 42;
20709      + const B: u32 = 42;
20710      + const C: u32 = 42;
20711      + ˇ
20712
20713        fn main() {
20714            println!("hello");
20715
20716            println!("world");
20717        }
20718      "#
20719        .unindent(),
20720    );
20721
20722    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20723    executor.run_until_parked();
20724
20725    cx.assert_state_with_diff(
20726        r#"
20727        use some::mod1;
20728        use some::mod2;
20729
20730        const A: u32 = 42;
20731      + const B: u32 = 42;
20732      + const C: u32 = 42;
20733      + const D: u32 = 42;
20734      + ˇ
20735
20736        fn main() {
20737            println!("hello");
20738
20739            println!("world");
20740        }
20741      "#
20742        .unindent(),
20743    );
20744
20745    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20746    executor.run_until_parked();
20747
20748    cx.assert_state_with_diff(
20749        r#"
20750        use some::mod1;
20751        use some::mod2;
20752
20753        const A: u32 = 42;
20754      + const B: u32 = 42;
20755      + const C: u32 = 42;
20756      + const D: u32 = 42;
20757      + const E: u32 = 42;
20758      + ˇ
20759
20760        fn main() {
20761            println!("hello");
20762
20763            println!("world");
20764        }
20765      "#
20766        .unindent(),
20767    );
20768
20769    cx.update_editor(|editor, window, cx| {
20770        editor.delete_line(&DeleteLine, window, cx);
20771    });
20772    executor.run_until_parked();
20773
20774    cx.assert_state_with_diff(
20775        r#"
20776        use some::mod1;
20777        use some::mod2;
20778
20779        const A: u32 = 42;
20780      + const B: u32 = 42;
20781      + const C: u32 = 42;
20782      + const D: u32 = 42;
20783      + const E: u32 = 42;
20784        ˇ
20785        fn main() {
20786            println!("hello");
20787
20788            println!("world");
20789        }
20790      "#
20791        .unindent(),
20792    );
20793
20794    cx.update_editor(|editor, window, cx| {
20795        editor.move_up(&MoveUp, window, cx);
20796        editor.delete_line(&DeleteLine, window, cx);
20797        editor.move_up(&MoveUp, window, cx);
20798        editor.delete_line(&DeleteLine, window, cx);
20799        editor.move_up(&MoveUp, window, cx);
20800        editor.delete_line(&DeleteLine, window, cx);
20801    });
20802    executor.run_until_parked();
20803    cx.assert_state_with_diff(
20804        r#"
20805        use some::mod1;
20806        use some::mod2;
20807
20808        const A: u32 = 42;
20809      + const B: u32 = 42;
20810        ˇ
20811        fn main() {
20812            println!("hello");
20813
20814            println!("world");
20815        }
20816      "#
20817        .unindent(),
20818    );
20819
20820    cx.update_editor(|editor, window, cx| {
20821        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20822        editor.delete_line(&DeleteLine, window, cx);
20823    });
20824    executor.run_until_parked();
20825    cx.assert_state_with_diff(
20826        r#"
20827        ˇ
20828        fn main() {
20829            println!("hello");
20830
20831            println!("world");
20832        }
20833      "#
20834        .unindent(),
20835    );
20836}
20837
20838#[gpui::test]
20839async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20840    init_test(cx, |_| {});
20841
20842    let mut cx = EditorTestContext::new(cx).await;
20843    cx.set_head_text(indoc! { "
20844        one
20845        two
20846        three
20847        four
20848        five
20849        "
20850    });
20851    cx.set_state(indoc! { "
20852        one
20853        ˇthree
20854        five
20855    "});
20856    cx.run_until_parked();
20857    cx.update_editor(|editor, window, cx| {
20858        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20859    });
20860    cx.assert_state_with_diff(
20861        indoc! { "
20862        one
20863      - two
20864        ˇthree
20865      - four
20866        five
20867    "}
20868        .to_string(),
20869    );
20870    cx.update_editor(|editor, window, cx| {
20871        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20872    });
20873
20874    cx.assert_state_with_diff(
20875        indoc! { "
20876        one
20877        ˇthree
20878        five
20879    "}
20880        .to_string(),
20881    );
20882
20883    cx.set_state(indoc! { "
20884        one
20885        ˇTWO
20886        three
20887        four
20888        five
20889    "});
20890    cx.run_until_parked();
20891    cx.update_editor(|editor, window, cx| {
20892        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20893    });
20894
20895    cx.assert_state_with_diff(
20896        indoc! { "
20897            one
20898          - two
20899          + ˇTWO
20900            three
20901            four
20902            five
20903        "}
20904        .to_string(),
20905    );
20906    cx.update_editor(|editor, window, cx| {
20907        editor.move_up(&Default::default(), window, cx);
20908        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20909    });
20910    cx.assert_state_with_diff(
20911        indoc! { "
20912            one
20913            ˇTWO
20914            three
20915            four
20916            five
20917        "}
20918        .to_string(),
20919    );
20920}
20921
20922#[gpui::test]
20923async fn test_edits_around_expanded_deletion_hunks(
20924    executor: BackgroundExecutor,
20925    cx: &mut TestAppContext,
20926) {
20927    init_test(cx, |_| {});
20928
20929    let mut cx = EditorTestContext::new(cx).await;
20930
20931    let diff_base = r#"
20932        use some::mod1;
20933        use some::mod2;
20934
20935        const A: u32 = 42;
20936        const B: u32 = 42;
20937        const C: u32 = 42;
20938
20939
20940        fn main() {
20941            println!("hello");
20942
20943            println!("world");
20944        }
20945    "#
20946    .unindent();
20947    executor.run_until_parked();
20948    cx.set_state(
20949        &r#"
20950        use some::mod1;
20951        use some::mod2;
20952
20953        ˇconst B: u32 = 42;
20954        const C: u32 = 42;
20955
20956
20957        fn main() {
20958            println!("hello");
20959
20960            println!("world");
20961        }
20962        "#
20963        .unindent(),
20964    );
20965
20966    cx.set_head_text(&diff_base);
20967    executor.run_until_parked();
20968
20969    cx.update_editor(|editor, window, cx| {
20970        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20971    });
20972    executor.run_until_parked();
20973
20974    cx.assert_state_with_diff(
20975        r#"
20976        use some::mod1;
20977        use some::mod2;
20978
20979      - const A: u32 = 42;
20980        ˇconst B: u32 = 42;
20981        const C: u32 = 42;
20982
20983
20984        fn main() {
20985            println!("hello");
20986
20987            println!("world");
20988        }
20989      "#
20990        .unindent(),
20991    );
20992
20993    cx.update_editor(|editor, window, cx| {
20994        editor.delete_line(&DeleteLine, window, cx);
20995    });
20996    executor.run_until_parked();
20997    cx.assert_state_with_diff(
20998        r#"
20999        use some::mod1;
21000        use some::mod2;
21001
21002      - const A: u32 = 42;
21003      - const B: u32 = 42;
21004        ˇconst C: u32 = 42;
21005
21006
21007        fn main() {
21008            println!("hello");
21009
21010            println!("world");
21011        }
21012      "#
21013        .unindent(),
21014    );
21015
21016    cx.update_editor(|editor, window, cx| {
21017        editor.delete_line(&DeleteLine, window, cx);
21018    });
21019    executor.run_until_parked();
21020    cx.assert_state_with_diff(
21021        r#"
21022        use some::mod1;
21023        use some::mod2;
21024
21025      - const A: u32 = 42;
21026      - const B: u32 = 42;
21027      - const C: u32 = 42;
21028        ˇ
21029
21030        fn main() {
21031            println!("hello");
21032
21033            println!("world");
21034        }
21035      "#
21036        .unindent(),
21037    );
21038
21039    cx.update_editor(|editor, window, cx| {
21040        editor.handle_input("replacement", window, cx);
21041    });
21042    executor.run_until_parked();
21043    cx.assert_state_with_diff(
21044        r#"
21045        use some::mod1;
21046        use some::mod2;
21047
21048      - const A: u32 = 42;
21049      - const B: u32 = 42;
21050      - const C: u32 = 42;
21051      -
21052      + replacementˇ
21053
21054        fn main() {
21055            println!("hello");
21056
21057            println!("world");
21058        }
21059      "#
21060        .unindent(),
21061    );
21062}
21063
21064#[gpui::test]
21065async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21066    init_test(cx, |_| {});
21067
21068    let mut cx = EditorTestContext::new(cx).await;
21069
21070    let base_text = r#"
21071        one
21072        two
21073        three
21074        four
21075        five
21076    "#
21077    .unindent();
21078    executor.run_until_parked();
21079    cx.set_state(
21080        &r#"
21081        one
21082        two
21083        fˇour
21084        five
21085        "#
21086        .unindent(),
21087    );
21088
21089    cx.set_head_text(&base_text);
21090    executor.run_until_parked();
21091
21092    cx.update_editor(|editor, window, cx| {
21093        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21094    });
21095    executor.run_until_parked();
21096
21097    cx.assert_state_with_diff(
21098        r#"
21099          one
21100          two
21101        - three
21102          fˇour
21103          five
21104        "#
21105        .unindent(),
21106    );
21107
21108    cx.update_editor(|editor, window, cx| {
21109        editor.backspace(&Backspace, window, cx);
21110        editor.backspace(&Backspace, window, cx);
21111    });
21112    executor.run_until_parked();
21113    cx.assert_state_with_diff(
21114        r#"
21115          one
21116          two
21117        - threeˇ
21118        - four
21119        + our
21120          five
21121        "#
21122        .unindent(),
21123    );
21124}
21125
21126#[gpui::test]
21127async fn test_edit_after_expanded_modification_hunk(
21128    executor: BackgroundExecutor,
21129    cx: &mut TestAppContext,
21130) {
21131    init_test(cx, |_| {});
21132
21133    let mut cx = EditorTestContext::new(cx).await;
21134
21135    let diff_base = r#"
21136        use some::mod1;
21137        use some::mod2;
21138
21139        const A: u32 = 42;
21140        const B: u32 = 42;
21141        const C: u32 = 42;
21142        const D: u32 = 42;
21143
21144
21145        fn main() {
21146            println!("hello");
21147
21148            println!("world");
21149        }"#
21150    .unindent();
21151
21152    cx.set_state(
21153        &r#"
21154        use some::mod1;
21155        use some::mod2;
21156
21157        const A: u32 = 42;
21158        const B: u32 = 42;
21159        const C: u32 = 43ˇ
21160        const D: u32 = 42;
21161
21162
21163        fn main() {
21164            println!("hello");
21165
21166            println!("world");
21167        }"#
21168        .unindent(),
21169    );
21170
21171    cx.set_head_text(&diff_base);
21172    executor.run_until_parked();
21173    cx.update_editor(|editor, window, cx| {
21174        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21175    });
21176    executor.run_until_parked();
21177
21178    cx.assert_state_with_diff(
21179        r#"
21180        use some::mod1;
21181        use some::mod2;
21182
21183        const A: u32 = 42;
21184        const B: u32 = 42;
21185      - const C: u32 = 42;
21186      + const C: u32 = 43ˇ
21187        const D: u32 = 42;
21188
21189
21190        fn main() {
21191            println!("hello");
21192
21193            println!("world");
21194        }"#
21195        .unindent(),
21196    );
21197
21198    cx.update_editor(|editor, window, cx| {
21199        editor.handle_input("\nnew_line\n", window, cx);
21200    });
21201    executor.run_until_parked();
21202
21203    cx.assert_state_with_diff(
21204        r#"
21205        use some::mod1;
21206        use some::mod2;
21207
21208        const A: u32 = 42;
21209        const B: u32 = 42;
21210      - const C: u32 = 42;
21211      + const C: u32 = 43
21212      + new_line
21213      + ˇ
21214        const D: u32 = 42;
21215
21216
21217        fn main() {
21218            println!("hello");
21219
21220            println!("world");
21221        }"#
21222        .unindent(),
21223    );
21224}
21225
21226#[gpui::test]
21227async fn test_stage_and_unstage_added_file_hunk(
21228    executor: BackgroundExecutor,
21229    cx: &mut TestAppContext,
21230) {
21231    init_test(cx, |_| {});
21232
21233    let mut cx = EditorTestContext::new(cx).await;
21234    cx.update_editor(|editor, _, cx| {
21235        editor.set_expand_all_diff_hunks(cx);
21236    });
21237
21238    let working_copy = r#"
21239            ˇfn main() {
21240                println!("hello, world!");
21241            }
21242        "#
21243    .unindent();
21244
21245    cx.set_state(&working_copy);
21246    executor.run_until_parked();
21247
21248    cx.assert_state_with_diff(
21249        r#"
21250            + ˇfn main() {
21251            +     println!("hello, world!");
21252            + }
21253        "#
21254        .unindent(),
21255    );
21256    cx.assert_index_text(None);
21257
21258    cx.update_editor(|editor, window, cx| {
21259        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21260    });
21261    executor.run_until_parked();
21262    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21263    cx.assert_state_with_diff(
21264        r#"
21265            + ˇfn main() {
21266            +     println!("hello, world!");
21267            + }
21268        "#
21269        .unindent(),
21270    );
21271
21272    cx.update_editor(|editor, window, cx| {
21273        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21274    });
21275    executor.run_until_parked();
21276    cx.assert_index_text(None);
21277}
21278
21279async fn setup_indent_guides_editor(
21280    text: &str,
21281    cx: &mut TestAppContext,
21282) -> (BufferId, EditorTestContext) {
21283    init_test(cx, |_| {});
21284
21285    let mut cx = EditorTestContext::new(cx).await;
21286
21287    let buffer_id = cx.update_editor(|editor, window, cx| {
21288        editor.set_text(text, window, cx);
21289        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21290
21291        buffer_ids[0]
21292    });
21293
21294    (buffer_id, cx)
21295}
21296
21297fn assert_indent_guides(
21298    range: Range<u32>,
21299    expected: Vec<IndentGuide>,
21300    active_indices: Option<Vec<usize>>,
21301    cx: &mut EditorTestContext,
21302) {
21303    let indent_guides = cx.update_editor(|editor, window, cx| {
21304        let snapshot = editor.snapshot(window, cx).display_snapshot;
21305        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21306            editor,
21307            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21308            true,
21309            &snapshot,
21310            cx,
21311        );
21312
21313        indent_guides.sort_by(|a, b| {
21314            a.depth.cmp(&b.depth).then(
21315                a.start_row
21316                    .cmp(&b.start_row)
21317                    .then(a.end_row.cmp(&b.end_row)),
21318            )
21319        });
21320        indent_guides
21321    });
21322
21323    if let Some(expected) = active_indices {
21324        let active_indices = cx.update_editor(|editor, window, cx| {
21325            let snapshot = editor.snapshot(window, cx).display_snapshot;
21326            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21327        });
21328
21329        assert_eq!(
21330            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21331            expected,
21332            "Active indent guide indices do not match"
21333        );
21334    }
21335
21336    assert_eq!(indent_guides, expected, "Indent guides do not match");
21337}
21338
21339fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21340    IndentGuide {
21341        buffer_id,
21342        start_row: MultiBufferRow(start_row),
21343        end_row: MultiBufferRow(end_row),
21344        depth,
21345        tab_size: 4,
21346        settings: IndentGuideSettings {
21347            enabled: true,
21348            line_width: 1,
21349            active_line_width: 1,
21350            coloring: IndentGuideColoring::default(),
21351            background_coloring: IndentGuideBackgroundColoring::default(),
21352        },
21353    }
21354}
21355
21356#[gpui::test]
21357async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21358    let (buffer_id, mut cx) = setup_indent_guides_editor(
21359        &"
21360        fn main() {
21361            let a = 1;
21362        }"
21363        .unindent(),
21364        cx,
21365    )
21366    .await;
21367
21368    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21369}
21370
21371#[gpui::test]
21372async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21373    let (buffer_id, mut cx) = setup_indent_guides_editor(
21374        &"
21375        fn main() {
21376            let a = 1;
21377            let b = 2;
21378        }"
21379        .unindent(),
21380        cx,
21381    )
21382    .await;
21383
21384    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21385}
21386
21387#[gpui::test]
21388async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21389    let (buffer_id, mut cx) = setup_indent_guides_editor(
21390        &"
21391        fn main() {
21392            let a = 1;
21393            if a == 3 {
21394                let b = 2;
21395            } else {
21396                let c = 3;
21397            }
21398        }"
21399        .unindent(),
21400        cx,
21401    )
21402    .await;
21403
21404    assert_indent_guides(
21405        0..8,
21406        vec![
21407            indent_guide(buffer_id, 1, 6, 0),
21408            indent_guide(buffer_id, 3, 3, 1),
21409            indent_guide(buffer_id, 5, 5, 1),
21410        ],
21411        None,
21412        &mut cx,
21413    );
21414}
21415
21416#[gpui::test]
21417async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21418    let (buffer_id, mut cx) = setup_indent_guides_editor(
21419        &"
21420        fn main() {
21421            let a = 1;
21422                let b = 2;
21423            let c = 3;
21424        }"
21425        .unindent(),
21426        cx,
21427    )
21428    .await;
21429
21430    assert_indent_guides(
21431        0..5,
21432        vec![
21433            indent_guide(buffer_id, 1, 3, 0),
21434            indent_guide(buffer_id, 2, 2, 1),
21435        ],
21436        None,
21437        &mut cx,
21438    );
21439}
21440
21441#[gpui::test]
21442async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21443    let (buffer_id, mut cx) = setup_indent_guides_editor(
21444        &"
21445        fn main() {
21446            let a = 1;
21447
21448            let c = 3;
21449        }"
21450        .unindent(),
21451        cx,
21452    )
21453    .await;
21454
21455    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21456}
21457
21458#[gpui::test]
21459async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21460    let (buffer_id, mut cx) = setup_indent_guides_editor(
21461        &"
21462        fn main() {
21463            let a = 1;
21464
21465            let c = 3;
21466
21467            if a == 3 {
21468                let b = 2;
21469            } else {
21470                let c = 3;
21471            }
21472        }"
21473        .unindent(),
21474        cx,
21475    )
21476    .await;
21477
21478    assert_indent_guides(
21479        0..11,
21480        vec![
21481            indent_guide(buffer_id, 1, 9, 0),
21482            indent_guide(buffer_id, 6, 6, 1),
21483            indent_guide(buffer_id, 8, 8, 1),
21484        ],
21485        None,
21486        &mut cx,
21487    );
21488}
21489
21490#[gpui::test]
21491async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21492    let (buffer_id, mut cx) = setup_indent_guides_editor(
21493        &"
21494        fn main() {
21495            let a = 1;
21496
21497            let c = 3;
21498
21499            if a == 3 {
21500                let b = 2;
21501            } else {
21502                let c = 3;
21503            }
21504        }"
21505        .unindent(),
21506        cx,
21507    )
21508    .await;
21509
21510    assert_indent_guides(
21511        1..11,
21512        vec![
21513            indent_guide(buffer_id, 1, 9, 0),
21514            indent_guide(buffer_id, 6, 6, 1),
21515            indent_guide(buffer_id, 8, 8, 1),
21516        ],
21517        None,
21518        &mut cx,
21519    );
21520}
21521
21522#[gpui::test]
21523async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21524    let (buffer_id, mut cx) = setup_indent_guides_editor(
21525        &"
21526        fn main() {
21527            let a = 1;
21528
21529            let c = 3;
21530
21531            if a == 3 {
21532                let b = 2;
21533            } else {
21534                let c = 3;
21535            }
21536        }"
21537        .unindent(),
21538        cx,
21539    )
21540    .await;
21541
21542    assert_indent_guides(
21543        1..10,
21544        vec![
21545            indent_guide(buffer_id, 1, 9, 0),
21546            indent_guide(buffer_id, 6, 6, 1),
21547            indent_guide(buffer_id, 8, 8, 1),
21548        ],
21549        None,
21550        &mut cx,
21551    );
21552}
21553
21554#[gpui::test]
21555async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21556    let (buffer_id, mut cx) = setup_indent_guides_editor(
21557        &"
21558        fn main() {
21559            if a {
21560                b(
21561                    c,
21562                    d,
21563                )
21564            } else {
21565                e(
21566                    f
21567                )
21568            }
21569        }"
21570        .unindent(),
21571        cx,
21572    )
21573    .await;
21574
21575    assert_indent_guides(
21576        0..11,
21577        vec![
21578            indent_guide(buffer_id, 1, 10, 0),
21579            indent_guide(buffer_id, 2, 5, 1),
21580            indent_guide(buffer_id, 7, 9, 1),
21581            indent_guide(buffer_id, 3, 4, 2),
21582            indent_guide(buffer_id, 8, 8, 2),
21583        ],
21584        None,
21585        &mut cx,
21586    );
21587
21588    cx.update_editor(|editor, window, cx| {
21589        editor.fold_at(MultiBufferRow(2), window, cx);
21590        assert_eq!(
21591            editor.display_text(cx),
21592            "
21593            fn main() {
21594                if a {
21595                    b(⋯
21596                    )
21597                } else {
21598                    e(
21599                        f
21600                    )
21601                }
21602            }"
21603            .unindent()
21604        );
21605    });
21606
21607    assert_indent_guides(
21608        0..11,
21609        vec![
21610            indent_guide(buffer_id, 1, 10, 0),
21611            indent_guide(buffer_id, 2, 5, 1),
21612            indent_guide(buffer_id, 7, 9, 1),
21613            indent_guide(buffer_id, 8, 8, 2),
21614        ],
21615        None,
21616        &mut cx,
21617    );
21618}
21619
21620#[gpui::test]
21621async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21622    let (buffer_id, mut cx) = setup_indent_guides_editor(
21623        &"
21624        block1
21625            block2
21626                block3
21627                    block4
21628            block2
21629        block1
21630        block1"
21631            .unindent(),
21632        cx,
21633    )
21634    .await;
21635
21636    assert_indent_guides(
21637        1..10,
21638        vec![
21639            indent_guide(buffer_id, 1, 4, 0),
21640            indent_guide(buffer_id, 2, 3, 1),
21641            indent_guide(buffer_id, 3, 3, 2),
21642        ],
21643        None,
21644        &mut cx,
21645    );
21646}
21647
21648#[gpui::test]
21649async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21650    let (buffer_id, mut cx) = setup_indent_guides_editor(
21651        &"
21652        block1
21653            block2
21654                block3
21655
21656        block1
21657        block1"
21658            .unindent(),
21659        cx,
21660    )
21661    .await;
21662
21663    assert_indent_guides(
21664        0..6,
21665        vec![
21666            indent_guide(buffer_id, 1, 2, 0),
21667            indent_guide(buffer_id, 2, 2, 1),
21668        ],
21669        None,
21670        &mut cx,
21671    );
21672}
21673
21674#[gpui::test]
21675async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21676    let (buffer_id, mut cx) = setup_indent_guides_editor(
21677        &"
21678        function component() {
21679        \treturn (
21680        \t\t\t
21681        \t\t<div>
21682        \t\t\t<abc></abc>
21683        \t\t</div>
21684        \t)
21685        }"
21686        .unindent(),
21687        cx,
21688    )
21689    .await;
21690
21691    assert_indent_guides(
21692        0..8,
21693        vec![
21694            indent_guide(buffer_id, 1, 6, 0),
21695            indent_guide(buffer_id, 2, 5, 1),
21696            indent_guide(buffer_id, 4, 4, 2),
21697        ],
21698        None,
21699        &mut cx,
21700    );
21701}
21702
21703#[gpui::test]
21704async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21705    let (buffer_id, mut cx) = setup_indent_guides_editor(
21706        &"
21707        function component() {
21708        \treturn (
21709        \t
21710        \t\t<div>
21711        \t\t\t<abc></abc>
21712        \t\t</div>
21713        \t)
21714        }"
21715        .unindent(),
21716        cx,
21717    )
21718    .await;
21719
21720    assert_indent_guides(
21721        0..8,
21722        vec![
21723            indent_guide(buffer_id, 1, 6, 0),
21724            indent_guide(buffer_id, 2, 5, 1),
21725            indent_guide(buffer_id, 4, 4, 2),
21726        ],
21727        None,
21728        &mut cx,
21729    );
21730}
21731
21732#[gpui::test]
21733async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21734    let (buffer_id, mut cx) = setup_indent_guides_editor(
21735        &"
21736        block1
21737
21738
21739
21740            block2
21741        "
21742        .unindent(),
21743        cx,
21744    )
21745    .await;
21746
21747    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21748}
21749
21750#[gpui::test]
21751async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21752    let (buffer_id, mut cx) = setup_indent_guides_editor(
21753        &"
21754        def a:
21755        \tb = 3
21756        \tif True:
21757        \t\tc = 4
21758        \t\td = 5
21759        \tprint(b)
21760        "
21761        .unindent(),
21762        cx,
21763    )
21764    .await;
21765
21766    assert_indent_guides(
21767        0..6,
21768        vec![
21769            indent_guide(buffer_id, 1, 5, 0),
21770            indent_guide(buffer_id, 3, 4, 1),
21771        ],
21772        None,
21773        &mut cx,
21774    );
21775}
21776
21777#[gpui::test]
21778async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21779    let (buffer_id, mut cx) = setup_indent_guides_editor(
21780        &"
21781    fn main() {
21782        let a = 1;
21783    }"
21784        .unindent(),
21785        cx,
21786    )
21787    .await;
21788
21789    cx.update_editor(|editor, window, cx| {
21790        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21791            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21792        });
21793    });
21794
21795    assert_indent_guides(
21796        0..3,
21797        vec![indent_guide(buffer_id, 1, 1, 0)],
21798        Some(vec![0]),
21799        &mut cx,
21800    );
21801}
21802
21803#[gpui::test]
21804async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21805    let (buffer_id, mut cx) = setup_indent_guides_editor(
21806        &"
21807    fn main() {
21808        if 1 == 2 {
21809            let a = 1;
21810        }
21811    }"
21812        .unindent(),
21813        cx,
21814    )
21815    .await;
21816
21817    cx.update_editor(|editor, window, cx| {
21818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21819            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21820        });
21821    });
21822
21823    assert_indent_guides(
21824        0..4,
21825        vec![
21826            indent_guide(buffer_id, 1, 3, 0),
21827            indent_guide(buffer_id, 2, 2, 1),
21828        ],
21829        Some(vec![1]),
21830        &mut cx,
21831    );
21832
21833    cx.update_editor(|editor, window, cx| {
21834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21835            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21836        });
21837    });
21838
21839    assert_indent_guides(
21840        0..4,
21841        vec![
21842            indent_guide(buffer_id, 1, 3, 0),
21843            indent_guide(buffer_id, 2, 2, 1),
21844        ],
21845        Some(vec![1]),
21846        &mut cx,
21847    );
21848
21849    cx.update_editor(|editor, window, cx| {
21850        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21851            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21852        });
21853    });
21854
21855    assert_indent_guides(
21856        0..4,
21857        vec![
21858            indent_guide(buffer_id, 1, 3, 0),
21859            indent_guide(buffer_id, 2, 2, 1),
21860        ],
21861        Some(vec![0]),
21862        &mut cx,
21863    );
21864}
21865
21866#[gpui::test]
21867async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21868    let (buffer_id, mut cx) = setup_indent_guides_editor(
21869        &"
21870    fn main() {
21871        let a = 1;
21872
21873        let b = 2;
21874    }"
21875        .unindent(),
21876        cx,
21877    )
21878    .await;
21879
21880    cx.update_editor(|editor, window, cx| {
21881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21882            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21883        });
21884    });
21885
21886    assert_indent_guides(
21887        0..5,
21888        vec![indent_guide(buffer_id, 1, 3, 0)],
21889        Some(vec![0]),
21890        &mut cx,
21891    );
21892}
21893
21894#[gpui::test]
21895async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21896    let (buffer_id, mut cx) = setup_indent_guides_editor(
21897        &"
21898    def m:
21899        a = 1
21900        pass"
21901            .unindent(),
21902        cx,
21903    )
21904    .await;
21905
21906    cx.update_editor(|editor, window, cx| {
21907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21908            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21909        });
21910    });
21911
21912    assert_indent_guides(
21913        0..3,
21914        vec![indent_guide(buffer_id, 1, 2, 0)],
21915        Some(vec![0]),
21916        &mut cx,
21917    );
21918}
21919
21920#[gpui::test]
21921async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21922    init_test(cx, |_| {});
21923    let mut cx = EditorTestContext::new(cx).await;
21924    let text = indoc! {
21925        "
21926        impl A {
21927            fn b() {
21928                0;
21929                3;
21930                5;
21931                6;
21932                7;
21933            }
21934        }
21935        "
21936    };
21937    let base_text = indoc! {
21938        "
21939        impl A {
21940            fn b() {
21941                0;
21942                1;
21943                2;
21944                3;
21945                4;
21946            }
21947            fn c() {
21948                5;
21949                6;
21950                7;
21951            }
21952        }
21953        "
21954    };
21955
21956    cx.update_editor(|editor, window, cx| {
21957        editor.set_text(text, window, cx);
21958
21959        editor.buffer().update(cx, |multibuffer, cx| {
21960            let buffer = multibuffer.as_singleton().unwrap();
21961            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21962
21963            multibuffer.set_all_diff_hunks_expanded(cx);
21964            multibuffer.add_diff(diff, cx);
21965
21966            buffer.read(cx).remote_id()
21967        })
21968    });
21969    cx.run_until_parked();
21970
21971    cx.assert_state_with_diff(
21972        indoc! { "
21973          impl A {
21974              fn b() {
21975                  0;
21976        -         1;
21977        -         2;
21978                  3;
21979        -         4;
21980        -     }
21981        -     fn c() {
21982                  5;
21983                  6;
21984                  7;
21985              }
21986          }
21987          ˇ"
21988        }
21989        .to_string(),
21990    );
21991
21992    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21993        editor
21994            .snapshot(window, cx)
21995            .buffer_snapshot()
21996            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21997            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21998            .collect::<Vec<_>>()
21999    });
22000    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22001    assert_eq!(
22002        actual_guides,
22003        vec![
22004            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22005            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22006            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22007        ]
22008    );
22009}
22010
22011#[gpui::test]
22012async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22013    init_test(cx, |_| {});
22014    let mut cx = EditorTestContext::new(cx).await;
22015
22016    let diff_base = r#"
22017        a
22018        b
22019        c
22020        "#
22021    .unindent();
22022
22023    cx.set_state(
22024        &r#"
22025        ˇA
22026        b
22027        C
22028        "#
22029        .unindent(),
22030    );
22031    cx.set_head_text(&diff_base);
22032    cx.update_editor(|editor, window, cx| {
22033        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22034    });
22035    executor.run_until_parked();
22036
22037    let both_hunks_expanded = r#"
22038        - a
22039        + ˇA
22040          b
22041        - c
22042        + C
22043        "#
22044    .unindent();
22045
22046    cx.assert_state_with_diff(both_hunks_expanded.clone());
22047
22048    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22049        let snapshot = editor.snapshot(window, cx);
22050        let hunks = editor
22051            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22052            .collect::<Vec<_>>();
22053        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22054        hunks
22055            .into_iter()
22056            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22057            .collect::<Vec<_>>()
22058    });
22059    assert_eq!(hunk_ranges.len(), 2);
22060
22061    cx.update_editor(|editor, _, cx| {
22062        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22063    });
22064    executor.run_until_parked();
22065
22066    let second_hunk_expanded = r#"
22067          ˇA
22068          b
22069        - c
22070        + C
22071        "#
22072    .unindent();
22073
22074    cx.assert_state_with_diff(second_hunk_expanded);
22075
22076    cx.update_editor(|editor, _, cx| {
22077        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22078    });
22079    executor.run_until_parked();
22080
22081    cx.assert_state_with_diff(both_hunks_expanded.clone());
22082
22083    cx.update_editor(|editor, _, cx| {
22084        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22085    });
22086    executor.run_until_parked();
22087
22088    let first_hunk_expanded = r#"
22089        - a
22090        + ˇA
22091          b
22092          C
22093        "#
22094    .unindent();
22095
22096    cx.assert_state_with_diff(first_hunk_expanded);
22097
22098    cx.update_editor(|editor, _, cx| {
22099        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22100    });
22101    executor.run_until_parked();
22102
22103    cx.assert_state_with_diff(both_hunks_expanded);
22104
22105    cx.set_state(
22106        &r#"
22107        ˇA
22108        b
22109        "#
22110        .unindent(),
22111    );
22112    cx.run_until_parked();
22113
22114    // TODO this cursor position seems bad
22115    cx.assert_state_with_diff(
22116        r#"
22117        - ˇa
22118        + A
22119          b
22120        "#
22121        .unindent(),
22122    );
22123
22124    cx.update_editor(|editor, window, cx| {
22125        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22126    });
22127
22128    cx.assert_state_with_diff(
22129        r#"
22130            - ˇa
22131            + A
22132              b
22133            - c
22134            "#
22135        .unindent(),
22136    );
22137
22138    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22139        let snapshot = editor.snapshot(window, cx);
22140        let hunks = editor
22141            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22142            .collect::<Vec<_>>();
22143        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22144        hunks
22145            .into_iter()
22146            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22147            .collect::<Vec<_>>()
22148    });
22149    assert_eq!(hunk_ranges.len(), 2);
22150
22151    cx.update_editor(|editor, _, cx| {
22152        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22153    });
22154    executor.run_until_parked();
22155
22156    cx.assert_state_with_diff(
22157        r#"
22158        - ˇa
22159        + A
22160          b
22161        "#
22162        .unindent(),
22163    );
22164}
22165
22166#[gpui::test]
22167async fn test_toggle_deletion_hunk_at_start_of_file(
22168    executor: BackgroundExecutor,
22169    cx: &mut TestAppContext,
22170) {
22171    init_test(cx, |_| {});
22172    let mut cx = EditorTestContext::new(cx).await;
22173
22174    let diff_base = r#"
22175        a
22176        b
22177        c
22178        "#
22179    .unindent();
22180
22181    cx.set_state(
22182        &r#"
22183        ˇb
22184        c
22185        "#
22186        .unindent(),
22187    );
22188    cx.set_head_text(&diff_base);
22189    cx.update_editor(|editor, window, cx| {
22190        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22191    });
22192    executor.run_until_parked();
22193
22194    let hunk_expanded = r#"
22195        - a
22196          ˇb
22197          c
22198        "#
22199    .unindent();
22200
22201    cx.assert_state_with_diff(hunk_expanded.clone());
22202
22203    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22204        let snapshot = editor.snapshot(window, cx);
22205        let hunks = editor
22206            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22207            .collect::<Vec<_>>();
22208        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22209        hunks
22210            .into_iter()
22211            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22212            .collect::<Vec<_>>()
22213    });
22214    assert_eq!(hunk_ranges.len(), 1);
22215
22216    cx.update_editor(|editor, _, cx| {
22217        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22218    });
22219    executor.run_until_parked();
22220
22221    let hunk_collapsed = r#"
22222          ˇb
22223          c
22224        "#
22225    .unindent();
22226
22227    cx.assert_state_with_diff(hunk_collapsed);
22228
22229    cx.update_editor(|editor, _, cx| {
22230        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22231    });
22232    executor.run_until_parked();
22233
22234    cx.assert_state_with_diff(hunk_expanded);
22235}
22236
22237#[gpui::test]
22238async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22239    executor: BackgroundExecutor,
22240    cx: &mut TestAppContext,
22241) {
22242    init_test(cx, |_| {});
22243    let mut cx = EditorTestContext::new(cx).await;
22244
22245    cx.set_state("ˇnew\nsecond\nthird\n");
22246    cx.set_head_text("old\nsecond\nthird\n");
22247    cx.update_editor(|editor, window, cx| {
22248        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22249    });
22250    executor.run_until_parked();
22251    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22252
22253    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22254    cx.update_editor(|editor, window, cx| {
22255        let snapshot = editor.snapshot(window, cx);
22256        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22257        let hunks = editor
22258            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22259            .collect::<Vec<_>>();
22260        assert_eq!(hunks.len(), 1);
22261        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22262        editor.toggle_single_diff_hunk(hunk_range, cx)
22263    });
22264    executor.run_until_parked();
22265    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22266
22267    // Keep the editor scrolled to the top so the full hunk remains visible.
22268    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22269}
22270
22271#[gpui::test]
22272async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22273    init_test(cx, |_| {});
22274
22275    let fs = FakeFs::new(cx.executor());
22276    fs.insert_tree(
22277        path!("/test"),
22278        json!({
22279            ".git": {},
22280            "file-1": "ONE\n",
22281            "file-2": "TWO\n",
22282            "file-3": "THREE\n",
22283        }),
22284    )
22285    .await;
22286
22287    fs.set_head_for_repo(
22288        path!("/test/.git").as_ref(),
22289        &[
22290            ("file-1", "one\n".into()),
22291            ("file-2", "two\n".into()),
22292            ("file-3", "three\n".into()),
22293        ],
22294        "deadbeef",
22295    );
22296
22297    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22298    let mut buffers = vec![];
22299    for i in 1..=3 {
22300        let buffer = project
22301            .update(cx, |project, cx| {
22302                let path = format!(path!("/test/file-{}"), i);
22303                project.open_local_buffer(path, cx)
22304            })
22305            .await
22306            .unwrap();
22307        buffers.push(buffer);
22308    }
22309
22310    let multibuffer = cx.new(|cx| {
22311        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22312        multibuffer.set_all_diff_hunks_expanded(cx);
22313        for buffer in &buffers {
22314            let snapshot = buffer.read(cx).snapshot();
22315            multibuffer.set_excerpts_for_path(
22316                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22317                buffer.clone(),
22318                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22319                2,
22320                cx,
22321            );
22322        }
22323        multibuffer
22324    });
22325
22326    let editor = cx.add_window(|window, cx| {
22327        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22328    });
22329    cx.run_until_parked();
22330
22331    let snapshot = editor
22332        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22333        .unwrap();
22334    let hunks = snapshot
22335        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22336        .map(|hunk| match hunk {
22337            DisplayDiffHunk::Unfolded {
22338                display_row_range, ..
22339            } => display_row_range,
22340            DisplayDiffHunk::Folded { .. } => unreachable!(),
22341        })
22342        .collect::<Vec<_>>();
22343    assert_eq!(
22344        hunks,
22345        [
22346            DisplayRow(2)..DisplayRow(4),
22347            DisplayRow(7)..DisplayRow(9),
22348            DisplayRow(12)..DisplayRow(14),
22349        ]
22350    );
22351}
22352
22353#[gpui::test]
22354async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22355    init_test(cx, |_| {});
22356
22357    let mut cx = EditorTestContext::new(cx).await;
22358    cx.set_head_text(indoc! { "
22359        one
22360        two
22361        three
22362        four
22363        five
22364        "
22365    });
22366    cx.set_index_text(indoc! { "
22367        one
22368        two
22369        three
22370        four
22371        five
22372        "
22373    });
22374    cx.set_state(indoc! {"
22375        one
22376        TWO
22377        ˇTHREE
22378        FOUR
22379        five
22380    "});
22381    cx.run_until_parked();
22382    cx.update_editor(|editor, window, cx| {
22383        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22384    });
22385    cx.run_until_parked();
22386    cx.assert_index_text(Some(indoc! {"
22387        one
22388        TWO
22389        THREE
22390        FOUR
22391        five
22392    "}));
22393    cx.set_state(indoc! { "
22394        one
22395        TWO
22396        ˇTHREE-HUNDRED
22397        FOUR
22398        five
22399    "});
22400    cx.run_until_parked();
22401    cx.update_editor(|editor, window, cx| {
22402        let snapshot = editor.snapshot(window, cx);
22403        let hunks = editor
22404            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22405            .collect::<Vec<_>>();
22406        assert_eq!(hunks.len(), 1);
22407        assert_eq!(
22408            hunks[0].status(),
22409            DiffHunkStatus {
22410                kind: DiffHunkStatusKind::Modified,
22411                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22412            }
22413        );
22414
22415        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22416    });
22417    cx.run_until_parked();
22418    cx.assert_index_text(Some(indoc! {"
22419        one
22420        TWO
22421        THREE-HUNDRED
22422        FOUR
22423        five
22424    "}));
22425}
22426
22427#[gpui::test]
22428fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22429    init_test(cx, |_| {});
22430
22431    let editor = cx.add_window(|window, cx| {
22432        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22433        build_editor(buffer, window, cx)
22434    });
22435
22436    let render_args = Arc::new(Mutex::new(None));
22437    let snapshot = editor
22438        .update(cx, |editor, window, cx| {
22439            let snapshot = editor.buffer().read(cx).snapshot(cx);
22440            let range =
22441                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22442
22443            struct RenderArgs {
22444                row: MultiBufferRow,
22445                folded: bool,
22446                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22447            }
22448
22449            let crease = Crease::inline(
22450                range,
22451                FoldPlaceholder::test(),
22452                {
22453                    let toggle_callback = render_args.clone();
22454                    move |row, folded, callback, _window, _cx| {
22455                        *toggle_callback.lock() = Some(RenderArgs {
22456                            row,
22457                            folded,
22458                            callback,
22459                        });
22460                        div()
22461                    }
22462                },
22463                |_row, _folded, _window, _cx| div(),
22464            );
22465
22466            editor.insert_creases(Some(crease), cx);
22467            let snapshot = editor.snapshot(window, cx);
22468            let _div =
22469                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22470            snapshot
22471        })
22472        .unwrap();
22473
22474    let render_args = render_args.lock().take().unwrap();
22475    assert_eq!(render_args.row, MultiBufferRow(1));
22476    assert!(!render_args.folded);
22477    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22478
22479    cx.update_window(*editor, |_, window, cx| {
22480        (render_args.callback)(true, window, cx)
22481    })
22482    .unwrap();
22483    let snapshot = editor
22484        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22485        .unwrap();
22486    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22487
22488    cx.update_window(*editor, |_, window, cx| {
22489        (render_args.callback)(false, window, cx)
22490    })
22491    .unwrap();
22492    let snapshot = editor
22493        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22494        .unwrap();
22495    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22496}
22497
22498#[gpui::test]
22499async fn test_input_text(cx: &mut TestAppContext) {
22500    init_test(cx, |_| {});
22501    let mut cx = EditorTestContext::new(cx).await;
22502
22503    cx.set_state(
22504        &r#"ˇone
22505        two
22506
22507        three
22508        fourˇ
22509        five
22510
22511        siˇx"#
22512            .unindent(),
22513    );
22514
22515    cx.dispatch_action(HandleInput(String::new()));
22516    cx.assert_editor_state(
22517        &r#"ˇone
22518        two
22519
22520        three
22521        fourˇ
22522        five
22523
22524        siˇx"#
22525            .unindent(),
22526    );
22527
22528    cx.dispatch_action(HandleInput("AAAA".to_string()));
22529    cx.assert_editor_state(
22530        &r#"AAAAˇone
22531        two
22532
22533        three
22534        fourAAAAˇ
22535        five
22536
22537        siAAAAˇx"#
22538            .unindent(),
22539    );
22540}
22541
22542#[gpui::test]
22543async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22544    init_test(cx, |_| {});
22545
22546    let mut cx = EditorTestContext::new(cx).await;
22547    cx.set_state(
22548        r#"let foo = 1;
22549let foo = 2;
22550let foo = 3;
22551let fooˇ = 4;
22552let foo = 5;
22553let foo = 6;
22554let foo = 7;
22555let foo = 8;
22556let foo = 9;
22557let foo = 10;
22558let foo = 11;
22559let foo = 12;
22560let foo = 13;
22561let foo = 14;
22562let foo = 15;"#,
22563    );
22564
22565    cx.update_editor(|e, window, cx| {
22566        assert_eq!(
22567            e.next_scroll_position,
22568            NextScrollCursorCenterTopBottom::Center,
22569            "Default next scroll direction is center",
22570        );
22571
22572        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22573        assert_eq!(
22574            e.next_scroll_position,
22575            NextScrollCursorCenterTopBottom::Top,
22576            "After center, next scroll direction should be top",
22577        );
22578
22579        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22580        assert_eq!(
22581            e.next_scroll_position,
22582            NextScrollCursorCenterTopBottom::Bottom,
22583            "After top, next scroll direction should be bottom",
22584        );
22585
22586        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22587        assert_eq!(
22588            e.next_scroll_position,
22589            NextScrollCursorCenterTopBottom::Center,
22590            "After bottom, scrolling should start over",
22591        );
22592
22593        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22594        assert_eq!(
22595            e.next_scroll_position,
22596            NextScrollCursorCenterTopBottom::Top,
22597            "Scrolling continues if retriggered fast enough"
22598        );
22599    });
22600
22601    cx.executor()
22602        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22603    cx.executor().run_until_parked();
22604    cx.update_editor(|e, _, _| {
22605        assert_eq!(
22606            e.next_scroll_position,
22607            NextScrollCursorCenterTopBottom::Center,
22608            "If scrolling is not triggered fast enough, it should reset"
22609        );
22610    });
22611}
22612
22613#[gpui::test]
22614async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22615    init_test(cx, |_| {});
22616    let mut cx = EditorLspTestContext::new_rust(
22617        lsp::ServerCapabilities {
22618            definition_provider: Some(lsp::OneOf::Left(true)),
22619            references_provider: Some(lsp::OneOf::Left(true)),
22620            ..lsp::ServerCapabilities::default()
22621        },
22622        cx,
22623    )
22624    .await;
22625
22626    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22627        let go_to_definition = cx
22628            .lsp
22629            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22630                move |params, _| async move {
22631                    if empty_go_to_definition {
22632                        Ok(None)
22633                    } else {
22634                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22635                            uri: params.text_document_position_params.text_document.uri,
22636                            range: lsp::Range::new(
22637                                lsp::Position::new(4, 3),
22638                                lsp::Position::new(4, 6),
22639                            ),
22640                        })))
22641                    }
22642                },
22643            );
22644        let references = cx
22645            .lsp
22646            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22647                Ok(Some(vec![lsp::Location {
22648                    uri: params.text_document_position.text_document.uri,
22649                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22650                }]))
22651            });
22652        (go_to_definition, references)
22653    };
22654
22655    cx.set_state(
22656        &r#"fn one() {
22657            let mut a = ˇtwo();
22658        }
22659
22660        fn two() {}"#
22661            .unindent(),
22662    );
22663    set_up_lsp_handlers(false, &mut cx);
22664    let navigated = cx
22665        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22666        .await
22667        .expect("Failed to navigate to definition");
22668    assert_eq!(
22669        navigated,
22670        Navigated::Yes,
22671        "Should have navigated to definition from the GetDefinition response"
22672    );
22673    cx.assert_editor_state(
22674        &r#"fn one() {
22675            let mut a = two();
22676        }
22677
22678        fn «twoˇ»() {}"#
22679            .unindent(),
22680    );
22681
22682    let editors = cx.update_workspace(|workspace, _, cx| {
22683        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22684    });
22685    cx.update_editor(|_, _, test_editor_cx| {
22686        assert_eq!(
22687            editors.len(),
22688            1,
22689            "Initially, only one, test, editor should be open in the workspace"
22690        );
22691        assert_eq!(
22692            test_editor_cx.entity(),
22693            editors.last().expect("Asserted len is 1").clone()
22694        );
22695    });
22696
22697    set_up_lsp_handlers(true, &mut cx);
22698    let navigated = cx
22699        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22700        .await
22701        .expect("Failed to navigate to lookup references");
22702    assert_eq!(
22703        navigated,
22704        Navigated::Yes,
22705        "Should have navigated to references as a fallback after empty GoToDefinition response"
22706    );
22707    // We should not change the selections in the existing file,
22708    // if opening another milti buffer with the references
22709    cx.assert_editor_state(
22710        &r#"fn one() {
22711            let mut a = two();
22712        }
22713
22714        fn «twoˇ»() {}"#
22715            .unindent(),
22716    );
22717    let editors = cx.update_workspace(|workspace, _, cx| {
22718        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22719    });
22720    cx.update_editor(|_, _, test_editor_cx| {
22721        assert_eq!(
22722            editors.len(),
22723            2,
22724            "After falling back to references search, we open a new editor with the results"
22725        );
22726        let references_fallback_text = editors
22727            .into_iter()
22728            .find(|new_editor| *new_editor != test_editor_cx.entity())
22729            .expect("Should have one non-test editor now")
22730            .read(test_editor_cx)
22731            .text(test_editor_cx);
22732        assert_eq!(
22733            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22734            "Should use the range from the references response and not the GoToDefinition one"
22735        );
22736    });
22737}
22738
22739#[gpui::test]
22740async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22741    init_test(cx, |_| {});
22742    cx.update(|cx| {
22743        let mut editor_settings = EditorSettings::get_global(cx).clone();
22744        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22745        EditorSettings::override_global(editor_settings, cx);
22746    });
22747    let mut cx = EditorLspTestContext::new_rust(
22748        lsp::ServerCapabilities {
22749            definition_provider: Some(lsp::OneOf::Left(true)),
22750            references_provider: Some(lsp::OneOf::Left(true)),
22751            ..lsp::ServerCapabilities::default()
22752        },
22753        cx,
22754    )
22755    .await;
22756    let original_state = r#"fn one() {
22757        let mut a = ˇtwo();
22758    }
22759
22760    fn two() {}"#
22761        .unindent();
22762    cx.set_state(&original_state);
22763
22764    let mut go_to_definition = cx
22765        .lsp
22766        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22767            move |_, _| async move { Ok(None) },
22768        );
22769    let _references = cx
22770        .lsp
22771        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22772            panic!("Should not call for references with no go to definition fallback")
22773        });
22774
22775    let navigated = cx
22776        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22777        .await
22778        .expect("Failed to navigate to lookup references");
22779    go_to_definition
22780        .next()
22781        .await
22782        .expect("Should have called the go_to_definition handler");
22783
22784    assert_eq!(
22785        navigated,
22786        Navigated::No,
22787        "Should have navigated to references as a fallback after empty GoToDefinition response"
22788    );
22789    cx.assert_editor_state(&original_state);
22790    let editors = cx.update_workspace(|workspace, _, cx| {
22791        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22792    });
22793    cx.update_editor(|_, _, _| {
22794        assert_eq!(
22795            editors.len(),
22796            1,
22797            "After unsuccessful fallback, no other editor should have been opened"
22798        );
22799    });
22800}
22801
22802#[gpui::test]
22803async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22804    init_test(cx, |_| {});
22805    let mut cx = EditorLspTestContext::new_rust(
22806        lsp::ServerCapabilities {
22807            references_provider: Some(lsp::OneOf::Left(true)),
22808            ..lsp::ServerCapabilities::default()
22809        },
22810        cx,
22811    )
22812    .await;
22813
22814    cx.set_state(
22815        &r#"
22816        fn one() {
22817            let mut a = two();
22818        }
22819
22820        fn ˇtwo() {}"#
22821            .unindent(),
22822    );
22823    cx.lsp
22824        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22825            Ok(Some(vec![
22826                lsp::Location {
22827                    uri: params.text_document_position.text_document.uri.clone(),
22828                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22829                },
22830                lsp::Location {
22831                    uri: params.text_document_position.text_document.uri,
22832                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22833                },
22834            ]))
22835        });
22836    let navigated = cx
22837        .update_editor(|editor, window, cx| {
22838            editor.find_all_references(&FindAllReferences::default(), window, cx)
22839        })
22840        .unwrap()
22841        .await
22842        .expect("Failed to navigate to references");
22843    assert_eq!(
22844        navigated,
22845        Navigated::Yes,
22846        "Should have navigated to references from the FindAllReferences response"
22847    );
22848    cx.assert_editor_state(
22849        &r#"fn one() {
22850            let mut a = two();
22851        }
22852
22853        fn ˇtwo() {}"#
22854            .unindent(),
22855    );
22856
22857    let editors = cx.update_workspace(|workspace, _, cx| {
22858        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22859    });
22860    cx.update_editor(|_, _, _| {
22861        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22862    });
22863
22864    cx.set_state(
22865        &r#"fn one() {
22866            let mut a = ˇtwo();
22867        }
22868
22869        fn two() {}"#
22870            .unindent(),
22871    );
22872    let navigated = cx
22873        .update_editor(|editor, window, cx| {
22874            editor.find_all_references(&FindAllReferences::default(), window, cx)
22875        })
22876        .unwrap()
22877        .await
22878        .expect("Failed to navigate to references");
22879    assert_eq!(
22880        navigated,
22881        Navigated::Yes,
22882        "Should have navigated to references from the FindAllReferences response"
22883    );
22884    cx.assert_editor_state(
22885        &r#"fn one() {
22886            let mut a = ˇtwo();
22887        }
22888
22889        fn two() {}"#
22890            .unindent(),
22891    );
22892    let editors = cx.update_workspace(|workspace, _, cx| {
22893        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22894    });
22895    cx.update_editor(|_, _, _| {
22896        assert_eq!(
22897            editors.len(),
22898            2,
22899            "should have re-used the previous multibuffer"
22900        );
22901    });
22902
22903    cx.set_state(
22904        &r#"fn one() {
22905            let mut a = ˇtwo();
22906        }
22907        fn three() {}
22908        fn two() {}"#
22909            .unindent(),
22910    );
22911    cx.lsp
22912        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22913            Ok(Some(vec![
22914                lsp::Location {
22915                    uri: params.text_document_position.text_document.uri.clone(),
22916                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22917                },
22918                lsp::Location {
22919                    uri: params.text_document_position.text_document.uri,
22920                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22921                },
22922            ]))
22923        });
22924    let navigated = cx
22925        .update_editor(|editor, window, cx| {
22926            editor.find_all_references(&FindAllReferences::default(), window, cx)
22927        })
22928        .unwrap()
22929        .await
22930        .expect("Failed to navigate to references");
22931    assert_eq!(
22932        navigated,
22933        Navigated::Yes,
22934        "Should have navigated to references from the FindAllReferences response"
22935    );
22936    cx.assert_editor_state(
22937        &r#"fn one() {
22938                let mut a = ˇtwo();
22939            }
22940            fn three() {}
22941            fn two() {}"#
22942            .unindent(),
22943    );
22944    let editors = cx.update_workspace(|workspace, _, cx| {
22945        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22946    });
22947    cx.update_editor(|_, _, _| {
22948        assert_eq!(
22949            editors.len(),
22950            3,
22951            "should have used a new multibuffer as offsets changed"
22952        );
22953    });
22954}
22955#[gpui::test]
22956async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22957    init_test(cx, |_| {});
22958
22959    let language = Arc::new(Language::new(
22960        LanguageConfig::default(),
22961        Some(tree_sitter_rust::LANGUAGE.into()),
22962    ));
22963
22964    let text = r#"
22965        #[cfg(test)]
22966        mod tests() {
22967            #[test]
22968            fn runnable_1() {
22969                let a = 1;
22970            }
22971
22972            #[test]
22973            fn runnable_2() {
22974                let a = 1;
22975                let b = 2;
22976            }
22977        }
22978    "#
22979    .unindent();
22980
22981    let fs = FakeFs::new(cx.executor());
22982    fs.insert_file("/file.rs", Default::default()).await;
22983
22984    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22985    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22986    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22987    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22988    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22989
22990    let editor = cx.new_window_entity(|window, cx| {
22991        Editor::new(
22992            EditorMode::full(),
22993            multi_buffer,
22994            Some(project.clone()),
22995            window,
22996            cx,
22997        )
22998    });
22999
23000    editor.update_in(cx, |editor, window, cx| {
23001        let snapshot = editor.buffer().read(cx).snapshot(cx);
23002        editor.tasks.insert(
23003            (buffer.read(cx).remote_id(), 3),
23004            RunnableTasks {
23005                templates: vec![],
23006                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23007                column: 0,
23008                extra_variables: HashMap::default(),
23009                context_range: BufferOffset(43)..BufferOffset(85),
23010            },
23011        );
23012        editor.tasks.insert(
23013            (buffer.read(cx).remote_id(), 8),
23014            RunnableTasks {
23015                templates: vec![],
23016                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23017                column: 0,
23018                extra_variables: HashMap::default(),
23019                context_range: BufferOffset(86)..BufferOffset(191),
23020            },
23021        );
23022
23023        // Test finding task when cursor is inside function body
23024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23025            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23026        });
23027        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23028        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23029
23030        // Test finding task when cursor is on function name
23031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23032            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23033        });
23034        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23035        assert_eq!(row, 8, "Should find task when cursor is on function name");
23036    });
23037}
23038
23039#[gpui::test]
23040async fn test_folding_buffers(cx: &mut TestAppContext) {
23041    init_test(cx, |_| {});
23042
23043    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23044    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23045    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23046
23047    let fs = FakeFs::new(cx.executor());
23048    fs.insert_tree(
23049        path!("/a"),
23050        json!({
23051            "first.rs": sample_text_1,
23052            "second.rs": sample_text_2,
23053            "third.rs": sample_text_3,
23054        }),
23055    )
23056    .await;
23057    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23058    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23059    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23060    let worktree = project.update(cx, |project, cx| {
23061        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23062        assert_eq!(worktrees.len(), 1);
23063        worktrees.pop().unwrap()
23064    });
23065    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23066
23067    let buffer_1 = project
23068        .update(cx, |project, cx| {
23069            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23070        })
23071        .await
23072        .unwrap();
23073    let buffer_2 = project
23074        .update(cx, |project, cx| {
23075            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23076        })
23077        .await
23078        .unwrap();
23079    let buffer_3 = project
23080        .update(cx, |project, cx| {
23081            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23082        })
23083        .await
23084        .unwrap();
23085
23086    let multi_buffer = cx.new(|cx| {
23087        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23088        multi_buffer.push_excerpts(
23089            buffer_1.clone(),
23090            [
23091                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23092                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23093                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23094            ],
23095            cx,
23096        );
23097        multi_buffer.push_excerpts(
23098            buffer_2.clone(),
23099            [
23100                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23101                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23102                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23103            ],
23104            cx,
23105        );
23106        multi_buffer.push_excerpts(
23107            buffer_3.clone(),
23108            [
23109                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23110                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23111                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23112            ],
23113            cx,
23114        );
23115        multi_buffer
23116    });
23117    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23118        Editor::new(
23119            EditorMode::full(),
23120            multi_buffer.clone(),
23121            Some(project.clone()),
23122            window,
23123            cx,
23124        )
23125    });
23126
23127    assert_eq!(
23128        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23129        "\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",
23130    );
23131
23132    multi_buffer_editor.update(cx, |editor, cx| {
23133        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23134    });
23135    assert_eq!(
23136        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23137        "\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",
23138        "After folding the first buffer, its text should not be displayed"
23139    );
23140
23141    multi_buffer_editor.update(cx, |editor, cx| {
23142        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23143    });
23144    assert_eq!(
23145        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23146        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23147        "After folding the second buffer, its text should not be displayed"
23148    );
23149
23150    multi_buffer_editor.update(cx, |editor, cx| {
23151        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23152    });
23153    assert_eq!(
23154        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23155        "\n\n\n\n\n",
23156        "After folding the third buffer, its text should not be displayed"
23157    );
23158
23159    // Emulate selection inside the fold logic, that should work
23160    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23161        editor
23162            .snapshot(window, cx)
23163            .next_line_boundary(Point::new(0, 4));
23164    });
23165
23166    multi_buffer_editor.update(cx, |editor, cx| {
23167        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23168    });
23169    assert_eq!(
23170        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23171        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23172        "After unfolding the second buffer, its text should be displayed"
23173    );
23174
23175    // Typing inside of buffer 1 causes that buffer to be unfolded.
23176    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23177        assert_eq!(
23178            multi_buffer
23179                .read(cx)
23180                .snapshot(cx)
23181                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23182                .collect::<String>(),
23183            "bbbb"
23184        );
23185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23186            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23187        });
23188        editor.handle_input("B", window, cx);
23189    });
23190
23191    assert_eq!(
23192        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23193        "\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",
23194        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23195    );
23196
23197    multi_buffer_editor.update(cx, |editor, cx| {
23198        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23199    });
23200    assert_eq!(
23201        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23202        "\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",
23203        "After unfolding the all buffers, all original text should be displayed"
23204    );
23205}
23206
23207#[gpui::test]
23208async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23209    init_test(cx, |_| {});
23210
23211    let sample_text_1 = "1111\n2222\n3333".to_string();
23212    let sample_text_2 = "4444\n5555\n6666".to_string();
23213    let sample_text_3 = "7777\n8888\n9999".to_string();
23214
23215    let fs = FakeFs::new(cx.executor());
23216    fs.insert_tree(
23217        path!("/a"),
23218        json!({
23219            "first.rs": sample_text_1,
23220            "second.rs": sample_text_2,
23221            "third.rs": sample_text_3,
23222        }),
23223    )
23224    .await;
23225    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23226    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23227    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23228    let worktree = project.update(cx, |project, cx| {
23229        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23230        assert_eq!(worktrees.len(), 1);
23231        worktrees.pop().unwrap()
23232    });
23233    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23234
23235    let buffer_1 = project
23236        .update(cx, |project, cx| {
23237            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23238        })
23239        .await
23240        .unwrap();
23241    let buffer_2 = project
23242        .update(cx, |project, cx| {
23243            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23244        })
23245        .await
23246        .unwrap();
23247    let buffer_3 = project
23248        .update(cx, |project, cx| {
23249            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23250        })
23251        .await
23252        .unwrap();
23253
23254    let multi_buffer = cx.new(|cx| {
23255        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23256        multi_buffer.push_excerpts(
23257            buffer_1.clone(),
23258            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23259            cx,
23260        );
23261        multi_buffer.push_excerpts(
23262            buffer_2.clone(),
23263            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23264            cx,
23265        );
23266        multi_buffer.push_excerpts(
23267            buffer_3.clone(),
23268            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23269            cx,
23270        );
23271        multi_buffer
23272    });
23273
23274    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23275        Editor::new(
23276            EditorMode::full(),
23277            multi_buffer,
23278            Some(project.clone()),
23279            window,
23280            cx,
23281        )
23282    });
23283
23284    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23285    assert_eq!(
23286        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23287        full_text,
23288    );
23289
23290    multi_buffer_editor.update(cx, |editor, cx| {
23291        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23292    });
23293    assert_eq!(
23294        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23295        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23296        "After folding the first buffer, its text should not be displayed"
23297    );
23298
23299    multi_buffer_editor.update(cx, |editor, cx| {
23300        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23301    });
23302
23303    assert_eq!(
23304        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23305        "\n\n\n\n\n\n7777\n8888\n9999",
23306        "After folding the second buffer, its text should not be displayed"
23307    );
23308
23309    multi_buffer_editor.update(cx, |editor, cx| {
23310        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23311    });
23312    assert_eq!(
23313        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23314        "\n\n\n\n\n",
23315        "After folding the third buffer, its text should not be displayed"
23316    );
23317
23318    multi_buffer_editor.update(cx, |editor, cx| {
23319        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23320    });
23321    assert_eq!(
23322        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23323        "\n\n\n\n4444\n5555\n6666\n\n",
23324        "After unfolding the second buffer, its text should be displayed"
23325    );
23326
23327    multi_buffer_editor.update(cx, |editor, cx| {
23328        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23329    });
23330    assert_eq!(
23331        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23332        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23333        "After unfolding the first buffer, its text should be displayed"
23334    );
23335
23336    multi_buffer_editor.update(cx, |editor, cx| {
23337        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23338    });
23339    assert_eq!(
23340        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23341        full_text,
23342        "After unfolding all buffers, all original text should be displayed"
23343    );
23344}
23345
23346#[gpui::test]
23347async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23348    init_test(cx, |_| {});
23349
23350    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23351
23352    let fs = FakeFs::new(cx.executor());
23353    fs.insert_tree(
23354        path!("/a"),
23355        json!({
23356            "main.rs": sample_text,
23357        }),
23358    )
23359    .await;
23360    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23361    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23362    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23363    let worktree = project.update(cx, |project, cx| {
23364        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23365        assert_eq!(worktrees.len(), 1);
23366        worktrees.pop().unwrap()
23367    });
23368    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23369
23370    let buffer_1 = project
23371        .update(cx, |project, cx| {
23372            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23373        })
23374        .await
23375        .unwrap();
23376
23377    let multi_buffer = cx.new(|cx| {
23378        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23379        multi_buffer.push_excerpts(
23380            buffer_1.clone(),
23381            [ExcerptRange::new(
23382                Point::new(0, 0)
23383                    ..Point::new(
23384                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23385                        0,
23386                    ),
23387            )],
23388            cx,
23389        );
23390        multi_buffer
23391    });
23392    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23393        Editor::new(
23394            EditorMode::full(),
23395            multi_buffer,
23396            Some(project.clone()),
23397            window,
23398            cx,
23399        )
23400    });
23401
23402    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23403    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23404        enum TestHighlight {}
23405        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23406        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23407        editor.highlight_text::<TestHighlight>(
23408            vec![highlight_range.clone()],
23409            HighlightStyle::color(Hsla::green()),
23410            cx,
23411        );
23412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23413            s.select_ranges(Some(highlight_range))
23414        });
23415    });
23416
23417    let full_text = format!("\n\n{sample_text}");
23418    assert_eq!(
23419        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23420        full_text,
23421    );
23422}
23423
23424#[gpui::test]
23425async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23426    init_test(cx, |_| {});
23427    cx.update(|cx| {
23428        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23429            "keymaps/default-linux.json",
23430            cx,
23431        )
23432        .unwrap();
23433        cx.bind_keys(default_key_bindings);
23434    });
23435
23436    let (editor, cx) = cx.add_window_view(|window, cx| {
23437        let multi_buffer = MultiBuffer::build_multi(
23438            [
23439                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23440                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23441                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23442                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23443            ],
23444            cx,
23445        );
23446        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23447
23448        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23449        // fold all but the second buffer, so that we test navigating between two
23450        // adjacent folded buffers, as well as folded buffers at the start and
23451        // end the multibuffer
23452        editor.fold_buffer(buffer_ids[0], cx);
23453        editor.fold_buffer(buffer_ids[2], cx);
23454        editor.fold_buffer(buffer_ids[3], cx);
23455
23456        editor
23457    });
23458    cx.simulate_resize(size(px(1000.), px(1000.)));
23459
23460    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23461    cx.assert_excerpts_with_selections(indoc! {"
23462        [EXCERPT]
23463        ˇ[FOLDED]
23464        [EXCERPT]
23465        a1
23466        b1
23467        [EXCERPT]
23468        [FOLDED]
23469        [EXCERPT]
23470        [FOLDED]
23471        "
23472    });
23473    cx.simulate_keystroke("down");
23474    cx.assert_excerpts_with_selections(indoc! {"
23475        [EXCERPT]
23476        [FOLDED]
23477        [EXCERPT]
23478        ˇa1
23479        b1
23480        [EXCERPT]
23481        [FOLDED]
23482        [EXCERPT]
23483        [FOLDED]
23484        "
23485    });
23486    cx.simulate_keystroke("down");
23487    cx.assert_excerpts_with_selections(indoc! {"
23488        [EXCERPT]
23489        [FOLDED]
23490        [EXCERPT]
23491        a1
23492        ˇb1
23493        [EXCERPT]
23494        [FOLDED]
23495        [EXCERPT]
23496        [FOLDED]
23497        "
23498    });
23499    cx.simulate_keystroke("down");
23500    cx.assert_excerpts_with_selections(indoc! {"
23501        [EXCERPT]
23502        [FOLDED]
23503        [EXCERPT]
23504        a1
23505        b1
23506        ˇ[EXCERPT]
23507        [FOLDED]
23508        [EXCERPT]
23509        [FOLDED]
23510        "
23511    });
23512    cx.simulate_keystroke("down");
23513    cx.assert_excerpts_with_selections(indoc! {"
23514        [EXCERPT]
23515        [FOLDED]
23516        [EXCERPT]
23517        a1
23518        b1
23519        [EXCERPT]
23520        ˇ[FOLDED]
23521        [EXCERPT]
23522        [FOLDED]
23523        "
23524    });
23525    for _ in 0..5 {
23526        cx.simulate_keystroke("down");
23527        cx.assert_excerpts_with_selections(indoc! {"
23528            [EXCERPT]
23529            [FOLDED]
23530            [EXCERPT]
23531            a1
23532            b1
23533            [EXCERPT]
23534            [FOLDED]
23535            [EXCERPT]
23536            ˇ[FOLDED]
23537            "
23538        });
23539    }
23540
23541    cx.simulate_keystroke("up");
23542    cx.assert_excerpts_with_selections(indoc! {"
23543        [EXCERPT]
23544        [FOLDED]
23545        [EXCERPT]
23546        a1
23547        b1
23548        [EXCERPT]
23549        ˇ[FOLDED]
23550        [EXCERPT]
23551        [FOLDED]
23552        "
23553    });
23554    cx.simulate_keystroke("up");
23555    cx.assert_excerpts_with_selections(indoc! {"
23556        [EXCERPT]
23557        [FOLDED]
23558        [EXCERPT]
23559        a1
23560        b1
23561        ˇ[EXCERPT]
23562        [FOLDED]
23563        [EXCERPT]
23564        [FOLDED]
23565        "
23566    });
23567    cx.simulate_keystroke("up");
23568    cx.assert_excerpts_with_selections(indoc! {"
23569        [EXCERPT]
23570        [FOLDED]
23571        [EXCERPT]
23572        a1
23573        ˇb1
23574        [EXCERPT]
23575        [FOLDED]
23576        [EXCERPT]
23577        [FOLDED]
23578        "
23579    });
23580    cx.simulate_keystroke("up");
23581    cx.assert_excerpts_with_selections(indoc! {"
23582        [EXCERPT]
23583        [FOLDED]
23584        [EXCERPT]
23585        ˇa1
23586        b1
23587        [EXCERPT]
23588        [FOLDED]
23589        [EXCERPT]
23590        [FOLDED]
23591        "
23592    });
23593    for _ in 0..5 {
23594        cx.simulate_keystroke("up");
23595        cx.assert_excerpts_with_selections(indoc! {"
23596            [EXCERPT]
23597            ˇ[FOLDED]
23598            [EXCERPT]
23599            a1
23600            b1
23601            [EXCERPT]
23602            [FOLDED]
23603            [EXCERPT]
23604            [FOLDED]
23605            "
23606        });
23607    }
23608}
23609
23610#[gpui::test]
23611async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23612    init_test(cx, |_| {});
23613
23614    // Simple insertion
23615    assert_highlighted_edits(
23616        "Hello, world!",
23617        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23618        true,
23619        cx,
23620        |highlighted_edits, cx| {
23621            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23622            assert_eq!(highlighted_edits.highlights.len(), 1);
23623            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23624            assert_eq!(
23625                highlighted_edits.highlights[0].1.background_color,
23626                Some(cx.theme().status().created_background)
23627            );
23628        },
23629    )
23630    .await;
23631
23632    // Replacement
23633    assert_highlighted_edits(
23634        "This is a test.",
23635        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23636        false,
23637        cx,
23638        |highlighted_edits, cx| {
23639            assert_eq!(highlighted_edits.text, "That is a test.");
23640            assert_eq!(highlighted_edits.highlights.len(), 1);
23641            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23642            assert_eq!(
23643                highlighted_edits.highlights[0].1.background_color,
23644                Some(cx.theme().status().created_background)
23645            );
23646        },
23647    )
23648    .await;
23649
23650    // Multiple edits
23651    assert_highlighted_edits(
23652        "Hello, world!",
23653        vec![
23654            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23655            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23656        ],
23657        false,
23658        cx,
23659        |highlighted_edits, cx| {
23660            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23661            assert_eq!(highlighted_edits.highlights.len(), 2);
23662            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23663            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23664            assert_eq!(
23665                highlighted_edits.highlights[0].1.background_color,
23666                Some(cx.theme().status().created_background)
23667            );
23668            assert_eq!(
23669                highlighted_edits.highlights[1].1.background_color,
23670                Some(cx.theme().status().created_background)
23671            );
23672        },
23673    )
23674    .await;
23675
23676    // Multiple lines with edits
23677    assert_highlighted_edits(
23678        "First line\nSecond line\nThird line\nFourth line",
23679        vec![
23680            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23681            (
23682                Point::new(2, 0)..Point::new(2, 10),
23683                "New third line".to_string(),
23684            ),
23685            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23686        ],
23687        false,
23688        cx,
23689        |highlighted_edits, cx| {
23690            assert_eq!(
23691                highlighted_edits.text,
23692                "Second modified\nNew third line\nFourth updated line"
23693            );
23694            assert_eq!(highlighted_edits.highlights.len(), 3);
23695            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23696            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23697            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23698            for highlight in &highlighted_edits.highlights {
23699                assert_eq!(
23700                    highlight.1.background_color,
23701                    Some(cx.theme().status().created_background)
23702                );
23703            }
23704        },
23705    )
23706    .await;
23707}
23708
23709#[gpui::test]
23710async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23711    init_test(cx, |_| {});
23712
23713    // Deletion
23714    assert_highlighted_edits(
23715        "Hello, world!",
23716        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23717        true,
23718        cx,
23719        |highlighted_edits, cx| {
23720            assert_eq!(highlighted_edits.text, "Hello, world!");
23721            assert_eq!(highlighted_edits.highlights.len(), 1);
23722            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23723            assert_eq!(
23724                highlighted_edits.highlights[0].1.background_color,
23725                Some(cx.theme().status().deleted_background)
23726            );
23727        },
23728    )
23729    .await;
23730
23731    // Insertion
23732    assert_highlighted_edits(
23733        "Hello, world!",
23734        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23735        true,
23736        cx,
23737        |highlighted_edits, cx| {
23738            assert_eq!(highlighted_edits.highlights.len(), 1);
23739            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23740            assert_eq!(
23741                highlighted_edits.highlights[0].1.background_color,
23742                Some(cx.theme().status().created_background)
23743            );
23744        },
23745    )
23746    .await;
23747}
23748
23749async fn assert_highlighted_edits(
23750    text: &str,
23751    edits: Vec<(Range<Point>, String)>,
23752    include_deletions: bool,
23753    cx: &mut TestAppContext,
23754    assertion_fn: impl Fn(HighlightedText, &App),
23755) {
23756    let window = cx.add_window(|window, cx| {
23757        let buffer = MultiBuffer::build_simple(text, cx);
23758        Editor::new(EditorMode::full(), buffer, None, window, cx)
23759    });
23760    let cx = &mut VisualTestContext::from_window(*window, cx);
23761
23762    let (buffer, snapshot) = window
23763        .update(cx, |editor, _window, cx| {
23764            (
23765                editor.buffer().clone(),
23766                editor.buffer().read(cx).snapshot(cx),
23767            )
23768        })
23769        .unwrap();
23770
23771    let edits = edits
23772        .into_iter()
23773        .map(|(range, edit)| {
23774            (
23775                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23776                edit,
23777            )
23778        })
23779        .collect::<Vec<_>>();
23780
23781    let text_anchor_edits = edits
23782        .clone()
23783        .into_iter()
23784        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23785        .collect::<Vec<_>>();
23786
23787    let edit_preview = window
23788        .update(cx, |_, _window, cx| {
23789            buffer
23790                .read(cx)
23791                .as_singleton()
23792                .unwrap()
23793                .read(cx)
23794                .preview_edits(text_anchor_edits.into(), cx)
23795        })
23796        .unwrap()
23797        .await;
23798
23799    cx.update(|_window, cx| {
23800        let highlighted_edits = edit_prediction_edit_text(
23801            snapshot.as_singleton().unwrap().2,
23802            &edits,
23803            &edit_preview,
23804            include_deletions,
23805            cx,
23806        );
23807        assertion_fn(highlighted_edits, cx)
23808    });
23809}
23810
23811#[track_caller]
23812fn assert_breakpoint(
23813    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23814    path: &Arc<Path>,
23815    expected: Vec<(u32, Breakpoint)>,
23816) {
23817    if expected.is_empty() {
23818        assert!(!breakpoints.contains_key(path), "{}", path.display());
23819    } else {
23820        let mut breakpoint = breakpoints
23821            .get(path)
23822            .unwrap()
23823            .iter()
23824            .map(|breakpoint| {
23825                (
23826                    breakpoint.row,
23827                    Breakpoint {
23828                        message: breakpoint.message.clone(),
23829                        state: breakpoint.state,
23830                        condition: breakpoint.condition.clone(),
23831                        hit_condition: breakpoint.hit_condition.clone(),
23832                    },
23833                )
23834            })
23835            .collect::<Vec<_>>();
23836
23837        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23838
23839        assert_eq!(expected, breakpoint);
23840    }
23841}
23842
23843fn add_log_breakpoint_at_cursor(
23844    editor: &mut Editor,
23845    log_message: &str,
23846    window: &mut Window,
23847    cx: &mut Context<Editor>,
23848) {
23849    let (anchor, bp) = editor
23850        .breakpoints_at_cursors(window, cx)
23851        .first()
23852        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23853        .unwrap_or_else(|| {
23854            let snapshot = editor.snapshot(window, cx);
23855            let cursor_position: Point =
23856                editor.selections.newest(&snapshot.display_snapshot).head();
23857
23858            let breakpoint_position = snapshot
23859                .buffer_snapshot()
23860                .anchor_before(Point::new(cursor_position.row, 0));
23861
23862            (breakpoint_position, Breakpoint::new_log(log_message))
23863        });
23864
23865    editor.edit_breakpoint_at_anchor(
23866        anchor,
23867        bp,
23868        BreakpointEditAction::EditLogMessage(log_message.into()),
23869        cx,
23870    );
23871}
23872
23873#[gpui::test]
23874async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23875    init_test(cx, |_| {});
23876
23877    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23878    let fs = FakeFs::new(cx.executor());
23879    fs.insert_tree(
23880        path!("/a"),
23881        json!({
23882            "main.rs": sample_text,
23883        }),
23884    )
23885    .await;
23886    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23888    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23889
23890    let fs = FakeFs::new(cx.executor());
23891    fs.insert_tree(
23892        path!("/a"),
23893        json!({
23894            "main.rs": sample_text,
23895        }),
23896    )
23897    .await;
23898    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23899    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23900    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23901    let worktree_id = workspace
23902        .update(cx, |workspace, _window, cx| {
23903            workspace.project().update(cx, |project, cx| {
23904                project.worktrees(cx).next().unwrap().read(cx).id()
23905            })
23906        })
23907        .unwrap();
23908
23909    let buffer = project
23910        .update(cx, |project, cx| {
23911            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23912        })
23913        .await
23914        .unwrap();
23915
23916    let (editor, cx) = cx.add_window_view(|window, cx| {
23917        Editor::new(
23918            EditorMode::full(),
23919            MultiBuffer::build_from_buffer(buffer, cx),
23920            Some(project.clone()),
23921            window,
23922            cx,
23923        )
23924    });
23925
23926    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23927    let abs_path = project.read_with(cx, |project, cx| {
23928        project
23929            .absolute_path(&project_path, cx)
23930            .map(Arc::from)
23931            .unwrap()
23932    });
23933
23934    // assert we can add breakpoint on the first line
23935    editor.update_in(cx, |editor, window, cx| {
23936        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23937        editor.move_to_end(&MoveToEnd, window, cx);
23938        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23939    });
23940
23941    let breakpoints = editor.update(cx, |editor, cx| {
23942        editor
23943            .breakpoint_store()
23944            .as_ref()
23945            .unwrap()
23946            .read(cx)
23947            .all_source_breakpoints(cx)
23948    });
23949
23950    assert_eq!(1, breakpoints.len());
23951    assert_breakpoint(
23952        &breakpoints,
23953        &abs_path,
23954        vec![
23955            (0, Breakpoint::new_standard()),
23956            (3, Breakpoint::new_standard()),
23957        ],
23958    );
23959
23960    editor.update_in(cx, |editor, window, cx| {
23961        editor.move_to_beginning(&MoveToBeginning, window, cx);
23962        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23963    });
23964
23965    let breakpoints = editor.update(cx, |editor, cx| {
23966        editor
23967            .breakpoint_store()
23968            .as_ref()
23969            .unwrap()
23970            .read(cx)
23971            .all_source_breakpoints(cx)
23972    });
23973
23974    assert_eq!(1, breakpoints.len());
23975    assert_breakpoint(
23976        &breakpoints,
23977        &abs_path,
23978        vec![(3, Breakpoint::new_standard())],
23979    );
23980
23981    editor.update_in(cx, |editor, window, cx| {
23982        editor.move_to_end(&MoveToEnd, window, cx);
23983        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23984    });
23985
23986    let breakpoints = editor.update(cx, |editor, cx| {
23987        editor
23988            .breakpoint_store()
23989            .as_ref()
23990            .unwrap()
23991            .read(cx)
23992            .all_source_breakpoints(cx)
23993    });
23994
23995    assert_eq!(0, breakpoints.len());
23996    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23997}
23998
23999#[gpui::test]
24000async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24001    init_test(cx, |_| {});
24002
24003    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24004
24005    let fs = FakeFs::new(cx.executor());
24006    fs.insert_tree(
24007        path!("/a"),
24008        json!({
24009            "main.rs": sample_text,
24010        }),
24011    )
24012    .await;
24013    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24014    let (workspace, cx) =
24015        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24016
24017    let worktree_id = workspace.update(cx, |workspace, cx| {
24018        workspace.project().update(cx, |project, cx| {
24019            project.worktrees(cx).next().unwrap().read(cx).id()
24020        })
24021    });
24022
24023    let buffer = project
24024        .update(cx, |project, cx| {
24025            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24026        })
24027        .await
24028        .unwrap();
24029
24030    let (editor, cx) = cx.add_window_view(|window, cx| {
24031        Editor::new(
24032            EditorMode::full(),
24033            MultiBuffer::build_from_buffer(buffer, cx),
24034            Some(project.clone()),
24035            window,
24036            cx,
24037        )
24038    });
24039
24040    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24041    let abs_path = project.read_with(cx, |project, cx| {
24042        project
24043            .absolute_path(&project_path, cx)
24044            .map(Arc::from)
24045            .unwrap()
24046    });
24047
24048    editor.update_in(cx, |editor, window, cx| {
24049        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24050    });
24051
24052    let breakpoints = editor.update(cx, |editor, cx| {
24053        editor
24054            .breakpoint_store()
24055            .as_ref()
24056            .unwrap()
24057            .read(cx)
24058            .all_source_breakpoints(cx)
24059    });
24060
24061    assert_breakpoint(
24062        &breakpoints,
24063        &abs_path,
24064        vec![(0, Breakpoint::new_log("hello world"))],
24065    );
24066
24067    // Removing a log message from a log breakpoint should remove it
24068    editor.update_in(cx, |editor, window, cx| {
24069        add_log_breakpoint_at_cursor(editor, "", window, cx);
24070    });
24071
24072    let breakpoints = editor.update(cx, |editor, cx| {
24073        editor
24074            .breakpoint_store()
24075            .as_ref()
24076            .unwrap()
24077            .read(cx)
24078            .all_source_breakpoints(cx)
24079    });
24080
24081    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24082
24083    editor.update_in(cx, |editor, window, cx| {
24084        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24085        editor.move_to_end(&MoveToEnd, window, cx);
24086        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24087        // Not adding a log message to a standard breakpoint shouldn't remove it
24088        add_log_breakpoint_at_cursor(editor, "", window, cx);
24089    });
24090
24091    let breakpoints = editor.update(cx, |editor, cx| {
24092        editor
24093            .breakpoint_store()
24094            .as_ref()
24095            .unwrap()
24096            .read(cx)
24097            .all_source_breakpoints(cx)
24098    });
24099
24100    assert_breakpoint(
24101        &breakpoints,
24102        &abs_path,
24103        vec![
24104            (0, Breakpoint::new_standard()),
24105            (3, Breakpoint::new_standard()),
24106        ],
24107    );
24108
24109    editor.update_in(cx, |editor, window, cx| {
24110        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24111    });
24112
24113    let breakpoints = editor.update(cx, |editor, cx| {
24114        editor
24115            .breakpoint_store()
24116            .as_ref()
24117            .unwrap()
24118            .read(cx)
24119            .all_source_breakpoints(cx)
24120    });
24121
24122    assert_breakpoint(
24123        &breakpoints,
24124        &abs_path,
24125        vec![
24126            (0, Breakpoint::new_standard()),
24127            (3, Breakpoint::new_log("hello world")),
24128        ],
24129    );
24130
24131    editor.update_in(cx, |editor, window, cx| {
24132        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24133    });
24134
24135    let breakpoints = editor.update(cx, |editor, cx| {
24136        editor
24137            .breakpoint_store()
24138            .as_ref()
24139            .unwrap()
24140            .read(cx)
24141            .all_source_breakpoints(cx)
24142    });
24143
24144    assert_breakpoint(
24145        &breakpoints,
24146        &abs_path,
24147        vec![
24148            (0, Breakpoint::new_standard()),
24149            (3, Breakpoint::new_log("hello Earth!!")),
24150        ],
24151    );
24152}
24153
24154/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24155/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24156/// or when breakpoints were placed out of order. This tests for a regression too
24157#[gpui::test]
24158async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24159    init_test(cx, |_| {});
24160
24161    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24162    let fs = FakeFs::new(cx.executor());
24163    fs.insert_tree(
24164        path!("/a"),
24165        json!({
24166            "main.rs": sample_text,
24167        }),
24168    )
24169    .await;
24170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24171    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24172    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24173
24174    let fs = FakeFs::new(cx.executor());
24175    fs.insert_tree(
24176        path!("/a"),
24177        json!({
24178            "main.rs": sample_text,
24179        }),
24180    )
24181    .await;
24182    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24183    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24184    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24185    let worktree_id = workspace
24186        .update(cx, |workspace, _window, cx| {
24187            workspace.project().update(cx, |project, cx| {
24188                project.worktrees(cx).next().unwrap().read(cx).id()
24189            })
24190        })
24191        .unwrap();
24192
24193    let buffer = project
24194        .update(cx, |project, cx| {
24195            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24196        })
24197        .await
24198        .unwrap();
24199
24200    let (editor, cx) = cx.add_window_view(|window, cx| {
24201        Editor::new(
24202            EditorMode::full(),
24203            MultiBuffer::build_from_buffer(buffer, cx),
24204            Some(project.clone()),
24205            window,
24206            cx,
24207        )
24208    });
24209
24210    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24211    let abs_path = project.read_with(cx, |project, cx| {
24212        project
24213            .absolute_path(&project_path, cx)
24214            .map(Arc::from)
24215            .unwrap()
24216    });
24217
24218    // assert we can add breakpoint on the first line
24219    editor.update_in(cx, |editor, window, cx| {
24220        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24221        editor.move_to_end(&MoveToEnd, window, cx);
24222        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24223        editor.move_up(&MoveUp, window, cx);
24224        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24225    });
24226
24227    let breakpoints = editor.update(cx, |editor, cx| {
24228        editor
24229            .breakpoint_store()
24230            .as_ref()
24231            .unwrap()
24232            .read(cx)
24233            .all_source_breakpoints(cx)
24234    });
24235
24236    assert_eq!(1, breakpoints.len());
24237    assert_breakpoint(
24238        &breakpoints,
24239        &abs_path,
24240        vec![
24241            (0, Breakpoint::new_standard()),
24242            (2, Breakpoint::new_standard()),
24243            (3, Breakpoint::new_standard()),
24244        ],
24245    );
24246
24247    editor.update_in(cx, |editor, window, cx| {
24248        editor.move_to_beginning(&MoveToBeginning, window, cx);
24249        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24250        editor.move_to_end(&MoveToEnd, window, cx);
24251        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24252        // Disabling a breakpoint that doesn't exist should do nothing
24253        editor.move_up(&MoveUp, window, cx);
24254        editor.move_up(&MoveUp, window, cx);
24255        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24256    });
24257
24258    let breakpoints = editor.update(cx, |editor, cx| {
24259        editor
24260            .breakpoint_store()
24261            .as_ref()
24262            .unwrap()
24263            .read(cx)
24264            .all_source_breakpoints(cx)
24265    });
24266
24267    let disable_breakpoint = {
24268        let mut bp = Breakpoint::new_standard();
24269        bp.state = BreakpointState::Disabled;
24270        bp
24271    };
24272
24273    assert_eq!(1, breakpoints.len());
24274    assert_breakpoint(
24275        &breakpoints,
24276        &abs_path,
24277        vec![
24278            (0, disable_breakpoint.clone()),
24279            (2, Breakpoint::new_standard()),
24280            (3, disable_breakpoint.clone()),
24281        ],
24282    );
24283
24284    editor.update_in(cx, |editor, window, cx| {
24285        editor.move_to_beginning(&MoveToBeginning, window, cx);
24286        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24287        editor.move_to_end(&MoveToEnd, window, cx);
24288        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24289        editor.move_up(&MoveUp, window, cx);
24290        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24291    });
24292
24293    let breakpoints = editor.update(cx, |editor, cx| {
24294        editor
24295            .breakpoint_store()
24296            .as_ref()
24297            .unwrap()
24298            .read(cx)
24299            .all_source_breakpoints(cx)
24300    });
24301
24302    assert_eq!(1, breakpoints.len());
24303    assert_breakpoint(
24304        &breakpoints,
24305        &abs_path,
24306        vec![
24307            (0, Breakpoint::new_standard()),
24308            (2, disable_breakpoint),
24309            (3, Breakpoint::new_standard()),
24310        ],
24311    );
24312}
24313
24314#[gpui::test]
24315async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24316    init_test(cx, |_| {});
24317    let capabilities = lsp::ServerCapabilities {
24318        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24319            prepare_provider: Some(true),
24320            work_done_progress_options: Default::default(),
24321        })),
24322        ..Default::default()
24323    };
24324    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24325
24326    cx.set_state(indoc! {"
24327        struct Fˇoo {}
24328    "});
24329
24330    cx.update_editor(|editor, _, cx| {
24331        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24332        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24333        editor.highlight_background::<DocumentHighlightRead>(
24334            &[highlight_range],
24335            |_, theme| theme.colors().editor_document_highlight_read_background,
24336            cx,
24337        );
24338    });
24339
24340    let mut prepare_rename_handler = cx
24341        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24342            move |_, _, _| async move {
24343                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24344                    start: lsp::Position {
24345                        line: 0,
24346                        character: 7,
24347                    },
24348                    end: lsp::Position {
24349                        line: 0,
24350                        character: 10,
24351                    },
24352                })))
24353            },
24354        );
24355    let prepare_rename_task = cx
24356        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24357        .expect("Prepare rename was not started");
24358    prepare_rename_handler.next().await.unwrap();
24359    prepare_rename_task.await.expect("Prepare rename failed");
24360
24361    let mut rename_handler =
24362        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24363            let edit = lsp::TextEdit {
24364                range: lsp::Range {
24365                    start: lsp::Position {
24366                        line: 0,
24367                        character: 7,
24368                    },
24369                    end: lsp::Position {
24370                        line: 0,
24371                        character: 10,
24372                    },
24373                },
24374                new_text: "FooRenamed".to_string(),
24375            };
24376            Ok(Some(lsp::WorkspaceEdit::new(
24377                // Specify the same edit twice
24378                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24379            )))
24380        });
24381    let rename_task = cx
24382        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24383        .expect("Confirm rename was not started");
24384    rename_handler.next().await.unwrap();
24385    rename_task.await.expect("Confirm rename failed");
24386    cx.run_until_parked();
24387
24388    // Despite two edits, only one is actually applied as those are identical
24389    cx.assert_editor_state(indoc! {"
24390        struct FooRenamedˇ {}
24391    "});
24392}
24393
24394#[gpui::test]
24395async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24396    init_test(cx, |_| {});
24397    // These capabilities indicate that the server does not support prepare rename.
24398    let capabilities = lsp::ServerCapabilities {
24399        rename_provider: Some(lsp::OneOf::Left(true)),
24400        ..Default::default()
24401    };
24402    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24403
24404    cx.set_state(indoc! {"
24405        struct Fˇoo {}
24406    "});
24407
24408    cx.update_editor(|editor, _window, cx| {
24409        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24410        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24411        editor.highlight_background::<DocumentHighlightRead>(
24412            &[highlight_range],
24413            |_, theme| theme.colors().editor_document_highlight_read_background,
24414            cx,
24415        );
24416    });
24417
24418    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24419        .expect("Prepare rename was not started")
24420        .await
24421        .expect("Prepare rename failed");
24422
24423    let mut rename_handler =
24424        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24425            let edit = lsp::TextEdit {
24426                range: lsp::Range {
24427                    start: lsp::Position {
24428                        line: 0,
24429                        character: 7,
24430                    },
24431                    end: lsp::Position {
24432                        line: 0,
24433                        character: 10,
24434                    },
24435                },
24436                new_text: "FooRenamed".to_string(),
24437            };
24438            Ok(Some(lsp::WorkspaceEdit::new(
24439                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24440            )))
24441        });
24442    let rename_task = cx
24443        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24444        .expect("Confirm rename was not started");
24445    rename_handler.next().await.unwrap();
24446    rename_task.await.expect("Confirm rename failed");
24447    cx.run_until_parked();
24448
24449    // Correct range is renamed, as `surrounding_word` is used to find it.
24450    cx.assert_editor_state(indoc! {"
24451        struct FooRenamedˇ {}
24452    "});
24453}
24454
24455#[gpui::test]
24456async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24457    init_test(cx, |_| {});
24458    let mut cx = EditorTestContext::new(cx).await;
24459
24460    let language = Arc::new(
24461        Language::new(
24462            LanguageConfig::default(),
24463            Some(tree_sitter_html::LANGUAGE.into()),
24464        )
24465        .with_brackets_query(
24466            r#"
24467            ("<" @open "/>" @close)
24468            ("</" @open ">" @close)
24469            ("<" @open ">" @close)
24470            ("\"" @open "\"" @close)
24471            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24472        "#,
24473        )
24474        .unwrap(),
24475    );
24476    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24477
24478    cx.set_state(indoc! {"
24479        <span>ˇ</span>
24480    "});
24481    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24482    cx.assert_editor_state(indoc! {"
24483        <span>
24484        ˇ
24485        </span>
24486    "});
24487
24488    cx.set_state(indoc! {"
24489        <span><span></span>ˇ</span>
24490    "});
24491    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24492    cx.assert_editor_state(indoc! {"
24493        <span><span></span>
24494        ˇ</span>
24495    "});
24496
24497    cx.set_state(indoc! {"
24498        <span>ˇ
24499        </span>
24500    "});
24501    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24502    cx.assert_editor_state(indoc! {"
24503        <span>
24504        ˇ
24505        </span>
24506    "});
24507}
24508
24509#[gpui::test(iterations = 10)]
24510async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24511    init_test(cx, |_| {});
24512
24513    let fs = FakeFs::new(cx.executor());
24514    fs.insert_tree(
24515        path!("/dir"),
24516        json!({
24517            "a.ts": "a",
24518        }),
24519    )
24520    .await;
24521
24522    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24523    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24524    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24525
24526    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24527    language_registry.add(Arc::new(Language::new(
24528        LanguageConfig {
24529            name: "TypeScript".into(),
24530            matcher: LanguageMatcher {
24531                path_suffixes: vec!["ts".to_string()],
24532                ..Default::default()
24533            },
24534            ..Default::default()
24535        },
24536        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24537    )));
24538    let mut fake_language_servers = language_registry.register_fake_lsp(
24539        "TypeScript",
24540        FakeLspAdapter {
24541            capabilities: lsp::ServerCapabilities {
24542                code_lens_provider: Some(lsp::CodeLensOptions {
24543                    resolve_provider: Some(true),
24544                }),
24545                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24546                    commands: vec!["_the/command".to_string()],
24547                    ..lsp::ExecuteCommandOptions::default()
24548                }),
24549                ..lsp::ServerCapabilities::default()
24550            },
24551            ..FakeLspAdapter::default()
24552        },
24553    );
24554
24555    let editor = workspace
24556        .update(cx, |workspace, window, cx| {
24557            workspace.open_abs_path(
24558                PathBuf::from(path!("/dir/a.ts")),
24559                OpenOptions::default(),
24560                window,
24561                cx,
24562            )
24563        })
24564        .unwrap()
24565        .await
24566        .unwrap()
24567        .downcast::<Editor>()
24568        .unwrap();
24569    cx.executor().run_until_parked();
24570
24571    let fake_server = fake_language_servers.next().await.unwrap();
24572
24573    let buffer = editor.update(cx, |editor, cx| {
24574        editor
24575            .buffer()
24576            .read(cx)
24577            .as_singleton()
24578            .expect("have opened a single file by path")
24579    });
24580
24581    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24582    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24583    drop(buffer_snapshot);
24584    let actions = cx
24585        .update_window(*workspace, |_, window, cx| {
24586            project.code_actions(&buffer, anchor..anchor, window, cx)
24587        })
24588        .unwrap();
24589
24590    fake_server
24591        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24592            Ok(Some(vec![
24593                lsp::CodeLens {
24594                    range: lsp::Range::default(),
24595                    command: Some(lsp::Command {
24596                        title: "Code lens command".to_owned(),
24597                        command: "_the/command".to_owned(),
24598                        arguments: None,
24599                    }),
24600                    data: None,
24601                },
24602                lsp::CodeLens {
24603                    range: lsp::Range::default(),
24604                    command: Some(lsp::Command {
24605                        title: "Command not in capabilities".to_owned(),
24606                        command: "not in capabilities".to_owned(),
24607                        arguments: None,
24608                    }),
24609                    data: None,
24610                },
24611                lsp::CodeLens {
24612                    range: lsp::Range {
24613                        start: lsp::Position {
24614                            line: 1,
24615                            character: 1,
24616                        },
24617                        end: lsp::Position {
24618                            line: 1,
24619                            character: 1,
24620                        },
24621                    },
24622                    command: Some(lsp::Command {
24623                        title: "Command not in range".to_owned(),
24624                        command: "_the/command".to_owned(),
24625                        arguments: None,
24626                    }),
24627                    data: None,
24628                },
24629            ]))
24630        })
24631        .next()
24632        .await;
24633
24634    let actions = actions.await.unwrap();
24635    assert_eq!(
24636        actions.len(),
24637        1,
24638        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24639    );
24640    let action = actions[0].clone();
24641    let apply = project.update(cx, |project, cx| {
24642        project.apply_code_action(buffer.clone(), action, true, cx)
24643    });
24644
24645    // Resolving the code action does not populate its edits. In absence of
24646    // edits, we must execute the given command.
24647    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24648        |mut lens, _| async move {
24649            let lens_command = lens.command.as_mut().expect("should have a command");
24650            assert_eq!(lens_command.title, "Code lens command");
24651            lens_command.arguments = Some(vec![json!("the-argument")]);
24652            Ok(lens)
24653        },
24654    );
24655
24656    // While executing the command, the language server sends the editor
24657    // a `workspaceEdit` request.
24658    fake_server
24659        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24660            let fake = fake_server.clone();
24661            move |params, _| {
24662                assert_eq!(params.command, "_the/command");
24663                let fake = fake.clone();
24664                async move {
24665                    fake.server
24666                        .request::<lsp::request::ApplyWorkspaceEdit>(
24667                            lsp::ApplyWorkspaceEditParams {
24668                                label: None,
24669                                edit: lsp::WorkspaceEdit {
24670                                    changes: Some(
24671                                        [(
24672                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24673                                            vec![lsp::TextEdit {
24674                                                range: lsp::Range::new(
24675                                                    lsp::Position::new(0, 0),
24676                                                    lsp::Position::new(0, 0),
24677                                                ),
24678                                                new_text: "X".into(),
24679                                            }],
24680                                        )]
24681                                        .into_iter()
24682                                        .collect(),
24683                                    ),
24684                                    ..lsp::WorkspaceEdit::default()
24685                                },
24686                            },
24687                        )
24688                        .await
24689                        .into_response()
24690                        .unwrap();
24691                    Ok(Some(json!(null)))
24692                }
24693            }
24694        })
24695        .next()
24696        .await;
24697
24698    // Applying the code lens command returns a project transaction containing the edits
24699    // sent by the language server in its `workspaceEdit` request.
24700    let transaction = apply.await.unwrap();
24701    assert!(transaction.0.contains_key(&buffer));
24702    buffer.update(cx, |buffer, cx| {
24703        assert_eq!(buffer.text(), "Xa");
24704        buffer.undo(cx);
24705        assert_eq!(buffer.text(), "a");
24706    });
24707
24708    let actions_after_edits = cx
24709        .update_window(*workspace, |_, window, cx| {
24710            project.code_actions(&buffer, anchor..anchor, window, cx)
24711        })
24712        .unwrap()
24713        .await
24714        .unwrap();
24715    assert_eq!(
24716        actions, actions_after_edits,
24717        "For the same selection, same code lens actions should be returned"
24718    );
24719
24720    let _responses =
24721        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24722            panic!("No more code lens requests are expected");
24723        });
24724    editor.update_in(cx, |editor, window, cx| {
24725        editor.select_all(&SelectAll, window, cx);
24726    });
24727    cx.executor().run_until_parked();
24728    let new_actions = cx
24729        .update_window(*workspace, |_, window, cx| {
24730            project.code_actions(&buffer, anchor..anchor, window, cx)
24731        })
24732        .unwrap()
24733        .await
24734        .unwrap();
24735    assert_eq!(
24736        actions, new_actions,
24737        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24738    );
24739}
24740
24741#[gpui::test]
24742async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24743    init_test(cx, |_| {});
24744
24745    let fs = FakeFs::new(cx.executor());
24746    let main_text = r#"fn main() {
24747println!("1");
24748println!("2");
24749println!("3");
24750println!("4");
24751println!("5");
24752}"#;
24753    let lib_text = "mod foo {}";
24754    fs.insert_tree(
24755        path!("/a"),
24756        json!({
24757            "lib.rs": lib_text,
24758            "main.rs": main_text,
24759        }),
24760    )
24761    .await;
24762
24763    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24764    let (workspace, cx) =
24765        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24766    let worktree_id = workspace.update(cx, |workspace, cx| {
24767        workspace.project().update(cx, |project, cx| {
24768            project.worktrees(cx).next().unwrap().read(cx).id()
24769        })
24770    });
24771
24772    let expected_ranges = vec![
24773        Point::new(0, 0)..Point::new(0, 0),
24774        Point::new(1, 0)..Point::new(1, 1),
24775        Point::new(2, 0)..Point::new(2, 2),
24776        Point::new(3, 0)..Point::new(3, 3),
24777    ];
24778
24779    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24780    let editor_1 = workspace
24781        .update_in(cx, |workspace, window, cx| {
24782            workspace.open_path(
24783                (worktree_id, rel_path("main.rs")),
24784                Some(pane_1.downgrade()),
24785                true,
24786                window,
24787                cx,
24788            )
24789        })
24790        .unwrap()
24791        .await
24792        .downcast::<Editor>()
24793        .unwrap();
24794    pane_1.update(cx, |pane, cx| {
24795        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24796        open_editor.update(cx, |editor, cx| {
24797            assert_eq!(
24798                editor.display_text(cx),
24799                main_text,
24800                "Original main.rs text on initial open",
24801            );
24802            assert_eq!(
24803                editor
24804                    .selections
24805                    .all::<Point>(&editor.display_snapshot(cx))
24806                    .into_iter()
24807                    .map(|s| s.range())
24808                    .collect::<Vec<_>>(),
24809                vec![Point::zero()..Point::zero()],
24810                "Default selections on initial open",
24811            );
24812        })
24813    });
24814    editor_1.update_in(cx, |editor, window, cx| {
24815        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24816            s.select_ranges(expected_ranges.clone());
24817        });
24818    });
24819
24820    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24821        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24822    });
24823    let editor_2 = workspace
24824        .update_in(cx, |workspace, window, cx| {
24825            workspace.open_path(
24826                (worktree_id, rel_path("main.rs")),
24827                Some(pane_2.downgrade()),
24828                true,
24829                window,
24830                cx,
24831            )
24832        })
24833        .unwrap()
24834        .await
24835        .downcast::<Editor>()
24836        .unwrap();
24837    pane_2.update(cx, |pane, cx| {
24838        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24839        open_editor.update(cx, |editor, cx| {
24840            assert_eq!(
24841                editor.display_text(cx),
24842                main_text,
24843                "Original main.rs text on initial open in another panel",
24844            );
24845            assert_eq!(
24846                editor
24847                    .selections
24848                    .all::<Point>(&editor.display_snapshot(cx))
24849                    .into_iter()
24850                    .map(|s| s.range())
24851                    .collect::<Vec<_>>(),
24852                vec![Point::zero()..Point::zero()],
24853                "Default selections on initial open in another panel",
24854            );
24855        })
24856    });
24857
24858    editor_2.update_in(cx, |editor, window, cx| {
24859        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24860    });
24861
24862    let _other_editor_1 = workspace
24863        .update_in(cx, |workspace, window, cx| {
24864            workspace.open_path(
24865                (worktree_id, rel_path("lib.rs")),
24866                Some(pane_1.downgrade()),
24867                true,
24868                window,
24869                cx,
24870            )
24871        })
24872        .unwrap()
24873        .await
24874        .downcast::<Editor>()
24875        .unwrap();
24876    pane_1
24877        .update_in(cx, |pane, window, cx| {
24878            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24879        })
24880        .await
24881        .unwrap();
24882    drop(editor_1);
24883    pane_1.update(cx, |pane, cx| {
24884        pane.active_item()
24885            .unwrap()
24886            .downcast::<Editor>()
24887            .unwrap()
24888            .update(cx, |editor, cx| {
24889                assert_eq!(
24890                    editor.display_text(cx),
24891                    lib_text,
24892                    "Other file should be open and active",
24893                );
24894            });
24895        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24896    });
24897
24898    let _other_editor_2 = workspace
24899        .update_in(cx, |workspace, window, cx| {
24900            workspace.open_path(
24901                (worktree_id, rel_path("lib.rs")),
24902                Some(pane_2.downgrade()),
24903                true,
24904                window,
24905                cx,
24906            )
24907        })
24908        .unwrap()
24909        .await
24910        .downcast::<Editor>()
24911        .unwrap();
24912    pane_2
24913        .update_in(cx, |pane, window, cx| {
24914            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24915        })
24916        .await
24917        .unwrap();
24918    drop(editor_2);
24919    pane_2.update(cx, |pane, cx| {
24920        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24921        open_editor.update(cx, |editor, cx| {
24922            assert_eq!(
24923                editor.display_text(cx),
24924                lib_text,
24925                "Other file should be open and active in another panel too",
24926            );
24927        });
24928        assert_eq!(
24929            pane.items().count(),
24930            1,
24931            "No other editors should be open in another pane",
24932        );
24933    });
24934
24935    let _editor_1_reopened = workspace
24936        .update_in(cx, |workspace, window, cx| {
24937            workspace.open_path(
24938                (worktree_id, rel_path("main.rs")),
24939                Some(pane_1.downgrade()),
24940                true,
24941                window,
24942                cx,
24943            )
24944        })
24945        .unwrap()
24946        .await
24947        .downcast::<Editor>()
24948        .unwrap();
24949    let _editor_2_reopened = workspace
24950        .update_in(cx, |workspace, window, cx| {
24951            workspace.open_path(
24952                (worktree_id, rel_path("main.rs")),
24953                Some(pane_2.downgrade()),
24954                true,
24955                window,
24956                cx,
24957            )
24958        })
24959        .unwrap()
24960        .await
24961        .downcast::<Editor>()
24962        .unwrap();
24963    pane_1.update(cx, |pane, cx| {
24964        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24965        open_editor.update(cx, |editor, cx| {
24966            assert_eq!(
24967                editor.display_text(cx),
24968                main_text,
24969                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24970            );
24971            assert_eq!(
24972                editor
24973                    .selections
24974                    .all::<Point>(&editor.display_snapshot(cx))
24975                    .into_iter()
24976                    .map(|s| s.range())
24977                    .collect::<Vec<_>>(),
24978                expected_ranges,
24979                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24980            );
24981        })
24982    });
24983    pane_2.update(cx, |pane, cx| {
24984        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24985        open_editor.update(cx, |editor, cx| {
24986            assert_eq!(
24987                editor.display_text(cx),
24988                r#"fn main() {
24989⋯rintln!("1");
24990⋯intln!("2");
24991⋯ntln!("3");
24992println!("4");
24993println!("5");
24994}"#,
24995                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24996            );
24997            assert_eq!(
24998                editor
24999                    .selections
25000                    .all::<Point>(&editor.display_snapshot(cx))
25001                    .into_iter()
25002                    .map(|s| s.range())
25003                    .collect::<Vec<_>>(),
25004                vec![Point::zero()..Point::zero()],
25005                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25006            );
25007        })
25008    });
25009}
25010
25011#[gpui::test]
25012async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25013    init_test(cx, |_| {});
25014
25015    let fs = FakeFs::new(cx.executor());
25016    let main_text = r#"fn main() {
25017println!("1");
25018println!("2");
25019println!("3");
25020println!("4");
25021println!("5");
25022}"#;
25023    let lib_text = "mod foo {}";
25024    fs.insert_tree(
25025        path!("/a"),
25026        json!({
25027            "lib.rs": lib_text,
25028            "main.rs": main_text,
25029        }),
25030    )
25031    .await;
25032
25033    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25034    let (workspace, cx) =
25035        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25036    let worktree_id = workspace.update(cx, |workspace, cx| {
25037        workspace.project().update(cx, |project, cx| {
25038            project.worktrees(cx).next().unwrap().read(cx).id()
25039        })
25040    });
25041
25042    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25043    let editor = workspace
25044        .update_in(cx, |workspace, window, cx| {
25045            workspace.open_path(
25046                (worktree_id, rel_path("main.rs")),
25047                Some(pane.downgrade()),
25048                true,
25049                window,
25050                cx,
25051            )
25052        })
25053        .unwrap()
25054        .await
25055        .downcast::<Editor>()
25056        .unwrap();
25057    pane.update(cx, |pane, cx| {
25058        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25059        open_editor.update(cx, |editor, cx| {
25060            assert_eq!(
25061                editor.display_text(cx),
25062                main_text,
25063                "Original main.rs text on initial open",
25064            );
25065        })
25066    });
25067    editor.update_in(cx, |editor, window, cx| {
25068        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25069    });
25070
25071    cx.update_global(|store: &mut SettingsStore, cx| {
25072        store.update_user_settings(cx, |s| {
25073            s.workspace.restore_on_file_reopen = Some(false);
25074        });
25075    });
25076    editor.update_in(cx, |editor, window, cx| {
25077        editor.fold_ranges(
25078            vec![
25079                Point::new(1, 0)..Point::new(1, 1),
25080                Point::new(2, 0)..Point::new(2, 2),
25081                Point::new(3, 0)..Point::new(3, 3),
25082            ],
25083            false,
25084            window,
25085            cx,
25086        );
25087    });
25088    pane.update_in(cx, |pane, window, cx| {
25089        pane.close_all_items(&CloseAllItems::default(), window, cx)
25090    })
25091    .await
25092    .unwrap();
25093    pane.update(cx, |pane, _| {
25094        assert!(pane.active_item().is_none());
25095    });
25096    cx.update_global(|store: &mut SettingsStore, cx| {
25097        store.update_user_settings(cx, |s| {
25098            s.workspace.restore_on_file_reopen = Some(true);
25099        });
25100    });
25101
25102    let _editor_reopened = workspace
25103        .update_in(cx, |workspace, window, cx| {
25104            workspace.open_path(
25105                (worktree_id, rel_path("main.rs")),
25106                Some(pane.downgrade()),
25107                true,
25108                window,
25109                cx,
25110            )
25111        })
25112        .unwrap()
25113        .await
25114        .downcast::<Editor>()
25115        .unwrap();
25116    pane.update(cx, |pane, cx| {
25117        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25118        open_editor.update(cx, |editor, cx| {
25119            assert_eq!(
25120                editor.display_text(cx),
25121                main_text,
25122                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25123            );
25124        })
25125    });
25126}
25127
25128#[gpui::test]
25129async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25130    struct EmptyModalView {
25131        focus_handle: gpui::FocusHandle,
25132    }
25133    impl EventEmitter<DismissEvent> for EmptyModalView {}
25134    impl Render for EmptyModalView {
25135        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25136            div()
25137        }
25138    }
25139    impl Focusable for EmptyModalView {
25140        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25141            self.focus_handle.clone()
25142        }
25143    }
25144    impl workspace::ModalView for EmptyModalView {}
25145    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25146        EmptyModalView {
25147            focus_handle: cx.focus_handle(),
25148        }
25149    }
25150
25151    init_test(cx, |_| {});
25152
25153    let fs = FakeFs::new(cx.executor());
25154    let project = Project::test(fs, [], cx).await;
25155    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25156    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25157    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25158    let editor = cx.new_window_entity(|window, cx| {
25159        Editor::new(
25160            EditorMode::full(),
25161            buffer,
25162            Some(project.clone()),
25163            window,
25164            cx,
25165        )
25166    });
25167    workspace
25168        .update(cx, |workspace, window, cx| {
25169            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25170        })
25171        .unwrap();
25172    editor.update_in(cx, |editor, window, cx| {
25173        editor.open_context_menu(&OpenContextMenu, window, cx);
25174        assert!(editor.mouse_context_menu.is_some());
25175    });
25176    workspace
25177        .update(cx, |workspace, window, cx| {
25178            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25179        })
25180        .unwrap();
25181    cx.read(|cx| {
25182        assert!(editor.read(cx).mouse_context_menu.is_none());
25183    });
25184}
25185
25186fn set_linked_edit_ranges(
25187    opening: (Point, Point),
25188    closing: (Point, Point),
25189    editor: &mut Editor,
25190    cx: &mut Context<Editor>,
25191) {
25192    let Some((buffer, _)) = editor
25193        .buffer
25194        .read(cx)
25195        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25196    else {
25197        panic!("Failed to get buffer for selection position");
25198    };
25199    let buffer = buffer.read(cx);
25200    let buffer_id = buffer.remote_id();
25201    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25202    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25203    let mut linked_ranges = HashMap::default();
25204    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25205    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25206}
25207
25208#[gpui::test]
25209async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25210    init_test(cx, |_| {});
25211
25212    let fs = FakeFs::new(cx.executor());
25213    fs.insert_file(path!("/file.html"), Default::default())
25214        .await;
25215
25216    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25217
25218    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25219    let html_language = Arc::new(Language::new(
25220        LanguageConfig {
25221            name: "HTML".into(),
25222            matcher: LanguageMatcher {
25223                path_suffixes: vec!["html".to_string()],
25224                ..LanguageMatcher::default()
25225            },
25226            brackets: BracketPairConfig {
25227                pairs: vec![BracketPair {
25228                    start: "<".into(),
25229                    end: ">".into(),
25230                    close: true,
25231                    ..Default::default()
25232                }],
25233                ..Default::default()
25234            },
25235            ..Default::default()
25236        },
25237        Some(tree_sitter_html::LANGUAGE.into()),
25238    ));
25239    language_registry.add(html_language);
25240    let mut fake_servers = language_registry.register_fake_lsp(
25241        "HTML",
25242        FakeLspAdapter {
25243            capabilities: lsp::ServerCapabilities {
25244                completion_provider: Some(lsp::CompletionOptions {
25245                    resolve_provider: Some(true),
25246                    ..Default::default()
25247                }),
25248                ..Default::default()
25249            },
25250            ..Default::default()
25251        },
25252    );
25253
25254    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25255    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25256
25257    let worktree_id = workspace
25258        .update(cx, |workspace, _window, cx| {
25259            workspace.project().update(cx, |project, cx| {
25260                project.worktrees(cx).next().unwrap().read(cx).id()
25261            })
25262        })
25263        .unwrap();
25264    project
25265        .update(cx, |project, cx| {
25266            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25267        })
25268        .await
25269        .unwrap();
25270    let editor = workspace
25271        .update(cx, |workspace, window, cx| {
25272            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25273        })
25274        .unwrap()
25275        .await
25276        .unwrap()
25277        .downcast::<Editor>()
25278        .unwrap();
25279
25280    let fake_server = fake_servers.next().await.unwrap();
25281    editor.update_in(cx, |editor, window, cx| {
25282        editor.set_text("<ad></ad>", window, cx);
25283        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25284            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25285        });
25286        set_linked_edit_ranges(
25287            (Point::new(0, 1), Point::new(0, 3)),
25288            (Point::new(0, 6), Point::new(0, 8)),
25289            editor,
25290            cx,
25291        );
25292    });
25293    let mut completion_handle =
25294        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25295            Ok(Some(lsp::CompletionResponse::Array(vec![
25296                lsp::CompletionItem {
25297                    label: "head".to_string(),
25298                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25299                        lsp::InsertReplaceEdit {
25300                            new_text: "head".to_string(),
25301                            insert: lsp::Range::new(
25302                                lsp::Position::new(0, 1),
25303                                lsp::Position::new(0, 3),
25304                            ),
25305                            replace: lsp::Range::new(
25306                                lsp::Position::new(0, 1),
25307                                lsp::Position::new(0, 3),
25308                            ),
25309                        },
25310                    )),
25311                    ..Default::default()
25312                },
25313            ])))
25314        });
25315    editor.update_in(cx, |editor, window, cx| {
25316        editor.show_completions(&ShowCompletions, window, cx);
25317    });
25318    cx.run_until_parked();
25319    completion_handle.next().await.unwrap();
25320    editor.update(cx, |editor, _| {
25321        assert!(
25322            editor.context_menu_visible(),
25323            "Completion menu should be visible"
25324        );
25325    });
25326    editor.update_in(cx, |editor, window, cx| {
25327        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25328    });
25329    cx.executor().run_until_parked();
25330    editor.update(cx, |editor, cx| {
25331        assert_eq!(editor.text(cx), "<head></head>");
25332    });
25333}
25334
25335#[gpui::test]
25336async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25337    init_test(cx, |_| {});
25338
25339    let mut cx = EditorTestContext::new(cx).await;
25340    let language = Arc::new(Language::new(
25341        LanguageConfig {
25342            name: "TSX".into(),
25343            matcher: LanguageMatcher {
25344                path_suffixes: vec!["tsx".to_string()],
25345                ..LanguageMatcher::default()
25346            },
25347            brackets: BracketPairConfig {
25348                pairs: vec![BracketPair {
25349                    start: "<".into(),
25350                    end: ">".into(),
25351                    close: true,
25352                    ..Default::default()
25353                }],
25354                ..Default::default()
25355            },
25356            linked_edit_characters: HashSet::from_iter(['.']),
25357            ..Default::default()
25358        },
25359        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25360    ));
25361    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25362
25363    // Test typing > does not extend linked pair
25364    cx.set_state("<divˇ<div></div>");
25365    cx.update_editor(|editor, _, cx| {
25366        set_linked_edit_ranges(
25367            (Point::new(0, 1), Point::new(0, 4)),
25368            (Point::new(0, 11), Point::new(0, 14)),
25369            editor,
25370            cx,
25371        );
25372    });
25373    cx.update_editor(|editor, window, cx| {
25374        editor.handle_input(">", window, cx);
25375    });
25376    cx.assert_editor_state("<div>ˇ<div></div>");
25377
25378    // Test typing . do extend linked pair
25379    cx.set_state("<Animatedˇ></Animated>");
25380    cx.update_editor(|editor, _, cx| {
25381        set_linked_edit_ranges(
25382            (Point::new(0, 1), Point::new(0, 9)),
25383            (Point::new(0, 12), Point::new(0, 20)),
25384            editor,
25385            cx,
25386        );
25387    });
25388    cx.update_editor(|editor, window, cx| {
25389        editor.handle_input(".", window, cx);
25390    });
25391    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25392    cx.update_editor(|editor, _, cx| {
25393        set_linked_edit_ranges(
25394            (Point::new(0, 1), Point::new(0, 10)),
25395            (Point::new(0, 13), Point::new(0, 21)),
25396            editor,
25397            cx,
25398        );
25399    });
25400    cx.update_editor(|editor, window, cx| {
25401        editor.handle_input("V", window, cx);
25402    });
25403    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25404}
25405
25406#[gpui::test]
25407async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25408    init_test(cx, |_| {});
25409
25410    let fs = FakeFs::new(cx.executor());
25411    fs.insert_tree(
25412        path!("/root"),
25413        json!({
25414            "a": {
25415                "main.rs": "fn main() {}",
25416            },
25417            "foo": {
25418                "bar": {
25419                    "external_file.rs": "pub mod external {}",
25420                }
25421            }
25422        }),
25423    )
25424    .await;
25425
25426    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25428    language_registry.add(rust_lang());
25429    let _fake_servers = language_registry.register_fake_lsp(
25430        "Rust",
25431        FakeLspAdapter {
25432            ..FakeLspAdapter::default()
25433        },
25434    );
25435    let (workspace, cx) =
25436        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25437    let worktree_id = workspace.update(cx, |workspace, cx| {
25438        workspace.project().update(cx, |project, cx| {
25439            project.worktrees(cx).next().unwrap().read(cx).id()
25440        })
25441    });
25442
25443    let assert_language_servers_count =
25444        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25445            project.update(cx, |project, cx| {
25446                let current = project
25447                    .lsp_store()
25448                    .read(cx)
25449                    .as_local()
25450                    .unwrap()
25451                    .language_servers
25452                    .len();
25453                assert_eq!(expected, current, "{context}");
25454            });
25455        };
25456
25457    assert_language_servers_count(
25458        0,
25459        "No servers should be running before any file is open",
25460        cx,
25461    );
25462    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25463    let main_editor = workspace
25464        .update_in(cx, |workspace, window, cx| {
25465            workspace.open_path(
25466                (worktree_id, rel_path("main.rs")),
25467                Some(pane.downgrade()),
25468                true,
25469                window,
25470                cx,
25471            )
25472        })
25473        .unwrap()
25474        .await
25475        .downcast::<Editor>()
25476        .unwrap();
25477    pane.update(cx, |pane, cx| {
25478        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25479        open_editor.update(cx, |editor, cx| {
25480            assert_eq!(
25481                editor.display_text(cx),
25482                "fn main() {}",
25483                "Original main.rs text on initial open",
25484            );
25485        });
25486        assert_eq!(open_editor, main_editor);
25487    });
25488    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25489
25490    let external_editor = workspace
25491        .update_in(cx, |workspace, window, cx| {
25492            workspace.open_abs_path(
25493                PathBuf::from("/root/foo/bar/external_file.rs"),
25494                OpenOptions::default(),
25495                window,
25496                cx,
25497            )
25498        })
25499        .await
25500        .expect("opening external file")
25501        .downcast::<Editor>()
25502        .expect("downcasted external file's open element to editor");
25503    pane.update(cx, |pane, cx| {
25504        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25505        open_editor.update(cx, |editor, cx| {
25506            assert_eq!(
25507                editor.display_text(cx),
25508                "pub mod external {}",
25509                "External file is open now",
25510            );
25511        });
25512        assert_eq!(open_editor, external_editor);
25513    });
25514    assert_language_servers_count(
25515        1,
25516        "Second, external, *.rs file should join the existing server",
25517        cx,
25518    );
25519
25520    pane.update_in(cx, |pane, window, cx| {
25521        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25522    })
25523    .await
25524    .unwrap();
25525    pane.update_in(cx, |pane, window, cx| {
25526        pane.navigate_backward(&Default::default(), window, cx);
25527    });
25528    cx.run_until_parked();
25529    pane.update(cx, |pane, cx| {
25530        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25531        open_editor.update(cx, |editor, cx| {
25532            assert_eq!(
25533                editor.display_text(cx),
25534                "pub mod external {}",
25535                "External file is open now",
25536            );
25537        });
25538    });
25539    assert_language_servers_count(
25540        1,
25541        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25542        cx,
25543    );
25544
25545    cx.update(|_, cx| {
25546        workspace::reload(cx);
25547    });
25548    assert_language_servers_count(
25549        1,
25550        "After reloading the worktree with local and external files opened, only one project should be started",
25551        cx,
25552    );
25553}
25554
25555#[gpui::test]
25556async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25557    init_test(cx, |_| {});
25558
25559    let mut cx = EditorTestContext::new(cx).await;
25560    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25562
25563    // test cursor move to start of each line on tab
25564    // for `if`, `elif`, `else`, `while`, `with` and `for`
25565    cx.set_state(indoc! {"
25566        def main():
25567        ˇ    for item in items:
25568        ˇ        while item.active:
25569        ˇ            if item.value > 10:
25570        ˇ                continue
25571        ˇ            elif item.value < 0:
25572        ˇ                break
25573        ˇ            else:
25574        ˇ                with item.context() as ctx:
25575        ˇ                    yield count
25576        ˇ        else:
25577        ˇ            log('while else')
25578        ˇ    else:
25579        ˇ        log('for else')
25580    "});
25581    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25582    cx.wait_for_autoindent_applied().await;
25583    cx.assert_editor_state(indoc! {"
25584        def main():
25585            ˇfor item in items:
25586                ˇwhile item.active:
25587                    ˇif item.value > 10:
25588                        ˇcontinue
25589                    ˇelif item.value < 0:
25590                        ˇbreak
25591                    ˇelse:
25592                        ˇwith item.context() as ctx:
25593                            ˇyield count
25594                ˇelse:
25595                    ˇlog('while else')
25596            ˇelse:
25597                ˇlog('for else')
25598    "});
25599    // test relative indent is preserved when tab
25600    // for `if`, `elif`, `else`, `while`, `with` and `for`
25601    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25602    cx.wait_for_autoindent_applied().await;
25603    cx.assert_editor_state(indoc! {"
25604        def main():
25605                ˇfor item in items:
25606                    ˇwhile item.active:
25607                        ˇif item.value > 10:
25608                            ˇcontinue
25609                        ˇelif item.value < 0:
25610                            ˇbreak
25611                        ˇelse:
25612                            ˇwith item.context() as ctx:
25613                                ˇyield count
25614                    ˇelse:
25615                        ˇlog('while else')
25616                ˇelse:
25617                    ˇlog('for else')
25618    "});
25619
25620    // test cursor move to start of each line on tab
25621    // for `try`, `except`, `else`, `finally`, `match` and `def`
25622    cx.set_state(indoc! {"
25623        def main():
25624        ˇ    try:
25625        ˇ        fetch()
25626        ˇ    except ValueError:
25627        ˇ        handle_error()
25628        ˇ    else:
25629        ˇ        match value:
25630        ˇ            case _:
25631        ˇ    finally:
25632        ˇ        def status():
25633        ˇ            return 0
25634    "});
25635    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25636    cx.wait_for_autoindent_applied().await;
25637    cx.assert_editor_state(indoc! {"
25638        def main():
25639            ˇtry:
25640                ˇfetch()
25641            ˇexcept ValueError:
25642                ˇhandle_error()
25643            ˇelse:
25644                ˇmatch value:
25645                    ˇcase _:
25646            ˇfinally:
25647                ˇdef status():
25648                    ˇreturn 0
25649    "});
25650    // test relative indent is preserved when tab
25651    // for `try`, `except`, `else`, `finally`, `match` and `def`
25652    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25653    cx.wait_for_autoindent_applied().await;
25654    cx.assert_editor_state(indoc! {"
25655        def main():
25656                ˇtry:
25657                    ˇfetch()
25658                ˇexcept ValueError:
25659                    ˇhandle_error()
25660                ˇelse:
25661                    ˇmatch value:
25662                        ˇcase _:
25663                ˇfinally:
25664                    ˇdef status():
25665                        ˇreturn 0
25666    "});
25667}
25668
25669#[gpui::test]
25670async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25671    init_test(cx, |_| {});
25672
25673    let mut cx = EditorTestContext::new(cx).await;
25674    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25676
25677    // test `else` auto outdents when typed inside `if` block
25678    cx.set_state(indoc! {"
25679        def main():
25680            if i == 2:
25681                return
25682                ˇ
25683    "});
25684    cx.update_editor(|editor, window, cx| {
25685        editor.handle_input("else:", window, cx);
25686    });
25687    cx.wait_for_autoindent_applied().await;
25688    cx.assert_editor_state(indoc! {"
25689        def main():
25690            if i == 2:
25691                return
25692            else:ˇ
25693    "});
25694
25695    // test `except` auto outdents when typed inside `try` block
25696    cx.set_state(indoc! {"
25697        def main():
25698            try:
25699                i = 2
25700                ˇ
25701    "});
25702    cx.update_editor(|editor, window, cx| {
25703        editor.handle_input("except:", window, cx);
25704    });
25705    cx.wait_for_autoindent_applied().await;
25706    cx.assert_editor_state(indoc! {"
25707        def main():
25708            try:
25709                i = 2
25710            except:ˇ
25711    "});
25712
25713    // test `else` auto outdents when typed inside `except` block
25714    cx.set_state(indoc! {"
25715        def main():
25716            try:
25717                i = 2
25718            except:
25719                j = 2
25720                ˇ
25721    "});
25722    cx.update_editor(|editor, window, cx| {
25723        editor.handle_input("else:", window, cx);
25724    });
25725    cx.wait_for_autoindent_applied().await;
25726    cx.assert_editor_state(indoc! {"
25727        def main():
25728            try:
25729                i = 2
25730            except:
25731                j = 2
25732            else:ˇ
25733    "});
25734
25735    // test `finally` auto outdents when typed inside `else` block
25736    cx.set_state(indoc! {"
25737        def main():
25738            try:
25739                i = 2
25740            except:
25741                j = 2
25742            else:
25743                k = 2
25744                ˇ
25745    "});
25746    cx.update_editor(|editor, window, cx| {
25747        editor.handle_input("finally:", window, cx);
25748    });
25749    cx.wait_for_autoindent_applied().await;
25750    cx.assert_editor_state(indoc! {"
25751        def main():
25752            try:
25753                i = 2
25754            except:
25755                j = 2
25756            else:
25757                k = 2
25758            finally:ˇ
25759    "});
25760
25761    // test `else` does not outdents when typed inside `except` block right after for block
25762    cx.set_state(indoc! {"
25763        def main():
25764            try:
25765                i = 2
25766            except:
25767                for i in range(n):
25768                    pass
25769                ˇ
25770    "});
25771    cx.update_editor(|editor, window, cx| {
25772        editor.handle_input("else:", window, cx);
25773    });
25774    cx.wait_for_autoindent_applied().await;
25775    cx.assert_editor_state(indoc! {"
25776        def main():
25777            try:
25778                i = 2
25779            except:
25780                for i in range(n):
25781                    pass
25782                else:ˇ
25783    "});
25784
25785    // test `finally` auto outdents when typed inside `else` block right after for block
25786    cx.set_state(indoc! {"
25787        def main():
25788            try:
25789                i = 2
25790            except:
25791                j = 2
25792            else:
25793                for i in range(n):
25794                    pass
25795                ˇ
25796    "});
25797    cx.update_editor(|editor, window, cx| {
25798        editor.handle_input("finally:", window, cx);
25799    });
25800    cx.wait_for_autoindent_applied().await;
25801    cx.assert_editor_state(indoc! {"
25802        def main():
25803            try:
25804                i = 2
25805            except:
25806                j = 2
25807            else:
25808                for i in range(n):
25809                    pass
25810            finally:ˇ
25811    "});
25812
25813    // test `except` outdents to inner "try" block
25814    cx.set_state(indoc! {"
25815        def main():
25816            try:
25817                i = 2
25818                if i == 2:
25819                    try:
25820                        i = 3
25821                        ˇ
25822    "});
25823    cx.update_editor(|editor, window, cx| {
25824        editor.handle_input("except:", window, cx);
25825    });
25826    cx.wait_for_autoindent_applied().await;
25827    cx.assert_editor_state(indoc! {"
25828        def main():
25829            try:
25830                i = 2
25831                if i == 2:
25832                    try:
25833                        i = 3
25834                    except:ˇ
25835    "});
25836
25837    // test `except` outdents to outer "try" block
25838    cx.set_state(indoc! {"
25839        def main():
25840            try:
25841                i = 2
25842                if i == 2:
25843                    try:
25844                        i = 3
25845                ˇ
25846    "});
25847    cx.update_editor(|editor, window, cx| {
25848        editor.handle_input("except:", window, cx);
25849    });
25850    cx.wait_for_autoindent_applied().await;
25851    cx.assert_editor_state(indoc! {"
25852        def main():
25853            try:
25854                i = 2
25855                if i == 2:
25856                    try:
25857                        i = 3
25858            except:ˇ
25859    "});
25860
25861    // test `else` stays at correct indent when typed after `for` block
25862    cx.set_state(indoc! {"
25863        def main():
25864            for i in range(10):
25865                if i == 3:
25866                    break
25867            ˇ
25868    "});
25869    cx.update_editor(|editor, window, cx| {
25870        editor.handle_input("else:", window, cx);
25871    });
25872    cx.wait_for_autoindent_applied().await;
25873    cx.assert_editor_state(indoc! {"
25874        def main():
25875            for i in range(10):
25876                if i == 3:
25877                    break
25878            else:ˇ
25879    "});
25880
25881    // test does not outdent on typing after line with square brackets
25882    cx.set_state(indoc! {"
25883        def f() -> list[str]:
25884            ˇ
25885    "});
25886    cx.update_editor(|editor, window, cx| {
25887        editor.handle_input("a", window, cx);
25888    });
25889    cx.wait_for_autoindent_applied().await;
25890    cx.assert_editor_state(indoc! {"
25891        def f() -> list[str]:
2589225893    "});
25894
25895    // test does not outdent on typing : after case keyword
25896    cx.set_state(indoc! {"
25897        match 1:
25898            caseˇ
25899    "});
25900    cx.update_editor(|editor, window, cx| {
25901        editor.handle_input(":", window, cx);
25902    });
25903    cx.wait_for_autoindent_applied().await;
25904    cx.assert_editor_state(indoc! {"
25905        match 1:
25906            case:ˇ
25907    "});
25908}
25909
25910#[gpui::test]
25911async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25912    init_test(cx, |_| {});
25913    update_test_language_settings(cx, |settings| {
25914        settings.defaults.extend_comment_on_newline = Some(false);
25915    });
25916    let mut cx = EditorTestContext::new(cx).await;
25917    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25918    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25919
25920    // test correct indent after newline on comment
25921    cx.set_state(indoc! {"
25922        # COMMENT:ˇ
25923    "});
25924    cx.update_editor(|editor, window, cx| {
25925        editor.newline(&Newline, window, cx);
25926    });
25927    cx.wait_for_autoindent_applied().await;
25928    cx.assert_editor_state(indoc! {"
25929        # COMMENT:
25930        ˇ
25931    "});
25932
25933    // test correct indent after newline in brackets
25934    cx.set_state(indoc! {"
25935        {ˇ}
25936    "});
25937    cx.update_editor(|editor, window, cx| {
25938        editor.newline(&Newline, window, cx);
25939    });
25940    cx.wait_for_autoindent_applied().await;
25941    cx.assert_editor_state(indoc! {"
25942        {
25943            ˇ
25944        }
25945    "});
25946
25947    cx.set_state(indoc! {"
25948        (ˇ)
25949    "});
25950    cx.update_editor(|editor, window, cx| {
25951        editor.newline(&Newline, window, cx);
25952    });
25953    cx.run_until_parked();
25954    cx.assert_editor_state(indoc! {"
25955        (
25956            ˇ
25957        )
25958    "});
25959
25960    // do not indent after empty lists or dictionaries
25961    cx.set_state(indoc! {"
25962        a = []ˇ
25963    "});
25964    cx.update_editor(|editor, window, cx| {
25965        editor.newline(&Newline, window, cx);
25966    });
25967    cx.run_until_parked();
25968    cx.assert_editor_state(indoc! {"
25969        a = []
25970        ˇ
25971    "});
25972}
25973
25974#[gpui::test]
25975async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
25976    init_test(cx, |_| {});
25977
25978    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
25979    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
25980    language_registry.add(markdown_lang());
25981    language_registry.add(python_lang);
25982
25983    let mut cx = EditorTestContext::new(cx).await;
25984    cx.update_buffer(|buffer, cx| {
25985        buffer.set_language_registry(language_registry);
25986        buffer.set_language(Some(markdown_lang()), cx);
25987    });
25988
25989    // Test that `else:` correctly outdents to match `if:` inside the Python code block
25990    cx.set_state(indoc! {"
25991        # Heading
25992
25993        ```python
25994        def main():
25995            if condition:
25996                pass
25997                ˇ
25998        ```
25999    "});
26000    cx.update_editor(|editor, window, cx| {
26001        editor.handle_input("else:", window, cx);
26002    });
26003    cx.run_until_parked();
26004    cx.assert_editor_state(indoc! {"
26005        # Heading
26006
26007        ```python
26008        def main():
26009            if condition:
26010                pass
26011            else:ˇ
26012        ```
26013    "});
26014}
26015
26016#[gpui::test]
26017async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26018    init_test(cx, |_| {});
26019
26020    let mut cx = EditorTestContext::new(cx).await;
26021    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26022    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26023
26024    // test cursor move to start of each line on tab
26025    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26026    cx.set_state(indoc! {"
26027        function main() {
26028        ˇ    for item in $items; do
26029        ˇ        while [ -n \"$item\" ]; do
26030        ˇ            if [ \"$value\" -gt 10 ]; then
26031        ˇ                continue
26032        ˇ            elif [ \"$value\" -lt 0 ]; then
26033        ˇ                break
26034        ˇ            else
26035        ˇ                echo \"$item\"
26036        ˇ            fi
26037        ˇ        done
26038        ˇ    done
26039        ˇ}
26040    "});
26041    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26042    cx.wait_for_autoindent_applied().await;
26043    cx.assert_editor_state(indoc! {"
26044        function main() {
26045            ˇfor item in $items; do
26046                ˇwhile [ -n \"$item\" ]; do
26047                    ˇif [ \"$value\" -gt 10 ]; then
26048                        ˇcontinue
26049                    ˇelif [ \"$value\" -lt 0 ]; then
26050                        ˇbreak
26051                    ˇelse
26052                        ˇecho \"$item\"
26053                    ˇfi
26054                ˇdone
26055            ˇdone
26056        ˇ}
26057    "});
26058    // test relative indent is preserved when tab
26059    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26060    cx.wait_for_autoindent_applied().await;
26061    cx.assert_editor_state(indoc! {"
26062        function main() {
26063                ˇfor item in $items; do
26064                    ˇwhile [ -n \"$item\" ]; do
26065                        ˇif [ \"$value\" -gt 10 ]; then
26066                            ˇcontinue
26067                        ˇelif [ \"$value\" -lt 0 ]; then
26068                            ˇbreak
26069                        ˇelse
26070                            ˇecho \"$item\"
26071                        ˇfi
26072                    ˇdone
26073                ˇdone
26074            ˇ}
26075    "});
26076
26077    // test cursor move to start of each line on tab
26078    // for `case` statement with patterns
26079    cx.set_state(indoc! {"
26080        function handle() {
26081        ˇ    case \"$1\" in
26082        ˇ        start)
26083        ˇ            echo \"a\"
26084        ˇ            ;;
26085        ˇ        stop)
26086        ˇ            echo \"b\"
26087        ˇ            ;;
26088        ˇ        *)
26089        ˇ            echo \"c\"
26090        ˇ            ;;
26091        ˇ    esac
26092        ˇ}
26093    "});
26094    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26095    cx.wait_for_autoindent_applied().await;
26096    cx.assert_editor_state(indoc! {"
26097        function handle() {
26098            ˇcase \"$1\" in
26099                ˇstart)
26100                    ˇecho \"a\"
26101                    ˇ;;
26102                ˇstop)
26103                    ˇecho \"b\"
26104                    ˇ;;
26105                ˇ*)
26106                    ˇecho \"c\"
26107                    ˇ;;
26108            ˇesac
26109        ˇ}
26110    "});
26111}
26112
26113#[gpui::test]
26114async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26115    init_test(cx, |_| {});
26116
26117    let mut cx = EditorTestContext::new(cx).await;
26118    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26120
26121    // test indents on comment insert
26122    cx.set_state(indoc! {"
26123        function main() {
26124        ˇ    for item in $items; do
26125        ˇ        while [ -n \"$item\" ]; do
26126        ˇ            if [ \"$value\" -gt 10 ]; then
26127        ˇ                continue
26128        ˇ            elif [ \"$value\" -lt 0 ]; then
26129        ˇ                break
26130        ˇ            else
26131        ˇ                echo \"$item\"
26132        ˇ            fi
26133        ˇ        done
26134        ˇ    done
26135        ˇ}
26136    "});
26137    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26138    cx.wait_for_autoindent_applied().await;
26139    cx.assert_editor_state(indoc! {"
26140        function main() {
26141        #ˇ    for item in $items; do
26142        #ˇ        while [ -n \"$item\" ]; do
26143        #ˇ            if [ \"$value\" -gt 10 ]; then
26144        #ˇ                continue
26145        #ˇ            elif [ \"$value\" -lt 0 ]; then
26146        #ˇ                break
26147        #ˇ            else
26148        #ˇ                echo \"$item\"
26149        #ˇ            fi
26150        #ˇ        done
26151        #ˇ    done
26152        #ˇ}
26153    "});
26154}
26155
26156#[gpui::test]
26157async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26158    init_test(cx, |_| {});
26159
26160    let mut cx = EditorTestContext::new(cx).await;
26161    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26162    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26163
26164    // test `else` auto outdents when typed inside `if` block
26165    cx.set_state(indoc! {"
26166        if [ \"$1\" = \"test\" ]; then
26167            echo \"foo bar\"
26168            ˇ
26169    "});
26170    cx.update_editor(|editor, window, cx| {
26171        editor.handle_input("else", window, cx);
26172    });
26173    cx.wait_for_autoindent_applied().await;
26174    cx.assert_editor_state(indoc! {"
26175        if [ \"$1\" = \"test\" ]; then
26176            echo \"foo bar\"
26177        elseˇ
26178    "});
26179
26180    // test `elif` auto outdents when typed inside `if` block
26181    cx.set_state(indoc! {"
26182        if [ \"$1\" = \"test\" ]; then
26183            echo \"foo bar\"
26184            ˇ
26185    "});
26186    cx.update_editor(|editor, window, cx| {
26187        editor.handle_input("elif", window, cx);
26188    });
26189    cx.wait_for_autoindent_applied().await;
26190    cx.assert_editor_state(indoc! {"
26191        if [ \"$1\" = \"test\" ]; then
26192            echo \"foo bar\"
26193        elifˇ
26194    "});
26195
26196    // test `fi` auto outdents when typed inside `else` block
26197    cx.set_state(indoc! {"
26198        if [ \"$1\" = \"test\" ]; then
26199            echo \"foo bar\"
26200        else
26201            echo \"bar baz\"
26202            ˇ
26203    "});
26204    cx.update_editor(|editor, window, cx| {
26205        editor.handle_input("fi", window, cx);
26206    });
26207    cx.wait_for_autoindent_applied().await;
26208    cx.assert_editor_state(indoc! {"
26209        if [ \"$1\" = \"test\" ]; then
26210            echo \"foo bar\"
26211        else
26212            echo \"bar baz\"
26213        fiˇ
26214    "});
26215
26216    // test `done` auto outdents when typed inside `while` block
26217    cx.set_state(indoc! {"
26218        while read line; do
26219            echo \"$line\"
26220            ˇ
26221    "});
26222    cx.update_editor(|editor, window, cx| {
26223        editor.handle_input("done", window, cx);
26224    });
26225    cx.wait_for_autoindent_applied().await;
26226    cx.assert_editor_state(indoc! {"
26227        while read line; do
26228            echo \"$line\"
26229        doneˇ
26230    "});
26231
26232    // test `done` auto outdents when typed inside `for` block
26233    cx.set_state(indoc! {"
26234        for file in *.txt; do
26235            cat \"$file\"
26236            ˇ
26237    "});
26238    cx.update_editor(|editor, window, cx| {
26239        editor.handle_input("done", window, cx);
26240    });
26241    cx.wait_for_autoindent_applied().await;
26242    cx.assert_editor_state(indoc! {"
26243        for file in *.txt; do
26244            cat \"$file\"
26245        doneˇ
26246    "});
26247
26248    // test `esac` auto outdents when typed inside `case` block
26249    cx.set_state(indoc! {"
26250        case \"$1\" in
26251            start)
26252                echo \"foo bar\"
26253                ;;
26254            stop)
26255                echo \"bar baz\"
26256                ;;
26257            ˇ
26258    "});
26259    cx.update_editor(|editor, window, cx| {
26260        editor.handle_input("esac", window, cx);
26261    });
26262    cx.wait_for_autoindent_applied().await;
26263    cx.assert_editor_state(indoc! {"
26264        case \"$1\" in
26265            start)
26266                echo \"foo bar\"
26267                ;;
26268            stop)
26269                echo \"bar baz\"
26270                ;;
26271        esacˇ
26272    "});
26273
26274    // test `*)` auto outdents when typed inside `case` block
26275    cx.set_state(indoc! {"
26276        case \"$1\" in
26277            start)
26278                echo \"foo bar\"
26279                ;;
26280                ˇ
26281    "});
26282    cx.update_editor(|editor, window, cx| {
26283        editor.handle_input("*)", window, cx);
26284    });
26285    cx.wait_for_autoindent_applied().await;
26286    cx.assert_editor_state(indoc! {"
26287        case \"$1\" in
26288            start)
26289                echo \"foo bar\"
26290                ;;
26291            *)ˇ
26292    "});
26293
26294    // test `fi` outdents to correct level with nested if blocks
26295    cx.set_state(indoc! {"
26296        if [ \"$1\" = \"test\" ]; then
26297            echo \"outer if\"
26298            if [ \"$2\" = \"debug\" ]; then
26299                echo \"inner if\"
26300                ˇ
26301    "});
26302    cx.update_editor(|editor, window, cx| {
26303        editor.handle_input("fi", window, cx);
26304    });
26305    cx.wait_for_autoindent_applied().await;
26306    cx.assert_editor_state(indoc! {"
26307        if [ \"$1\" = \"test\" ]; then
26308            echo \"outer if\"
26309            if [ \"$2\" = \"debug\" ]; then
26310                echo \"inner if\"
26311            fiˇ
26312    "});
26313}
26314
26315#[gpui::test]
26316async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26317    init_test(cx, |_| {});
26318    update_test_language_settings(cx, |settings| {
26319        settings.defaults.extend_comment_on_newline = Some(false);
26320    });
26321    let mut cx = EditorTestContext::new(cx).await;
26322    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26324
26325    // test correct indent after newline on comment
26326    cx.set_state(indoc! {"
26327        # COMMENT:ˇ
26328    "});
26329    cx.update_editor(|editor, window, cx| {
26330        editor.newline(&Newline, window, cx);
26331    });
26332    cx.wait_for_autoindent_applied().await;
26333    cx.assert_editor_state(indoc! {"
26334        # COMMENT:
26335        ˇ
26336    "});
26337
26338    // test correct indent after newline after `then`
26339    cx.set_state(indoc! {"
26340
26341        if [ \"$1\" = \"test\" ]; thenˇ
26342    "});
26343    cx.update_editor(|editor, window, cx| {
26344        editor.newline(&Newline, window, cx);
26345    });
26346    cx.wait_for_autoindent_applied().await;
26347    cx.assert_editor_state(indoc! {"
26348
26349        if [ \"$1\" = \"test\" ]; then
26350            ˇ
26351    "});
26352
26353    // test correct indent after newline after `else`
26354    cx.set_state(indoc! {"
26355        if [ \"$1\" = \"test\" ]; then
26356        elseˇ
26357    "});
26358    cx.update_editor(|editor, window, cx| {
26359        editor.newline(&Newline, window, cx);
26360    });
26361    cx.wait_for_autoindent_applied().await;
26362    cx.assert_editor_state(indoc! {"
26363        if [ \"$1\" = \"test\" ]; then
26364        else
26365            ˇ
26366    "});
26367
26368    // test correct indent after newline after `elif`
26369    cx.set_state(indoc! {"
26370        if [ \"$1\" = \"test\" ]; then
26371        elifˇ
26372    "});
26373    cx.update_editor(|editor, window, cx| {
26374        editor.newline(&Newline, window, cx);
26375    });
26376    cx.wait_for_autoindent_applied().await;
26377    cx.assert_editor_state(indoc! {"
26378        if [ \"$1\" = \"test\" ]; then
26379        elif
26380            ˇ
26381    "});
26382
26383    // test correct indent after newline after `do`
26384    cx.set_state(indoc! {"
26385        for file in *.txt; doˇ
26386    "});
26387    cx.update_editor(|editor, window, cx| {
26388        editor.newline(&Newline, window, cx);
26389    });
26390    cx.wait_for_autoindent_applied().await;
26391    cx.assert_editor_state(indoc! {"
26392        for file in *.txt; do
26393            ˇ
26394    "});
26395
26396    // test correct indent after newline after case pattern
26397    cx.set_state(indoc! {"
26398        case \"$1\" in
26399            start)ˇ
26400    "});
26401    cx.update_editor(|editor, window, cx| {
26402        editor.newline(&Newline, window, cx);
26403    });
26404    cx.wait_for_autoindent_applied().await;
26405    cx.assert_editor_state(indoc! {"
26406        case \"$1\" in
26407            start)
26408                ˇ
26409    "});
26410
26411    // test correct indent after newline after case pattern
26412    cx.set_state(indoc! {"
26413        case \"$1\" in
26414            start)
26415                ;;
26416            *)ˇ
26417    "});
26418    cx.update_editor(|editor, window, cx| {
26419        editor.newline(&Newline, window, cx);
26420    });
26421    cx.wait_for_autoindent_applied().await;
26422    cx.assert_editor_state(indoc! {"
26423        case \"$1\" in
26424            start)
26425                ;;
26426            *)
26427                ˇ
26428    "});
26429
26430    // test correct indent after newline after function opening brace
26431    cx.set_state(indoc! {"
26432        function test() {ˇ}
26433    "});
26434    cx.update_editor(|editor, window, cx| {
26435        editor.newline(&Newline, window, cx);
26436    });
26437    cx.wait_for_autoindent_applied().await;
26438    cx.assert_editor_state(indoc! {"
26439        function test() {
26440            ˇ
26441        }
26442    "});
26443
26444    // test no extra indent after semicolon on same line
26445    cx.set_state(indoc! {"
26446        echo \"test\"26447    "});
26448    cx.update_editor(|editor, window, cx| {
26449        editor.newline(&Newline, window, cx);
26450    });
26451    cx.wait_for_autoindent_applied().await;
26452    cx.assert_editor_state(indoc! {"
26453        echo \"test\";
26454        ˇ
26455    "});
26456}
26457
26458fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26459    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26460    point..point
26461}
26462
26463#[track_caller]
26464fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26465    let (text, ranges) = marked_text_ranges(marked_text, true);
26466    assert_eq!(editor.text(cx), text);
26467    assert_eq!(
26468        editor.selections.ranges(&editor.display_snapshot(cx)),
26469        ranges
26470            .iter()
26471            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26472            .collect::<Vec<_>>(),
26473        "Assert selections are {}",
26474        marked_text
26475    );
26476}
26477
26478pub fn handle_signature_help_request(
26479    cx: &mut EditorLspTestContext,
26480    mocked_response: lsp::SignatureHelp,
26481) -> impl Future<Output = ()> + use<> {
26482    let mut request =
26483        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26484            let mocked_response = mocked_response.clone();
26485            async move { Ok(Some(mocked_response)) }
26486        });
26487
26488    async move {
26489        request.next().await;
26490    }
26491}
26492
26493#[track_caller]
26494pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26495    cx.update_editor(|editor, _, _| {
26496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26497            let entries = menu.entries.borrow();
26498            let entries = entries
26499                .iter()
26500                .map(|entry| entry.string.as_str())
26501                .collect::<Vec<_>>();
26502            assert_eq!(entries, expected);
26503        } else {
26504            panic!("Expected completions menu");
26505        }
26506    });
26507}
26508
26509#[gpui::test]
26510async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26511    init_test(cx, |_| {});
26512    let mut cx = EditorLspTestContext::new_rust(
26513        lsp::ServerCapabilities {
26514            completion_provider: Some(lsp::CompletionOptions {
26515                ..Default::default()
26516            }),
26517            ..Default::default()
26518        },
26519        cx,
26520    )
26521    .await;
26522    cx.lsp
26523        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26524            Ok(Some(lsp::CompletionResponse::Array(vec![
26525                lsp::CompletionItem {
26526                    label: "unsafe".into(),
26527                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26528                        range: lsp::Range {
26529                            start: lsp::Position {
26530                                line: 0,
26531                                character: 9,
26532                            },
26533                            end: lsp::Position {
26534                                line: 0,
26535                                character: 11,
26536                            },
26537                        },
26538                        new_text: "unsafe".to_string(),
26539                    })),
26540                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26541                    ..Default::default()
26542                },
26543            ])))
26544        });
26545
26546    cx.update_editor(|editor, _, cx| {
26547        editor.project().unwrap().update(cx, |project, cx| {
26548            project.snippets().update(cx, |snippets, _cx| {
26549                snippets.add_snippet_for_test(
26550                    None,
26551                    PathBuf::from("test_snippets.json"),
26552                    vec![
26553                        Arc::new(project::snippet_provider::Snippet {
26554                            prefix: vec![
26555                                "unlimited word count".to_string(),
26556                                "unlimit word count".to_string(),
26557                                "unlimited unknown".to_string(),
26558                            ],
26559                            body: "this is many words".to_string(),
26560                            description: Some("description".to_string()),
26561                            name: "multi-word snippet test".to_string(),
26562                        }),
26563                        Arc::new(project::snippet_provider::Snippet {
26564                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26565                            body: "fewer words".to_string(),
26566                            description: Some("alt description".to_string()),
26567                            name: "other name".to_string(),
26568                        }),
26569                        Arc::new(project::snippet_provider::Snippet {
26570                            prefix: vec!["ab aa".to_string()],
26571                            body: "abcd".to_string(),
26572                            description: None,
26573                            name: "alphabet".to_string(),
26574                        }),
26575                    ],
26576                );
26577            });
26578        })
26579    });
26580
26581    let get_completions = |cx: &mut EditorLspTestContext| {
26582        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26583            Some(CodeContextMenu::Completions(context_menu)) => {
26584                let entries = context_menu.entries.borrow();
26585                entries
26586                    .iter()
26587                    .map(|entry| entry.string.clone())
26588                    .collect_vec()
26589            }
26590            _ => vec![],
26591        })
26592    };
26593
26594    // snippets:
26595    //  @foo
26596    //  foo bar
26597    //
26598    // when typing:
26599    //
26600    // when typing:
26601    //  - if I type a symbol "open the completions with snippets only"
26602    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26603    //
26604    // stuff we need:
26605    //  - filtering logic change?
26606    //  - remember how far back the completion started.
26607
26608    let test_cases: &[(&str, &[&str])] = &[
26609        (
26610            "un",
26611            &[
26612                "unsafe",
26613                "unlimit word count",
26614                "unlimited unknown",
26615                "unlimited word count",
26616                "unsnip",
26617            ],
26618        ),
26619        (
26620            "u ",
26621            &[
26622                "unlimit word count",
26623                "unlimited unknown",
26624                "unlimited word count",
26625            ],
26626        ),
26627        ("u a", &["ab aa", "unsafe"]), // unsAfe
26628        (
26629            "u u",
26630            &[
26631                "unsafe",
26632                "unlimit word count",
26633                "unlimited unknown", // ranked highest among snippets
26634                "unlimited word count",
26635                "unsnip",
26636            ],
26637        ),
26638        ("uw c", &["unlimit word count", "unlimited word count"]),
26639        (
26640            "u w",
26641            &[
26642                "unlimit word count",
26643                "unlimited word count",
26644                "unlimited unknown",
26645            ],
26646        ),
26647        ("u w ", &["unlimit word count", "unlimited word count"]),
26648        (
26649            "u ",
26650            &[
26651                "unlimit word count",
26652                "unlimited unknown",
26653                "unlimited word count",
26654            ],
26655        ),
26656        ("wor", &[]),
26657        ("uf", &["unsafe"]),
26658        ("af", &["unsafe"]),
26659        ("afu", &[]),
26660        (
26661            "ue",
26662            &["unsafe", "unlimited unknown", "unlimited word count"],
26663        ),
26664        ("@", &["@few"]),
26665        ("@few", &["@few"]),
26666        ("@ ", &[]),
26667        ("a@", &["@few"]),
26668        ("a@f", &["@few", "unsafe"]),
26669        ("a@fw", &["@few"]),
26670        ("a", &["ab aa", "unsafe"]),
26671        ("aa", &["ab aa"]),
26672        ("aaa", &["ab aa"]),
26673        ("ab", &["ab aa"]),
26674        ("ab ", &["ab aa"]),
26675        ("ab a", &["ab aa", "unsafe"]),
26676        ("ab ab", &["ab aa"]),
26677        ("ab ab aa", &["ab aa"]),
26678    ];
26679
26680    for &(input_to_simulate, expected_completions) in test_cases {
26681        cx.set_state("fn a() { ˇ }\n");
26682        for c in input_to_simulate.split("") {
26683            cx.simulate_input(c);
26684            cx.run_until_parked();
26685        }
26686        let expected_completions = expected_completions
26687            .iter()
26688            .map(|s| s.to_string())
26689            .collect_vec();
26690        assert_eq!(
26691            get_completions(&mut cx),
26692            expected_completions,
26693            "< actual / expected >, input = {input_to_simulate:?}",
26694        );
26695    }
26696}
26697
26698/// Handle completion request passing a marked string specifying where the completion
26699/// should be triggered from using '|' character, what range should be replaced, and what completions
26700/// should be returned using '<' and '>' to delimit the range.
26701///
26702/// Also see `handle_completion_request_with_insert_and_replace`.
26703#[track_caller]
26704pub fn handle_completion_request(
26705    marked_string: &str,
26706    completions: Vec<&'static str>,
26707    is_incomplete: bool,
26708    counter: Arc<AtomicUsize>,
26709    cx: &mut EditorLspTestContext,
26710) -> impl Future<Output = ()> {
26711    let complete_from_marker: TextRangeMarker = '|'.into();
26712    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26713    let (_, mut marked_ranges) = marked_text_ranges_by(
26714        marked_string,
26715        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26716    );
26717
26718    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26719        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26720    ));
26721    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26722    let replace_range =
26723        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26724
26725    let mut request =
26726        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26727            let completions = completions.clone();
26728            counter.fetch_add(1, atomic::Ordering::Release);
26729            async move {
26730                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26731                assert_eq!(
26732                    params.text_document_position.position,
26733                    complete_from_position
26734                );
26735                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26736                    is_incomplete,
26737                    item_defaults: None,
26738                    items: completions
26739                        .iter()
26740                        .map(|completion_text| lsp::CompletionItem {
26741                            label: completion_text.to_string(),
26742                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26743                                range: replace_range,
26744                                new_text: completion_text.to_string(),
26745                            })),
26746                            ..Default::default()
26747                        })
26748                        .collect(),
26749                })))
26750            }
26751        });
26752
26753    async move {
26754        request.next().await;
26755    }
26756}
26757
26758/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26759/// given instead, which also contains an `insert` range.
26760///
26761/// This function uses markers to define ranges:
26762/// - `|` marks the cursor position
26763/// - `<>` marks the replace range
26764/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26765pub fn handle_completion_request_with_insert_and_replace(
26766    cx: &mut EditorLspTestContext,
26767    marked_string: &str,
26768    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26769    counter: Arc<AtomicUsize>,
26770) -> impl Future<Output = ()> {
26771    let complete_from_marker: TextRangeMarker = '|'.into();
26772    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26773    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26774
26775    let (_, mut marked_ranges) = marked_text_ranges_by(
26776        marked_string,
26777        vec![
26778            complete_from_marker.clone(),
26779            replace_range_marker.clone(),
26780            insert_range_marker.clone(),
26781        ],
26782    );
26783
26784    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26785        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26786    ));
26787    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26788    let replace_range =
26789        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26790
26791    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26792        Some(ranges) if !ranges.is_empty() => {
26793            let range1 = ranges[0].clone();
26794            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26795        }
26796        _ => lsp::Range {
26797            start: replace_range.start,
26798            end: complete_from_position,
26799        },
26800    };
26801
26802    let mut request =
26803        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26804            let completions = completions.clone();
26805            counter.fetch_add(1, atomic::Ordering::Release);
26806            async move {
26807                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26808                assert_eq!(
26809                    params.text_document_position.position, complete_from_position,
26810                    "marker `|` position doesn't match",
26811                );
26812                Ok(Some(lsp::CompletionResponse::Array(
26813                    completions
26814                        .iter()
26815                        .map(|(label, new_text)| lsp::CompletionItem {
26816                            label: label.to_string(),
26817                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26818                                lsp::InsertReplaceEdit {
26819                                    insert: insert_range,
26820                                    replace: replace_range,
26821                                    new_text: new_text.to_string(),
26822                                },
26823                            )),
26824                            ..Default::default()
26825                        })
26826                        .collect(),
26827                )))
26828            }
26829        });
26830
26831    async move {
26832        request.next().await;
26833    }
26834}
26835
26836fn handle_resolve_completion_request(
26837    cx: &mut EditorLspTestContext,
26838    edits: Option<Vec<(&'static str, &'static str)>>,
26839) -> impl Future<Output = ()> {
26840    let edits = edits.map(|edits| {
26841        edits
26842            .iter()
26843            .map(|(marked_string, new_text)| {
26844                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26845                let replace_range = cx.to_lsp_range(
26846                    MultiBufferOffset(marked_ranges[0].start)
26847                        ..MultiBufferOffset(marked_ranges[0].end),
26848                );
26849                lsp::TextEdit::new(replace_range, new_text.to_string())
26850            })
26851            .collect::<Vec<_>>()
26852    });
26853
26854    let mut request =
26855        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26856            let edits = edits.clone();
26857            async move {
26858                Ok(lsp::CompletionItem {
26859                    additional_text_edits: edits,
26860                    ..Default::default()
26861                })
26862            }
26863        });
26864
26865    async move {
26866        request.next().await;
26867    }
26868}
26869
26870pub(crate) fn update_test_language_settings(
26871    cx: &mut TestAppContext,
26872    f: impl Fn(&mut AllLanguageSettingsContent),
26873) {
26874    cx.update(|cx| {
26875        SettingsStore::update_global(cx, |store, cx| {
26876            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26877        });
26878    });
26879}
26880
26881pub(crate) fn update_test_project_settings(
26882    cx: &mut TestAppContext,
26883    f: impl Fn(&mut ProjectSettingsContent),
26884) {
26885    cx.update(|cx| {
26886        SettingsStore::update_global(cx, |store, cx| {
26887            store.update_user_settings(cx, |settings| f(&mut settings.project));
26888        });
26889    });
26890}
26891
26892pub(crate) fn update_test_editor_settings(
26893    cx: &mut TestAppContext,
26894    f: impl Fn(&mut EditorSettingsContent),
26895) {
26896    cx.update(|cx| {
26897        SettingsStore::update_global(cx, |store, cx| {
26898            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26899        })
26900    })
26901}
26902
26903pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26904    cx.update(|cx| {
26905        assets::Assets.load_test_fonts(cx);
26906        let store = SettingsStore::test(cx);
26907        cx.set_global(store);
26908        theme::init(theme::LoadThemes::JustBase, cx);
26909        release_channel::init(semver::Version::new(0, 0, 0), cx);
26910        crate::init(cx);
26911    });
26912    zlog::init_test();
26913    update_test_language_settings(cx, f);
26914}
26915
26916#[track_caller]
26917fn assert_hunk_revert(
26918    not_reverted_text_with_selections: &str,
26919    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26920    expected_reverted_text_with_selections: &str,
26921    base_text: &str,
26922    cx: &mut EditorLspTestContext,
26923) {
26924    cx.set_state(not_reverted_text_with_selections);
26925    cx.set_head_text(base_text);
26926    cx.executor().run_until_parked();
26927
26928    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26929        let snapshot = editor.snapshot(window, cx);
26930        let reverted_hunk_statuses = snapshot
26931            .buffer_snapshot()
26932            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26933            .map(|hunk| hunk.status().kind)
26934            .collect::<Vec<_>>();
26935
26936        editor.git_restore(&Default::default(), window, cx);
26937        reverted_hunk_statuses
26938    });
26939    cx.executor().run_until_parked();
26940    cx.assert_editor_state(expected_reverted_text_with_selections);
26941    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26942}
26943
26944#[gpui::test(iterations = 10)]
26945async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26946    init_test(cx, |_| {});
26947
26948    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26949    let counter = diagnostic_requests.clone();
26950
26951    let fs = FakeFs::new(cx.executor());
26952    fs.insert_tree(
26953        path!("/a"),
26954        json!({
26955            "first.rs": "fn main() { let a = 5; }",
26956            "second.rs": "// Test file",
26957        }),
26958    )
26959    .await;
26960
26961    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26962    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26963    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26964
26965    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26966    language_registry.add(rust_lang());
26967    let mut fake_servers = language_registry.register_fake_lsp(
26968        "Rust",
26969        FakeLspAdapter {
26970            capabilities: lsp::ServerCapabilities {
26971                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26972                    lsp::DiagnosticOptions {
26973                        identifier: None,
26974                        inter_file_dependencies: true,
26975                        workspace_diagnostics: true,
26976                        work_done_progress_options: Default::default(),
26977                    },
26978                )),
26979                ..Default::default()
26980            },
26981            ..Default::default()
26982        },
26983    );
26984
26985    let editor = workspace
26986        .update(cx, |workspace, window, cx| {
26987            workspace.open_abs_path(
26988                PathBuf::from(path!("/a/first.rs")),
26989                OpenOptions::default(),
26990                window,
26991                cx,
26992            )
26993        })
26994        .unwrap()
26995        .await
26996        .unwrap()
26997        .downcast::<Editor>()
26998        .unwrap();
26999    let fake_server = fake_servers.next().await.unwrap();
27000    let server_id = fake_server.server.server_id();
27001    let mut first_request = fake_server
27002        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27003            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27004            let result_id = Some(new_result_id.to_string());
27005            assert_eq!(
27006                params.text_document.uri,
27007                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27008            );
27009            async move {
27010                Ok(lsp::DocumentDiagnosticReportResult::Report(
27011                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27012                        related_documents: None,
27013                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27014                            items: Vec::new(),
27015                            result_id,
27016                        },
27017                    }),
27018                ))
27019            }
27020        });
27021
27022    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27023        project.update(cx, |project, cx| {
27024            let buffer_id = editor
27025                .read(cx)
27026                .buffer()
27027                .read(cx)
27028                .as_singleton()
27029                .expect("created a singleton buffer")
27030                .read(cx)
27031                .remote_id();
27032            let buffer_result_id = project
27033                .lsp_store()
27034                .read(cx)
27035                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27036            assert_eq!(expected, buffer_result_id);
27037        });
27038    };
27039
27040    ensure_result_id(None, cx);
27041    cx.executor().advance_clock(Duration::from_millis(60));
27042    cx.executor().run_until_parked();
27043    assert_eq!(
27044        diagnostic_requests.load(atomic::Ordering::Acquire),
27045        1,
27046        "Opening file should trigger diagnostic request"
27047    );
27048    first_request
27049        .next()
27050        .await
27051        .expect("should have sent the first diagnostics pull request");
27052    ensure_result_id(Some(SharedString::new("1")), cx);
27053
27054    // Editing should trigger diagnostics
27055    editor.update_in(cx, |editor, window, cx| {
27056        editor.handle_input("2", window, cx)
27057    });
27058    cx.executor().advance_clock(Duration::from_millis(60));
27059    cx.executor().run_until_parked();
27060    assert_eq!(
27061        diagnostic_requests.load(atomic::Ordering::Acquire),
27062        2,
27063        "Editing should trigger diagnostic request"
27064    );
27065    ensure_result_id(Some(SharedString::new("2")), cx);
27066
27067    // Moving cursor should not trigger diagnostic request
27068    editor.update_in(cx, |editor, window, cx| {
27069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27070            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27071        });
27072    });
27073    cx.executor().advance_clock(Duration::from_millis(60));
27074    cx.executor().run_until_parked();
27075    assert_eq!(
27076        diagnostic_requests.load(atomic::Ordering::Acquire),
27077        2,
27078        "Cursor movement should not trigger diagnostic request"
27079    );
27080    ensure_result_id(Some(SharedString::new("2")), cx);
27081    // Multiple rapid edits should be debounced
27082    for _ in 0..5 {
27083        editor.update_in(cx, |editor, window, cx| {
27084            editor.handle_input("x", window, cx)
27085        });
27086    }
27087    cx.executor().advance_clock(Duration::from_millis(60));
27088    cx.executor().run_until_parked();
27089
27090    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27091    assert!(
27092        final_requests <= 4,
27093        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27094    );
27095    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27096}
27097
27098#[gpui::test]
27099async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27100    // Regression test for issue #11671
27101    // Previously, adding a cursor after moving multiple cursors would reset
27102    // the cursor count instead of adding to the existing cursors.
27103    init_test(cx, |_| {});
27104    let mut cx = EditorTestContext::new(cx).await;
27105
27106    // Create a simple buffer with cursor at start
27107    cx.set_state(indoc! {"
27108        ˇaaaa
27109        bbbb
27110        cccc
27111        dddd
27112        eeee
27113        ffff
27114        gggg
27115        hhhh"});
27116
27117    // Add 2 cursors below (so we have 3 total)
27118    cx.update_editor(|editor, window, cx| {
27119        editor.add_selection_below(&Default::default(), window, cx);
27120        editor.add_selection_below(&Default::default(), window, cx);
27121    });
27122
27123    // Verify we have 3 cursors
27124    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27125    assert_eq!(
27126        initial_count, 3,
27127        "Should have 3 cursors after adding 2 below"
27128    );
27129
27130    // Move down one line
27131    cx.update_editor(|editor, window, cx| {
27132        editor.move_down(&MoveDown, window, cx);
27133    });
27134
27135    // Add another cursor below
27136    cx.update_editor(|editor, window, cx| {
27137        editor.add_selection_below(&Default::default(), window, cx);
27138    });
27139
27140    // Should now have 4 cursors (3 original + 1 new)
27141    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27142    assert_eq!(
27143        final_count, 4,
27144        "Should have 4 cursors after moving and adding another"
27145    );
27146}
27147
27148#[gpui::test]
27149async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27150    init_test(cx, |_| {});
27151
27152    let mut cx = EditorTestContext::new(cx).await;
27153
27154    cx.set_state(indoc!(
27155        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27156           Second line here"#
27157    ));
27158
27159    cx.update_editor(|editor, window, cx| {
27160        // Enable soft wrapping with a narrow width to force soft wrapping and
27161        // confirm that more than 2 rows are being displayed.
27162        editor.set_wrap_width(Some(100.0.into()), cx);
27163        assert!(editor.display_text(cx).lines().count() > 2);
27164
27165        editor.add_selection_below(
27166            &AddSelectionBelow {
27167                skip_soft_wrap: true,
27168            },
27169            window,
27170            cx,
27171        );
27172
27173        assert_eq!(
27174            display_ranges(editor, cx),
27175            &[
27176                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27177                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27178            ]
27179        );
27180
27181        editor.add_selection_above(
27182            &AddSelectionAbove {
27183                skip_soft_wrap: true,
27184            },
27185            window,
27186            cx,
27187        );
27188
27189        assert_eq!(
27190            display_ranges(editor, cx),
27191            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27192        );
27193
27194        editor.add_selection_below(
27195            &AddSelectionBelow {
27196                skip_soft_wrap: false,
27197            },
27198            window,
27199            cx,
27200        );
27201
27202        assert_eq!(
27203            display_ranges(editor, cx),
27204            &[
27205                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27206                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27207            ]
27208        );
27209
27210        editor.add_selection_above(
27211            &AddSelectionAbove {
27212                skip_soft_wrap: false,
27213            },
27214            window,
27215            cx,
27216        );
27217
27218        assert_eq!(
27219            display_ranges(editor, cx),
27220            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27221        );
27222    });
27223}
27224
27225#[gpui::test]
27226async fn test_insert_snippet(cx: &mut TestAppContext) {
27227    init_test(cx, |_| {});
27228    let mut cx = EditorTestContext::new(cx).await;
27229
27230    cx.update_editor(|editor, _, cx| {
27231        editor.project().unwrap().update(cx, |project, cx| {
27232            project.snippets().update(cx, |snippets, _cx| {
27233                let snippet = project::snippet_provider::Snippet {
27234                    prefix: vec![], // no prefix needed!
27235                    body: "an Unspecified".to_string(),
27236                    description: Some("shhhh it's a secret".to_string()),
27237                    name: "super secret snippet".to_string(),
27238                };
27239                snippets.add_snippet_for_test(
27240                    None,
27241                    PathBuf::from("test_snippets.json"),
27242                    vec![Arc::new(snippet)],
27243                );
27244
27245                let snippet = project::snippet_provider::Snippet {
27246                    prefix: vec![], // no prefix needed!
27247                    body: " Location".to_string(),
27248                    description: Some("the word 'location'".to_string()),
27249                    name: "location word".to_string(),
27250                };
27251                snippets.add_snippet_for_test(
27252                    Some("Markdown".to_string()),
27253                    PathBuf::from("test_snippets.json"),
27254                    vec![Arc::new(snippet)],
27255                );
27256            });
27257        })
27258    });
27259
27260    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27261
27262    cx.update_editor(|editor, window, cx| {
27263        editor.insert_snippet_at_selections(
27264            &InsertSnippet {
27265                language: None,
27266                name: Some("super secret snippet".to_string()),
27267                snippet: None,
27268            },
27269            window,
27270            cx,
27271        );
27272
27273        // Language is specified in the action,
27274        // so the buffer language does not need to match
27275        editor.insert_snippet_at_selections(
27276            &InsertSnippet {
27277                language: Some("Markdown".to_string()),
27278                name: Some("location word".to_string()),
27279                snippet: None,
27280            },
27281            window,
27282            cx,
27283        );
27284
27285        editor.insert_snippet_at_selections(
27286            &InsertSnippet {
27287                language: None,
27288                name: None,
27289                snippet: Some("$0 after".to_string()),
27290            },
27291            window,
27292            cx,
27293        );
27294    });
27295
27296    cx.assert_editor_state(
27297        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27298    );
27299}
27300
27301#[gpui::test(iterations = 10)]
27302async fn test_document_colors(cx: &mut TestAppContext) {
27303    let expected_color = Rgba {
27304        r: 0.33,
27305        g: 0.33,
27306        b: 0.33,
27307        a: 0.33,
27308    };
27309
27310    init_test(cx, |_| {});
27311
27312    let fs = FakeFs::new(cx.executor());
27313    fs.insert_tree(
27314        path!("/a"),
27315        json!({
27316            "first.rs": "fn main() { let a = 5; }",
27317        }),
27318    )
27319    .await;
27320
27321    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27322    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27323    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27324
27325    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27326    language_registry.add(rust_lang());
27327    let mut fake_servers = language_registry.register_fake_lsp(
27328        "Rust",
27329        FakeLspAdapter {
27330            capabilities: lsp::ServerCapabilities {
27331                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27332                ..lsp::ServerCapabilities::default()
27333            },
27334            name: "rust-analyzer",
27335            ..FakeLspAdapter::default()
27336        },
27337    );
27338    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27339        "Rust",
27340        FakeLspAdapter {
27341            capabilities: lsp::ServerCapabilities {
27342                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27343                ..lsp::ServerCapabilities::default()
27344            },
27345            name: "not-rust-analyzer",
27346            ..FakeLspAdapter::default()
27347        },
27348    );
27349
27350    let editor = workspace
27351        .update(cx, |workspace, window, cx| {
27352            workspace.open_abs_path(
27353                PathBuf::from(path!("/a/first.rs")),
27354                OpenOptions::default(),
27355                window,
27356                cx,
27357            )
27358        })
27359        .unwrap()
27360        .await
27361        .unwrap()
27362        .downcast::<Editor>()
27363        .unwrap();
27364    let fake_language_server = fake_servers.next().await.unwrap();
27365    let fake_language_server_without_capabilities =
27366        fake_servers_without_capabilities.next().await.unwrap();
27367    let requests_made = Arc::new(AtomicUsize::new(0));
27368    let closure_requests_made = Arc::clone(&requests_made);
27369    let mut color_request_handle = fake_language_server
27370        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27371            let requests_made = Arc::clone(&closure_requests_made);
27372            async move {
27373                assert_eq!(
27374                    params.text_document.uri,
27375                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27376                );
27377                requests_made.fetch_add(1, atomic::Ordering::Release);
27378                Ok(vec![
27379                    lsp::ColorInformation {
27380                        range: lsp::Range {
27381                            start: lsp::Position {
27382                                line: 0,
27383                                character: 0,
27384                            },
27385                            end: lsp::Position {
27386                                line: 0,
27387                                character: 1,
27388                            },
27389                        },
27390                        color: lsp::Color {
27391                            red: 0.33,
27392                            green: 0.33,
27393                            blue: 0.33,
27394                            alpha: 0.33,
27395                        },
27396                    },
27397                    lsp::ColorInformation {
27398                        range: lsp::Range {
27399                            start: lsp::Position {
27400                                line: 0,
27401                                character: 0,
27402                            },
27403                            end: lsp::Position {
27404                                line: 0,
27405                                character: 1,
27406                            },
27407                        },
27408                        color: lsp::Color {
27409                            red: 0.33,
27410                            green: 0.33,
27411                            blue: 0.33,
27412                            alpha: 0.33,
27413                        },
27414                    },
27415                ])
27416            }
27417        });
27418
27419    let _handle = fake_language_server_without_capabilities
27420        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27421            panic!("Should not be called");
27422        });
27423    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27424    color_request_handle.next().await.unwrap();
27425    cx.run_until_parked();
27426    assert_eq!(
27427        1,
27428        requests_made.load(atomic::Ordering::Acquire),
27429        "Should query for colors once per editor open"
27430    );
27431    editor.update_in(cx, |editor, _, cx| {
27432        assert_eq!(
27433            vec![expected_color],
27434            extract_color_inlays(editor, cx),
27435            "Should have an initial inlay"
27436        );
27437    });
27438
27439    // opening another file in a split should not influence the LSP query counter
27440    workspace
27441        .update(cx, |workspace, window, cx| {
27442            assert_eq!(
27443                workspace.panes().len(),
27444                1,
27445                "Should have one pane with one editor"
27446            );
27447            workspace.move_item_to_pane_in_direction(
27448                &MoveItemToPaneInDirection {
27449                    direction: SplitDirection::Right,
27450                    focus: false,
27451                    clone: true,
27452                },
27453                window,
27454                cx,
27455            );
27456        })
27457        .unwrap();
27458    cx.run_until_parked();
27459    workspace
27460        .update(cx, |workspace, _, cx| {
27461            let panes = workspace.panes();
27462            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27463            for pane in panes {
27464                let editor = pane
27465                    .read(cx)
27466                    .active_item()
27467                    .and_then(|item| item.downcast::<Editor>())
27468                    .expect("Should have opened an editor in each split");
27469                let editor_file = editor
27470                    .read(cx)
27471                    .buffer()
27472                    .read(cx)
27473                    .as_singleton()
27474                    .expect("test deals with singleton buffers")
27475                    .read(cx)
27476                    .file()
27477                    .expect("test buffese should have a file")
27478                    .path();
27479                assert_eq!(
27480                    editor_file.as_ref(),
27481                    rel_path("first.rs"),
27482                    "Both editors should be opened for the same file"
27483                )
27484            }
27485        })
27486        .unwrap();
27487
27488    cx.executor().advance_clock(Duration::from_millis(500));
27489    let save = editor.update_in(cx, |editor, window, cx| {
27490        editor.move_to_end(&MoveToEnd, window, cx);
27491        editor.handle_input("dirty", window, cx);
27492        editor.save(
27493            SaveOptions {
27494                format: true,
27495                autosave: true,
27496            },
27497            project.clone(),
27498            window,
27499            cx,
27500        )
27501    });
27502    save.await.unwrap();
27503
27504    color_request_handle.next().await.unwrap();
27505    cx.run_until_parked();
27506    assert_eq!(
27507        2,
27508        requests_made.load(atomic::Ordering::Acquire),
27509        "Should query for colors once per save (deduplicated) and once per formatting after save"
27510    );
27511
27512    drop(editor);
27513    let close = workspace
27514        .update(cx, |workspace, window, cx| {
27515            workspace.active_pane().update(cx, |pane, cx| {
27516                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27517            })
27518        })
27519        .unwrap();
27520    close.await.unwrap();
27521    let close = workspace
27522        .update(cx, |workspace, window, cx| {
27523            workspace.active_pane().update(cx, |pane, cx| {
27524                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27525            })
27526        })
27527        .unwrap();
27528    close.await.unwrap();
27529    assert_eq!(
27530        2,
27531        requests_made.load(atomic::Ordering::Acquire),
27532        "After saving and closing all editors, no extra requests should be made"
27533    );
27534    workspace
27535        .update(cx, |workspace, _, cx| {
27536            assert!(
27537                workspace.active_item(cx).is_none(),
27538                "Should close all editors"
27539            )
27540        })
27541        .unwrap();
27542
27543    workspace
27544        .update(cx, |workspace, window, cx| {
27545            workspace.active_pane().update(cx, |pane, cx| {
27546                pane.navigate_backward(&workspace::GoBack, window, cx);
27547            })
27548        })
27549        .unwrap();
27550    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27551    cx.run_until_parked();
27552    let editor = workspace
27553        .update(cx, |workspace, _, cx| {
27554            workspace
27555                .active_item(cx)
27556                .expect("Should have reopened the editor again after navigating back")
27557                .downcast::<Editor>()
27558                .expect("Should be an editor")
27559        })
27560        .unwrap();
27561
27562    assert_eq!(
27563        2,
27564        requests_made.load(atomic::Ordering::Acquire),
27565        "Cache should be reused on buffer close and reopen"
27566    );
27567    editor.update(cx, |editor, cx| {
27568        assert_eq!(
27569            vec![expected_color],
27570            extract_color_inlays(editor, cx),
27571            "Should have an initial inlay"
27572        );
27573    });
27574
27575    drop(color_request_handle);
27576    let closure_requests_made = Arc::clone(&requests_made);
27577    let mut empty_color_request_handle = fake_language_server
27578        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27579            let requests_made = Arc::clone(&closure_requests_made);
27580            async move {
27581                assert_eq!(
27582                    params.text_document.uri,
27583                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27584                );
27585                requests_made.fetch_add(1, atomic::Ordering::Release);
27586                Ok(Vec::new())
27587            }
27588        });
27589    let save = editor.update_in(cx, |editor, window, cx| {
27590        editor.move_to_end(&MoveToEnd, window, cx);
27591        editor.handle_input("dirty_again", window, cx);
27592        editor.save(
27593            SaveOptions {
27594                format: false,
27595                autosave: true,
27596            },
27597            project.clone(),
27598            window,
27599            cx,
27600        )
27601    });
27602    save.await.unwrap();
27603
27604    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27605    empty_color_request_handle.next().await.unwrap();
27606    cx.run_until_parked();
27607    assert_eq!(
27608        3,
27609        requests_made.load(atomic::Ordering::Acquire),
27610        "Should query for colors once per save only, as formatting was not requested"
27611    );
27612    editor.update(cx, |editor, cx| {
27613        assert_eq!(
27614            Vec::<Rgba>::new(),
27615            extract_color_inlays(editor, cx),
27616            "Should clear all colors when the server returns an empty response"
27617        );
27618    });
27619}
27620
27621#[gpui::test]
27622async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27623    init_test(cx, |_| {});
27624    let (editor, cx) = cx.add_window_view(Editor::single_line);
27625    editor.update_in(cx, |editor, window, cx| {
27626        editor.set_text("oops\n\nwow\n", window, cx)
27627    });
27628    cx.run_until_parked();
27629    editor.update(cx, |editor, cx| {
27630        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27631    });
27632    editor.update(cx, |editor, cx| {
27633        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27634    });
27635    cx.run_until_parked();
27636    editor.update(cx, |editor, cx| {
27637        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27638    });
27639}
27640
27641#[gpui::test]
27642async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27643    init_test(cx, |_| {});
27644
27645    cx.update(|cx| {
27646        register_project_item::<Editor>(cx);
27647    });
27648
27649    let fs = FakeFs::new(cx.executor());
27650    fs.insert_tree("/root1", json!({})).await;
27651    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27652        .await;
27653
27654    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27655    let (workspace, cx) =
27656        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27657
27658    let worktree_id = project.update(cx, |project, cx| {
27659        project.worktrees(cx).next().unwrap().read(cx).id()
27660    });
27661
27662    let handle = workspace
27663        .update_in(cx, |workspace, window, cx| {
27664            let project_path = (worktree_id, rel_path("one.pdf"));
27665            workspace.open_path(project_path, None, true, window, cx)
27666        })
27667        .await
27668        .unwrap();
27669    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
27670    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
27671    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
27672    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
27673}
27674
27675#[gpui::test]
27676async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27677    init_test(cx, |_| {});
27678
27679    let language = Arc::new(Language::new(
27680        LanguageConfig::default(),
27681        Some(tree_sitter_rust::LANGUAGE.into()),
27682    ));
27683
27684    // Test hierarchical sibling navigation
27685    let text = r#"
27686        fn outer() {
27687            if condition {
27688                let a = 1;
27689            }
27690            let b = 2;
27691        }
27692
27693        fn another() {
27694            let c = 3;
27695        }
27696    "#;
27697
27698    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27699    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27700    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27701
27702    // Wait for parsing to complete
27703    editor
27704        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27705        .await;
27706
27707    editor.update_in(cx, |editor, window, cx| {
27708        // Start by selecting "let a = 1;" inside the if block
27709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27710            s.select_display_ranges([
27711                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27712            ]);
27713        });
27714
27715        let initial_selection = editor
27716            .selections
27717            .display_ranges(&editor.display_snapshot(cx));
27718        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27719
27720        // Test select next sibling - should move up levels to find the next sibling
27721        // Since "let a = 1;" has no siblings in the if block, it should move up
27722        // to find "let b = 2;" which is a sibling of the if block
27723        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27724        let next_selection = editor
27725            .selections
27726            .display_ranges(&editor.display_snapshot(cx));
27727
27728        // Should have a selection and it should be different from the initial
27729        assert_eq!(
27730            next_selection.len(),
27731            1,
27732            "Should have one selection after next"
27733        );
27734        assert_ne!(
27735            next_selection[0], initial_selection[0],
27736            "Next sibling selection should be different"
27737        );
27738
27739        // Test hierarchical navigation by going to the end of the current function
27740        // and trying to navigate to the next function
27741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27742            s.select_display_ranges([
27743                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27744            ]);
27745        });
27746
27747        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27748        let function_next_selection = editor
27749            .selections
27750            .display_ranges(&editor.display_snapshot(cx));
27751
27752        // Should move to the next function
27753        assert_eq!(
27754            function_next_selection.len(),
27755            1,
27756            "Should have one selection after function next"
27757        );
27758
27759        // Test select previous sibling navigation
27760        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27761        let prev_selection = editor
27762            .selections
27763            .display_ranges(&editor.display_snapshot(cx));
27764
27765        // Should have a selection and it should be different
27766        assert_eq!(
27767            prev_selection.len(),
27768            1,
27769            "Should have one selection after prev"
27770        );
27771        assert_ne!(
27772            prev_selection[0], function_next_selection[0],
27773            "Previous sibling selection should be different from next"
27774        );
27775    });
27776}
27777
27778#[gpui::test]
27779async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27780    init_test(cx, |_| {});
27781
27782    let mut cx = EditorTestContext::new(cx).await;
27783    cx.set_state(
27784        "let ˇvariable = 42;
27785let another = variable + 1;
27786let result = variable * 2;",
27787    );
27788
27789    // Set up document highlights manually (simulating LSP response)
27790    cx.update_editor(|editor, _window, cx| {
27791        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27792
27793        // Create highlights for "variable" occurrences
27794        let highlight_ranges = [
27795            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27796            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27797            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27798        ];
27799
27800        let anchor_ranges: Vec<_> = highlight_ranges
27801            .iter()
27802            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27803            .collect();
27804
27805        editor.highlight_background::<DocumentHighlightRead>(
27806            &anchor_ranges,
27807            |_, theme| theme.colors().editor_document_highlight_read_background,
27808            cx,
27809        );
27810    });
27811
27812    // Go to next highlight - should move to second "variable"
27813    cx.update_editor(|editor, window, cx| {
27814        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27815    });
27816    cx.assert_editor_state(
27817        "let variable = 42;
27818let another = ˇvariable + 1;
27819let result = variable * 2;",
27820    );
27821
27822    // Go to next highlight - should move to third "variable"
27823    cx.update_editor(|editor, window, cx| {
27824        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27825    });
27826    cx.assert_editor_state(
27827        "let variable = 42;
27828let another = variable + 1;
27829let result = ˇvariable * 2;",
27830    );
27831
27832    // Go to next highlight - should stay at third "variable" (no wrap-around)
27833    cx.update_editor(|editor, window, cx| {
27834        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27835    });
27836    cx.assert_editor_state(
27837        "let variable = 42;
27838let another = variable + 1;
27839let result = ˇvariable * 2;",
27840    );
27841
27842    // Now test going backwards from third position
27843    cx.update_editor(|editor, window, cx| {
27844        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27845    });
27846    cx.assert_editor_state(
27847        "let variable = 42;
27848let another = ˇvariable + 1;
27849let result = variable * 2;",
27850    );
27851
27852    // Go to previous highlight - should move to first "variable"
27853    cx.update_editor(|editor, window, cx| {
27854        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27855    });
27856    cx.assert_editor_state(
27857        "let ˇvariable = 42;
27858let another = variable + 1;
27859let result = variable * 2;",
27860    );
27861
27862    // Go to previous highlight - should stay on first "variable"
27863    cx.update_editor(|editor, window, cx| {
27864        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27865    });
27866    cx.assert_editor_state(
27867        "let ˇvariable = 42;
27868let another = variable + 1;
27869let result = variable * 2;",
27870    );
27871}
27872
27873#[gpui::test]
27874async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27875    cx: &mut gpui::TestAppContext,
27876) {
27877    init_test(cx, |_| {});
27878
27879    let url = "https://zed.dev";
27880
27881    let markdown_language = Arc::new(Language::new(
27882        LanguageConfig {
27883            name: "Markdown".into(),
27884            ..LanguageConfig::default()
27885        },
27886        None,
27887    ));
27888
27889    let mut cx = EditorTestContext::new(cx).await;
27890    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27891    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27892
27893    cx.update_editor(|editor, window, cx| {
27894        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27895        editor.paste(&Paste, window, cx);
27896    });
27897
27898    cx.assert_editor_state(&format!(
27899        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27900    ));
27901}
27902
27903#[gpui::test]
27904async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27905    init_test(cx, |_| {});
27906
27907    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27908    let mut cx = EditorTestContext::new(cx).await;
27909
27910    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27911
27912    // Case 1: Test if adding a character with multi cursors preserves nested list indents
27913    cx.set_state(&indoc! {"
27914        - [ ] Item 1
27915            - [ ] Item 1.a
27916        - [ˇ] Item 2
27917            - [ˇ] Item 2.a
27918            - [ˇ] Item 2.b
27919        "
27920    });
27921    cx.update_editor(|editor, window, cx| {
27922        editor.handle_input("x", window, cx);
27923    });
27924    cx.run_until_parked();
27925    cx.assert_editor_state(indoc! {"
27926        - [ ] Item 1
27927            - [ ] Item 1.a
27928        - [xˇ] Item 2
27929            - [xˇ] Item 2.a
27930            - [xˇ] Item 2.b
27931        "
27932    });
27933
27934    // Case 2: Test adding new line after nested list preserves indent of previous line
27935    cx.set_state(&indoc! {"
27936        - [ ] Item 1
27937            - [ ] Item 1.a
27938        - [x] Item 2
27939            - [x] Item 2.a
27940            - [x] Item 2.bˇ"
27941    });
27942    cx.update_editor(|editor, window, cx| {
27943        editor.newline(&Newline, window, cx);
27944    });
27945    cx.assert_editor_state(indoc! {"
27946        - [ ] Item 1
27947            - [ ] Item 1.a
27948        - [x] Item 2
27949            - [x] Item 2.a
27950            - [x] Item 2.b
27951            ˇ"
27952    });
27953
27954    // Case 3: Test adding a new nested list item preserves indent
27955    cx.set_state(&indoc! {"
27956        - [ ] Item 1
27957            - [ ] Item 1.a
27958        - [x] Item 2
27959            - [x] Item 2.a
27960            - [x] Item 2.b
27961            ˇ"
27962    });
27963    cx.update_editor(|editor, window, cx| {
27964        editor.handle_input("-", window, cx);
27965    });
27966    cx.run_until_parked();
27967    cx.assert_editor_state(indoc! {"
27968        - [ ] Item 1
27969            - [ ] Item 1.a
27970        - [x] Item 2
27971            - [x] Item 2.a
27972            - [x] Item 2.b
27973"
27974    });
27975    cx.update_editor(|editor, window, cx| {
27976        editor.handle_input(" [x] Item 2.c", window, cx);
27977    });
27978    cx.run_until_parked();
27979    cx.assert_editor_state(indoc! {"
27980        - [ ] Item 1
27981            - [ ] Item 1.a
27982        - [x] Item 2
27983            - [x] Item 2.a
27984            - [x] Item 2.b
27985            - [x] Item 2.cˇ"
27986    });
27987
27988    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
27989    cx.set_state(indoc! {"
27990        1. Item 1
27991            1. Item 1.a
27992        2. Item 2
27993            1. Item 2.a
27994            2. Item 2.bˇ"
27995    });
27996    cx.update_editor(|editor, window, cx| {
27997        editor.newline(&Newline, window, cx);
27998    });
27999    cx.assert_editor_state(indoc! {"
28000        1. Item 1
28001            1. Item 1.a
28002        2. Item 2
28003            1. Item 2.a
28004            2. Item 2.b
28005            ˇ"
28006    });
28007
28008    // Case 5: Adding new ordered list item preserves indent
28009    cx.set_state(indoc! {"
28010        1. Item 1
28011            1. Item 1.a
28012        2. Item 2
28013            1. Item 2.a
28014            2. Item 2.b
28015            ˇ"
28016    });
28017    cx.update_editor(|editor, window, cx| {
28018        editor.handle_input("3", window, cx);
28019    });
28020    cx.run_until_parked();
28021    cx.assert_editor_state(indoc! {"
28022        1. Item 1
28023            1. Item 1.a
28024        2. Item 2
28025            1. Item 2.a
28026            2. Item 2.b
28027"
28028    });
28029    cx.update_editor(|editor, window, cx| {
28030        editor.handle_input(".", window, cx);
28031    });
28032    cx.run_until_parked();
28033    cx.assert_editor_state(indoc! {"
28034        1. Item 1
28035            1. Item 1.a
28036        2. Item 2
28037            1. Item 2.a
28038            2. Item 2.b
28039            3.ˇ"
28040    });
28041    cx.update_editor(|editor, window, cx| {
28042        editor.handle_input(" Item 2.c", window, cx);
28043    });
28044    cx.run_until_parked();
28045    cx.assert_editor_state(indoc! {"
28046        1. Item 1
28047            1. Item 1.a
28048        2. Item 2
28049            1. Item 2.a
28050            2. Item 2.b
28051            3. Item 2.cˇ"
28052    });
28053
28054    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28055    cx.set_state(indoc! {"
28056        - Item 1
28057            - Item 1.a
28058            - Item 1.a
28059        ˇ"});
28060    cx.update_editor(|editor, window, cx| {
28061        editor.handle_input("-", window, cx);
28062    });
28063    cx.run_until_parked();
28064    cx.assert_editor_state(indoc! {"
28065        - Item 1
28066            - Item 1.a
28067            - Item 1.a
28068"});
28069
28070    // Case 7: Test blockquote newline preserves something
28071    cx.set_state(indoc! {"
28072        > Item 1ˇ"
28073    });
28074    cx.update_editor(|editor, window, cx| {
28075        editor.newline(&Newline, window, cx);
28076    });
28077    cx.assert_editor_state(indoc! {"
28078        > Item 1
28079        ˇ"
28080    });
28081}
28082
28083#[gpui::test]
28084async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28085    cx: &mut gpui::TestAppContext,
28086) {
28087    init_test(cx, |_| {});
28088
28089    let url = "https://zed.dev";
28090
28091    let markdown_language = Arc::new(Language::new(
28092        LanguageConfig {
28093            name: "Markdown".into(),
28094            ..LanguageConfig::default()
28095        },
28096        None,
28097    ));
28098
28099    let mut cx = EditorTestContext::new(cx).await;
28100    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28101    cx.set_state(&format!(
28102        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28103    ));
28104
28105    cx.update_editor(|editor, window, cx| {
28106        editor.copy(&Copy, window, cx);
28107    });
28108
28109    cx.set_state(&format!(
28110        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28111    ));
28112
28113    cx.update_editor(|editor, window, cx| {
28114        editor.paste(&Paste, window, cx);
28115    });
28116
28117    cx.assert_editor_state(&format!(
28118        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28119    ));
28120}
28121
28122#[gpui::test]
28123async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28124    cx: &mut gpui::TestAppContext,
28125) {
28126    init_test(cx, |_| {});
28127
28128    let url = "https://zed.dev";
28129
28130    let markdown_language = Arc::new(Language::new(
28131        LanguageConfig {
28132            name: "Markdown".into(),
28133            ..LanguageConfig::default()
28134        },
28135        None,
28136    ));
28137
28138    let mut cx = EditorTestContext::new(cx).await;
28139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28140    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28141
28142    cx.update_editor(|editor, window, cx| {
28143        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28144        editor.paste(&Paste, window, cx);
28145    });
28146
28147    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28148}
28149
28150#[gpui::test]
28151async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28152    cx: &mut gpui::TestAppContext,
28153) {
28154    init_test(cx, |_| {});
28155
28156    let text = "Awesome";
28157
28158    let markdown_language = Arc::new(Language::new(
28159        LanguageConfig {
28160            name: "Markdown".into(),
28161            ..LanguageConfig::default()
28162        },
28163        None,
28164    ));
28165
28166    let mut cx = EditorTestContext::new(cx).await;
28167    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28168    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28169
28170    cx.update_editor(|editor, window, cx| {
28171        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28172        editor.paste(&Paste, window, cx);
28173    });
28174
28175    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28176}
28177
28178#[gpui::test]
28179async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28180    cx: &mut gpui::TestAppContext,
28181) {
28182    init_test(cx, |_| {});
28183
28184    let url = "https://zed.dev";
28185
28186    let markdown_language = Arc::new(Language::new(
28187        LanguageConfig {
28188            name: "Rust".into(),
28189            ..LanguageConfig::default()
28190        },
28191        None,
28192    ));
28193
28194    let mut cx = EditorTestContext::new(cx).await;
28195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28196    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28197
28198    cx.update_editor(|editor, window, cx| {
28199        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28200        editor.paste(&Paste, window, cx);
28201    });
28202
28203    cx.assert_editor_state(&format!(
28204        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28205    ));
28206}
28207
28208#[gpui::test]
28209async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28210    cx: &mut TestAppContext,
28211) {
28212    init_test(cx, |_| {});
28213
28214    let url = "https://zed.dev";
28215
28216    let markdown_language = Arc::new(Language::new(
28217        LanguageConfig {
28218            name: "Markdown".into(),
28219            ..LanguageConfig::default()
28220        },
28221        None,
28222    ));
28223
28224    let (editor, cx) = cx.add_window_view(|window, cx| {
28225        let multi_buffer = MultiBuffer::build_multi(
28226            [
28227                ("this will embed -> link", vec![Point::row_range(0..1)]),
28228                ("this will replace -> link", vec![Point::row_range(0..1)]),
28229            ],
28230            cx,
28231        );
28232        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28234            s.select_ranges(vec![
28235                Point::new(0, 19)..Point::new(0, 23),
28236                Point::new(1, 21)..Point::new(1, 25),
28237            ])
28238        });
28239        let first_buffer_id = multi_buffer
28240            .read(cx)
28241            .excerpt_buffer_ids()
28242            .into_iter()
28243            .next()
28244            .unwrap();
28245        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28246        first_buffer.update(cx, |buffer, cx| {
28247            buffer.set_language(Some(markdown_language.clone()), cx);
28248        });
28249
28250        editor
28251    });
28252    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28253
28254    cx.update_editor(|editor, window, cx| {
28255        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28256        editor.paste(&Paste, window, cx);
28257    });
28258
28259    cx.assert_editor_state(&format!(
28260        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28261    ));
28262}
28263
28264#[gpui::test]
28265async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28266    init_test(cx, |_| {});
28267
28268    let fs = FakeFs::new(cx.executor());
28269    fs.insert_tree(
28270        path!("/project"),
28271        json!({
28272            "first.rs": "# First Document\nSome content here.",
28273            "second.rs": "Plain text content for second file.",
28274        }),
28275    )
28276    .await;
28277
28278    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28279    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28280    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28281
28282    let language = rust_lang();
28283    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28284    language_registry.add(language.clone());
28285    let mut fake_servers = language_registry.register_fake_lsp(
28286        "Rust",
28287        FakeLspAdapter {
28288            ..FakeLspAdapter::default()
28289        },
28290    );
28291
28292    let buffer1 = project
28293        .update(cx, |project, cx| {
28294            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28295        })
28296        .await
28297        .unwrap();
28298    let buffer2 = project
28299        .update(cx, |project, cx| {
28300            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28301        })
28302        .await
28303        .unwrap();
28304
28305    let multi_buffer = cx.new(|cx| {
28306        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28307        multi_buffer.set_excerpts_for_path(
28308            PathKey::for_buffer(&buffer1, cx),
28309            buffer1.clone(),
28310            [Point::zero()..buffer1.read(cx).max_point()],
28311            3,
28312            cx,
28313        );
28314        multi_buffer.set_excerpts_for_path(
28315            PathKey::for_buffer(&buffer2, cx),
28316            buffer2.clone(),
28317            [Point::zero()..buffer1.read(cx).max_point()],
28318            3,
28319            cx,
28320        );
28321        multi_buffer
28322    });
28323
28324    let (editor, cx) = cx.add_window_view(|window, cx| {
28325        Editor::new(
28326            EditorMode::full(),
28327            multi_buffer,
28328            Some(project.clone()),
28329            window,
28330            cx,
28331        )
28332    });
28333
28334    let fake_language_server = fake_servers.next().await.unwrap();
28335
28336    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28337
28338    let save = editor.update_in(cx, |editor, window, cx| {
28339        assert!(editor.is_dirty(cx));
28340
28341        editor.save(
28342            SaveOptions {
28343                format: true,
28344                autosave: true,
28345            },
28346            project,
28347            window,
28348            cx,
28349        )
28350    });
28351    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28352    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28353    let mut done_edit_rx = Some(done_edit_rx);
28354    let mut start_edit_tx = Some(start_edit_tx);
28355
28356    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28357        start_edit_tx.take().unwrap().send(()).unwrap();
28358        let done_edit_rx = done_edit_rx.take().unwrap();
28359        async move {
28360            done_edit_rx.await.unwrap();
28361            Ok(None)
28362        }
28363    });
28364
28365    start_edit_rx.await.unwrap();
28366    buffer2
28367        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28368        .unwrap();
28369
28370    done_edit_tx.send(()).unwrap();
28371
28372    save.await.unwrap();
28373    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28374}
28375
28376#[track_caller]
28377fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28378    editor
28379        .all_inlays(cx)
28380        .into_iter()
28381        .filter_map(|inlay| inlay.get_color())
28382        .map(Rgba::from)
28383        .collect()
28384}
28385
28386#[gpui::test]
28387fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28388    init_test(cx, |_| {});
28389
28390    let editor = cx.add_window(|window, cx| {
28391        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28392        build_editor(buffer, window, cx)
28393    });
28394
28395    editor
28396        .update(cx, |editor, window, cx| {
28397            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28398                s.select_display_ranges([
28399                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28400                ])
28401            });
28402
28403            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28404
28405            assert_eq!(
28406                editor.display_text(cx),
28407                "line1\nline2\nline2",
28408                "Duplicating last line upward should create duplicate above, not on same line"
28409            );
28410
28411            assert_eq!(
28412                editor
28413                    .selections
28414                    .display_ranges(&editor.display_snapshot(cx)),
28415                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28416                "Selection should move to the duplicated line"
28417            );
28418        })
28419        .unwrap();
28420}
28421
28422#[gpui::test]
28423async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28424    init_test(cx, |_| {});
28425
28426    let mut cx = EditorTestContext::new(cx).await;
28427
28428    cx.set_state("line1\nline2ˇ");
28429
28430    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28431
28432    let clipboard_text = cx
28433        .read_from_clipboard()
28434        .and_then(|item| item.text().as_deref().map(str::to_string));
28435
28436    assert_eq!(
28437        clipboard_text,
28438        Some("line2\n".to_string()),
28439        "Copying a line without trailing newline should include a newline"
28440    );
28441
28442    cx.set_state("line1\nˇ");
28443
28444    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28445
28446    cx.assert_editor_state("line1\nline2\nˇ");
28447}
28448
28449#[gpui::test]
28450async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28451    init_test(cx, |_| {});
28452
28453    let mut cx = EditorTestContext::new(cx).await;
28454
28455    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28456
28457    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28458
28459    let clipboard_text = cx
28460        .read_from_clipboard()
28461        .and_then(|item| item.text().as_deref().map(str::to_string));
28462
28463    assert_eq!(
28464        clipboard_text,
28465        Some("line1\nline2\nline3\n".to_string()),
28466        "Copying multiple lines should include a single newline between lines"
28467    );
28468
28469    cx.set_state("lineA\nˇ");
28470
28471    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28472
28473    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28474}
28475
28476#[gpui::test]
28477async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28478    init_test(cx, |_| {});
28479
28480    let mut cx = EditorTestContext::new(cx).await;
28481
28482    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28483
28484    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28485
28486    let clipboard_text = cx
28487        .read_from_clipboard()
28488        .and_then(|item| item.text().as_deref().map(str::to_string));
28489
28490    assert_eq!(
28491        clipboard_text,
28492        Some("line1\nline2\nline3\n".to_string()),
28493        "Copying multiple lines should include a single newline between lines"
28494    );
28495
28496    cx.set_state("lineA\nˇ");
28497
28498    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28499
28500    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28501}
28502
28503#[gpui::test]
28504async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28505    init_test(cx, |_| {});
28506
28507    let mut cx = EditorTestContext::new(cx).await;
28508
28509    cx.set_state("line1\nline2ˇ");
28510    cx.update_editor(|e, window, cx| {
28511        e.set_mode(EditorMode::SingleLine);
28512        assert!(e.key_context(window, cx).contains("end_of_input"));
28513    });
28514    cx.set_state("ˇline1\nline2");
28515    cx.update_editor(|e, window, cx| {
28516        assert!(!e.key_context(window, cx).contains("end_of_input"));
28517    });
28518    cx.set_state("line1ˇ\nline2");
28519    cx.update_editor(|e, window, cx| {
28520        assert!(!e.key_context(window, cx).contains("end_of_input"));
28521    });
28522}
28523
28524#[gpui::test]
28525async fn test_sticky_scroll(cx: &mut TestAppContext) {
28526    init_test(cx, |_| {});
28527    let mut cx = EditorTestContext::new(cx).await;
28528
28529    let buffer = indoc! {"
28530            ˇfn foo() {
28531                let abc = 123;
28532            }
28533            struct Bar;
28534            impl Bar {
28535                fn new() -> Self {
28536                    Self
28537                }
28538            }
28539            fn baz() {
28540            }
28541        "};
28542    cx.set_state(&buffer);
28543
28544    cx.update_editor(|e, _, cx| {
28545        e.buffer()
28546            .read(cx)
28547            .as_singleton()
28548            .unwrap()
28549            .update(cx, |buffer, cx| {
28550                buffer.set_language(Some(rust_lang()), cx);
28551            })
28552    });
28553
28554    let mut sticky_headers = |offset: ScrollOffset| {
28555        cx.update_editor(|e, window, cx| {
28556            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28557            let style = e.style(cx).clone();
28558            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28559                .into_iter()
28560                .map(
28561                    |StickyHeader {
28562                         start_point,
28563                         offset,
28564                         ..
28565                     }| { (start_point, offset) },
28566                )
28567                .collect::<Vec<_>>()
28568        })
28569    };
28570
28571    let fn_foo = Point { row: 0, column: 0 };
28572    let impl_bar = Point { row: 4, column: 0 };
28573    let fn_new = Point { row: 5, column: 4 };
28574
28575    assert_eq!(sticky_headers(0.0), vec![]);
28576    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28577    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28578    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28579    assert_eq!(sticky_headers(2.0), vec![]);
28580    assert_eq!(sticky_headers(2.5), vec![]);
28581    assert_eq!(sticky_headers(3.0), vec![]);
28582    assert_eq!(sticky_headers(3.5), vec![]);
28583    assert_eq!(sticky_headers(4.0), vec![]);
28584    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28585    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28586    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28587    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28588    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28589    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28590    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28591    assert_eq!(sticky_headers(8.0), vec![]);
28592    assert_eq!(sticky_headers(8.5), vec![]);
28593    assert_eq!(sticky_headers(9.0), vec![]);
28594    assert_eq!(sticky_headers(9.5), vec![]);
28595    assert_eq!(sticky_headers(10.0), vec![]);
28596}
28597
28598#[gpui::test]
28599async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28600    init_test(cx, |_| {});
28601    cx.update(|cx| {
28602        SettingsStore::update_global(cx, |store, cx| {
28603            store.update_user_settings(cx, |settings| {
28604                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28605                    enabled: Some(true),
28606                })
28607            });
28608        });
28609    });
28610    let mut cx = EditorTestContext::new(cx).await;
28611
28612    let line_height = cx.update_editor(|editor, window, cx| {
28613        editor
28614            .style(cx)
28615            .text
28616            .line_height_in_pixels(window.rem_size())
28617    });
28618
28619    let buffer = indoc! {"
28620            ˇfn foo() {
28621                let abc = 123;
28622            }
28623            struct Bar;
28624            impl Bar {
28625                fn new() -> Self {
28626                    Self
28627                }
28628            }
28629            fn baz() {
28630            }
28631        "};
28632    cx.set_state(&buffer);
28633
28634    cx.update_editor(|e, _, cx| {
28635        e.buffer()
28636            .read(cx)
28637            .as_singleton()
28638            .unwrap()
28639            .update(cx, |buffer, cx| {
28640                buffer.set_language(Some(rust_lang()), cx);
28641            })
28642    });
28643
28644    let fn_foo = || empty_range(0, 0);
28645    let impl_bar = || empty_range(4, 0);
28646    let fn_new = || empty_range(5, 4);
28647
28648    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28649        cx.update_editor(|e, window, cx| {
28650            e.scroll(
28651                gpui::Point {
28652                    x: 0.,
28653                    y: scroll_offset,
28654                },
28655                None,
28656                window,
28657                cx,
28658            );
28659        });
28660        cx.simulate_click(
28661            gpui::Point {
28662                x: px(0.),
28663                y: click_offset as f32 * line_height,
28664            },
28665            Modifiers::none(),
28666        );
28667        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28668    };
28669
28670    assert_eq!(
28671        scroll_and_click(
28672            4.5, // impl Bar is halfway off the screen
28673            0.0  // click top of screen
28674        ),
28675        // scrolled to impl Bar
28676        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28677    );
28678
28679    assert_eq!(
28680        scroll_and_click(
28681            4.5,  // impl Bar is halfway off the screen
28682            0.25  // click middle of impl Bar
28683        ),
28684        // scrolled to impl Bar
28685        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28686    );
28687
28688    assert_eq!(
28689        scroll_and_click(
28690            4.5, // impl Bar is halfway off the screen
28691            1.5  // click below impl Bar (e.g. fn new())
28692        ),
28693        // scrolled to fn new() - this is below the impl Bar header which has persisted
28694        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28695    );
28696
28697    assert_eq!(
28698        scroll_and_click(
28699            5.5,  // fn new is halfway underneath impl Bar
28700            0.75  // click on the overlap of impl Bar and fn new()
28701        ),
28702        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28703    );
28704
28705    assert_eq!(
28706        scroll_and_click(
28707            5.5,  // fn new is halfway underneath impl Bar
28708            1.25  // click on the visible part of fn new()
28709        ),
28710        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28711    );
28712
28713    assert_eq!(
28714        scroll_and_click(
28715            1.5, // fn foo is halfway off the screen
28716            0.0  // click top of screen
28717        ),
28718        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28719    );
28720
28721    assert_eq!(
28722        scroll_and_click(
28723            1.5,  // fn foo is halfway off the screen
28724            0.75  // click visible part of let abc...
28725        )
28726        .0,
28727        // no change in scroll
28728        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28729        (gpui::Point { x: 0., y: 1.5 })
28730    );
28731}
28732
28733#[gpui::test]
28734async fn test_next_prev_reference(cx: &mut TestAppContext) {
28735    const CYCLE_POSITIONS: &[&'static str] = &[
28736        indoc! {"
28737            fn foo() {
28738                let ˇabc = 123;
28739                let x = abc + 1;
28740                let y = abc + 2;
28741                let z = abc + 2;
28742            }
28743        "},
28744        indoc! {"
28745            fn foo() {
28746                let abc = 123;
28747                let x = ˇabc + 1;
28748                let y = abc + 2;
28749                let z = abc + 2;
28750            }
28751        "},
28752        indoc! {"
28753            fn foo() {
28754                let abc = 123;
28755                let x = abc + 1;
28756                let y = ˇabc + 2;
28757                let z = abc + 2;
28758            }
28759        "},
28760        indoc! {"
28761            fn foo() {
28762                let abc = 123;
28763                let x = abc + 1;
28764                let y = abc + 2;
28765                let z = ˇabc + 2;
28766            }
28767        "},
28768    ];
28769
28770    init_test(cx, |_| {});
28771
28772    let mut cx = EditorLspTestContext::new_rust(
28773        lsp::ServerCapabilities {
28774            references_provider: Some(lsp::OneOf::Left(true)),
28775            ..Default::default()
28776        },
28777        cx,
28778    )
28779    .await;
28780
28781    // importantly, the cursor is in the middle
28782    cx.set_state(indoc! {"
28783        fn foo() {
28784            let aˇbc = 123;
28785            let x = abc + 1;
28786            let y = abc + 2;
28787            let z = abc + 2;
28788        }
28789    "});
28790
28791    let reference_ranges = [
28792        lsp::Position::new(1, 8),
28793        lsp::Position::new(2, 12),
28794        lsp::Position::new(3, 12),
28795        lsp::Position::new(4, 12),
28796    ]
28797    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28798
28799    cx.lsp
28800        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28801            Ok(Some(
28802                reference_ranges
28803                    .map(|range| lsp::Location {
28804                        uri: params.text_document_position.text_document.uri.clone(),
28805                        range,
28806                    })
28807                    .to_vec(),
28808            ))
28809        });
28810
28811    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28812        cx.update_editor(|editor, window, cx| {
28813            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28814        })
28815        .unwrap()
28816        .await
28817        .unwrap()
28818    };
28819
28820    _move(Direction::Next, 1, &mut cx).await;
28821    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28822
28823    _move(Direction::Next, 1, &mut cx).await;
28824    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28825
28826    _move(Direction::Next, 1, &mut cx).await;
28827    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28828
28829    // loops back to the start
28830    _move(Direction::Next, 1, &mut cx).await;
28831    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28832
28833    // loops back to the end
28834    _move(Direction::Prev, 1, &mut cx).await;
28835    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28836
28837    _move(Direction::Prev, 1, &mut cx).await;
28838    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28839
28840    _move(Direction::Prev, 1, &mut cx).await;
28841    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28842
28843    _move(Direction::Prev, 1, &mut cx).await;
28844    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28845
28846    _move(Direction::Next, 3, &mut cx).await;
28847    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28848
28849    _move(Direction::Prev, 2, &mut cx).await;
28850    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28851}
28852
28853#[gpui::test]
28854async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28855    init_test(cx, |_| {});
28856
28857    let (editor, cx) = cx.add_window_view(|window, cx| {
28858        let multi_buffer = MultiBuffer::build_multi(
28859            [
28860                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28861                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28862            ],
28863            cx,
28864        );
28865        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28866    });
28867
28868    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28869    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28870
28871    cx.assert_excerpts_with_selections(indoc! {"
28872        [EXCERPT]
28873        ˇ1
28874        2
28875        3
28876        [EXCERPT]
28877        1
28878        2
28879        3
28880        "});
28881
28882    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28883    cx.update_editor(|editor, window, cx| {
28884        editor.change_selections(None.into(), window, cx, |s| {
28885            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28886        });
28887    });
28888    cx.assert_excerpts_with_selections(indoc! {"
28889        [EXCERPT]
28890        1
2889128892        3
28893        [EXCERPT]
28894        1
28895        2
28896        3
28897        "});
28898
28899    cx.update_editor(|editor, window, cx| {
28900        editor
28901            .select_all_matches(&SelectAllMatches, window, cx)
28902            .unwrap();
28903    });
28904    cx.assert_excerpts_with_selections(indoc! {"
28905        [EXCERPT]
28906        1
2890728908        3
28909        [EXCERPT]
28910        1
2891128912        3
28913        "});
28914
28915    cx.update_editor(|editor, window, cx| {
28916        editor.handle_input("X", window, cx);
28917    });
28918    cx.assert_excerpts_with_selections(indoc! {"
28919        [EXCERPT]
28920        1
2892128922        3
28923        [EXCERPT]
28924        1
2892528926        3
28927        "});
28928
28929    // Scenario 2: Select "2", then fold second buffer before insertion
28930    cx.update_multibuffer(|mb, cx| {
28931        for buffer_id in buffer_ids.iter() {
28932            let buffer = mb.buffer(*buffer_id).unwrap();
28933            buffer.update(cx, |buffer, cx| {
28934                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28935            });
28936        }
28937    });
28938
28939    // Select "2" and select all matches
28940    cx.update_editor(|editor, window, cx| {
28941        editor.change_selections(None.into(), window, cx, |s| {
28942            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28943        });
28944        editor
28945            .select_all_matches(&SelectAllMatches, window, cx)
28946            .unwrap();
28947    });
28948
28949    // Fold second buffer - should remove selections from folded buffer
28950    cx.update_editor(|editor, _, cx| {
28951        editor.fold_buffer(buffer_ids[1], cx);
28952    });
28953    cx.assert_excerpts_with_selections(indoc! {"
28954        [EXCERPT]
28955        1
2895628957        3
28958        [EXCERPT]
28959        [FOLDED]
28960        "});
28961
28962    // Insert text - should only affect first buffer
28963    cx.update_editor(|editor, window, cx| {
28964        editor.handle_input("Y", window, cx);
28965    });
28966    cx.update_editor(|editor, _, cx| {
28967        editor.unfold_buffer(buffer_ids[1], cx);
28968    });
28969    cx.assert_excerpts_with_selections(indoc! {"
28970        [EXCERPT]
28971        1
2897228973        3
28974        [EXCERPT]
28975        1
28976        2
28977        3
28978        "});
28979
28980    // Scenario 3: Select "2", then fold first buffer before insertion
28981    cx.update_multibuffer(|mb, cx| {
28982        for buffer_id in buffer_ids.iter() {
28983            let buffer = mb.buffer(*buffer_id).unwrap();
28984            buffer.update(cx, |buffer, cx| {
28985                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28986            });
28987        }
28988    });
28989
28990    // Select "2" and select all matches
28991    cx.update_editor(|editor, window, cx| {
28992        editor.change_selections(None.into(), window, cx, |s| {
28993            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28994        });
28995        editor
28996            .select_all_matches(&SelectAllMatches, window, cx)
28997            .unwrap();
28998    });
28999
29000    // Fold first buffer - should remove selections from folded buffer
29001    cx.update_editor(|editor, _, cx| {
29002        editor.fold_buffer(buffer_ids[0], cx);
29003    });
29004    cx.assert_excerpts_with_selections(indoc! {"
29005        [EXCERPT]
29006        [FOLDED]
29007        [EXCERPT]
29008        1
2900929010        3
29011        "});
29012
29013    // Insert text - should only affect second buffer
29014    cx.update_editor(|editor, window, cx| {
29015        editor.handle_input("Z", window, cx);
29016    });
29017    cx.update_editor(|editor, _, cx| {
29018        editor.unfold_buffer(buffer_ids[0], cx);
29019    });
29020    cx.assert_excerpts_with_selections(indoc! {"
29021        [EXCERPT]
29022        1
29023        2
29024        3
29025        [EXCERPT]
29026        1
2902729028        3
29029        "});
29030
29031    // Test correct folded header is selected upon fold
29032    cx.update_editor(|editor, _, cx| {
29033        editor.fold_buffer(buffer_ids[0], cx);
29034        editor.fold_buffer(buffer_ids[1], cx);
29035    });
29036    cx.assert_excerpts_with_selections(indoc! {"
29037        [EXCERPT]
29038        [FOLDED]
29039        [EXCERPT]
29040        ˇ[FOLDED]
29041        "});
29042
29043    // Test selection inside folded buffer unfolds it on type
29044    cx.update_editor(|editor, window, cx| {
29045        editor.handle_input("W", window, cx);
29046    });
29047    cx.update_editor(|editor, _, cx| {
29048        editor.unfold_buffer(buffer_ids[0], cx);
29049    });
29050    cx.assert_excerpts_with_selections(indoc! {"
29051        [EXCERPT]
29052        1
29053        2
29054        3
29055        [EXCERPT]
29056        Wˇ1
29057        Z
29058        3
29059        "});
29060}
29061
29062#[gpui::test]
29063async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
29064    init_test(cx, |_| {});
29065    let mut leader_cx = EditorTestContext::new(cx).await;
29066
29067    let diff_base = indoc!(
29068        r#"
29069        one
29070        two
29071        three
29072        four
29073        five
29074        six
29075        "#
29076    );
29077
29078    let initial_state = indoc!(
29079        r#"
29080        ˇone
29081        two
29082        THREE
29083        four
29084        five
29085        six
29086        "#
29087    );
29088
29089    leader_cx.set_state(initial_state);
29090
29091    leader_cx.set_head_text(&diff_base);
29092    leader_cx.run_until_parked();
29093
29094    let follower = leader_cx.update_multibuffer(|leader, cx| {
29095        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29096        leader.set_all_diff_hunks_expanded(cx);
29097        leader.get_or_create_follower(cx)
29098    });
29099    follower.update(cx, |follower, cx| {
29100        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29101        follower.set_all_diff_hunks_expanded(cx);
29102    });
29103
29104    let follower_editor =
29105        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
29106    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
29107
29108    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
29109    cx.run_until_parked();
29110
29111    leader_cx.assert_editor_state(initial_state);
29112    follower_cx.assert_editor_state(indoc! {
29113        r#"
29114        ˇone
29115        two
29116        three
29117        four
29118        five
29119        six
29120        "#
29121    });
29122
29123    follower_cx.editor(|editor, _window, cx| {
29124        assert!(editor.read_only(cx));
29125    });
29126
29127    leader_cx.update_editor(|editor, _window, cx| {
29128        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
29129    });
29130    cx.run_until_parked();
29131
29132    leader_cx.assert_editor_state(indoc! {
29133        r#"
29134        ˇone
29135        two
29136        THREE
29137        four
29138        FIVE
29139        six
29140        "#
29141    });
29142
29143    follower_cx.assert_editor_state(indoc! {
29144        r#"
29145        ˇone
29146        two
29147        three
29148        four
29149        five
29150        six
29151        "#
29152    });
29153
29154    leader_cx.update_editor(|editor, _window, cx| {
29155        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
29156    });
29157    cx.run_until_parked();
29158
29159    leader_cx.assert_editor_state(indoc! {
29160        r#"
29161        ˇone
29162        two
29163        THREE
29164        four
29165        FIVE
29166        six
29167        SEVEN"#
29168    });
29169
29170    follower_cx.assert_editor_state(indoc! {
29171        r#"
29172        ˇone
29173        two
29174        three
29175        four
29176        five
29177        six
29178        "#
29179    });
29180
29181    leader_cx.update_editor(|editor, window, cx| {
29182        editor.move_down(&MoveDown, window, cx);
29183        editor.refresh_selected_text_highlights(true, window, cx);
29184    });
29185    leader_cx.run_until_parked();
29186}
29187
29188#[gpui::test]
29189async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
29190    init_test(cx, |_| {});
29191    let base_text = "base\n";
29192    let buffer_text = "buffer\n";
29193
29194    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
29195    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
29196
29197    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
29198    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
29199    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
29200    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
29201
29202    let leader = cx.new(|cx| {
29203        let mut leader = MultiBuffer::new(Capability::ReadWrite);
29204        leader.set_all_diff_hunks_expanded(cx);
29205        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29206        leader
29207    });
29208    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
29209    follower.update(cx, |follower, _| {
29210        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29211    });
29212
29213    leader.update(cx, |leader, cx| {
29214        leader.insert_excerpts_after(
29215            ExcerptId::min(),
29216            extra_buffer_2.clone(),
29217            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29218            cx,
29219        );
29220        leader.add_diff(extra_diff_2.clone(), cx);
29221
29222        leader.insert_excerpts_after(
29223            ExcerptId::min(),
29224            extra_buffer_1.clone(),
29225            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29226            cx,
29227        );
29228        leader.add_diff(extra_diff_1.clone(), cx);
29229
29230        leader.insert_excerpts_after(
29231            ExcerptId::min(),
29232            buffer1.clone(),
29233            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29234            cx,
29235        );
29236        leader.add_diff(diff1.clone(), cx);
29237    });
29238
29239    cx.run_until_parked();
29240    let mut cx = cx.add_empty_window();
29241
29242    let leader_editor = cx
29243        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29244    let follower_editor = cx.new_window_entity(|window, cx| {
29245        Editor::for_multibuffer(follower.clone(), None, window, cx)
29246    });
29247
29248    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29249    leader_cx.assert_editor_state(indoc! {"
29250       ˇbuffer
29251
29252       dummy text 1
29253
29254       dummy text 2
29255    "});
29256    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29257    follower_cx.assert_editor_state(indoc! {"
29258        ˇbase
29259
29260
29261    "});
29262}
29263
29264#[gpui::test]
29265async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29266    init_test(cx, |_| {});
29267
29268    let (editor, cx) = cx.add_window_view(|window, cx| {
29269        let multi_buffer = MultiBuffer::build_multi(
29270            [
29271                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29272                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29273            ],
29274            cx,
29275        );
29276        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29277    });
29278
29279    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29280
29281    cx.assert_excerpts_with_selections(indoc! {"
29282        [EXCERPT]
29283        ˇ1
29284        2
29285        3
29286        [EXCERPT]
29287        1
29288        2
29289        3
29290        4
29291        5
29292        6
29293        7
29294        8
29295        9
29296        "});
29297
29298    cx.update_editor(|editor, window, cx| {
29299        editor.change_selections(None.into(), window, cx, |s| {
29300            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29301        });
29302    });
29303
29304    cx.assert_excerpts_with_selections(indoc! {"
29305        [EXCERPT]
29306        1
29307        2
29308        3
29309        [EXCERPT]
29310        1
29311        2
29312        3
29313        4
29314        5
29315        6
29316        ˇ7
29317        8
29318        9
29319        "});
29320
29321    cx.update_editor(|editor, _window, cx| {
29322        editor.set_vertical_scroll_margin(0, cx);
29323    });
29324
29325    cx.update_editor(|editor, window, cx| {
29326        assert_eq!(editor.vertical_scroll_margin(), 0);
29327        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29328        assert_eq!(
29329            editor.snapshot(window, cx).scroll_position(),
29330            gpui::Point::new(0., 12.0)
29331        );
29332    });
29333
29334    cx.update_editor(|editor, _window, cx| {
29335        editor.set_vertical_scroll_margin(3, cx);
29336    });
29337
29338    cx.update_editor(|editor, window, cx| {
29339        assert_eq!(editor.vertical_scroll_margin(), 3);
29340        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29341        assert_eq!(
29342            editor.snapshot(window, cx).scroll_position(),
29343            gpui::Point::new(0., 9.0)
29344        );
29345    });
29346}
29347
29348#[gpui::test]
29349async fn test_find_references_single_case(cx: &mut TestAppContext) {
29350    init_test(cx, |_| {});
29351    let mut cx = EditorLspTestContext::new_rust(
29352        lsp::ServerCapabilities {
29353            references_provider: Some(lsp::OneOf::Left(true)),
29354            ..lsp::ServerCapabilities::default()
29355        },
29356        cx,
29357    )
29358    .await;
29359
29360    let before = indoc!(
29361        r#"
29362        fn main() {
29363            let aˇbc = 123;
29364            let xyz = abc;
29365        }
29366        "#
29367    );
29368    let after = indoc!(
29369        r#"
29370        fn main() {
29371            let abc = 123;
29372            let xyz = ˇabc;
29373        }
29374        "#
29375    );
29376
29377    cx.lsp
29378        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29379            Ok(Some(vec![
29380                lsp::Location {
29381                    uri: params.text_document_position.text_document.uri.clone(),
29382                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29383                },
29384                lsp::Location {
29385                    uri: params.text_document_position.text_document.uri,
29386                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29387                },
29388            ]))
29389        });
29390
29391    cx.set_state(before);
29392
29393    let action = FindAllReferences {
29394        always_open_multibuffer: false,
29395    };
29396
29397    let navigated = cx
29398        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29399        .expect("should have spawned a task")
29400        .await
29401        .unwrap();
29402
29403    assert_eq!(navigated, Navigated::No);
29404
29405    cx.run_until_parked();
29406
29407    cx.assert_editor_state(after);
29408}
29409
29410#[gpui::test]
29411async fn test_local_worktree_trust(cx: &mut TestAppContext) {
29412    init_test(cx, |_| {});
29413    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
29414
29415    cx.update(|cx| {
29416        SettingsStore::update_global(cx, |store, cx| {
29417            store.update_user_settings(cx, |settings| {
29418                settings.project.all_languages.defaults.inlay_hints =
29419                    Some(InlayHintSettingsContent {
29420                        enabled: Some(true),
29421                        ..InlayHintSettingsContent::default()
29422                    });
29423            });
29424        });
29425    });
29426
29427    let fs = FakeFs::new(cx.executor());
29428    fs.insert_tree(
29429        path!("/project"),
29430        json!({
29431            ".zed": {
29432                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
29433            },
29434            "main.rs": "fn main() {}"
29435        }),
29436    )
29437    .await;
29438
29439    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
29440    let server_name = "override-rust-analyzer";
29441    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
29442
29443    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29444    language_registry.add(rust_lang());
29445
29446    let capabilities = lsp::ServerCapabilities {
29447        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29448        ..lsp::ServerCapabilities::default()
29449    };
29450    let mut fake_language_servers = language_registry.register_fake_lsp(
29451        "Rust",
29452        FakeLspAdapter {
29453            name: server_name,
29454            capabilities,
29455            initializer: Some(Box::new({
29456                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29457                move |fake_server| {
29458                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29459                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29460                        move |_params, _| {
29461                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
29462                            async move {
29463                                Ok(Some(vec![lsp::InlayHint {
29464                                    position: lsp::Position::new(0, 0),
29465                                    label: lsp::InlayHintLabel::String("hint".to_string()),
29466                                    kind: None,
29467                                    text_edits: None,
29468                                    tooltip: None,
29469                                    padding_left: None,
29470                                    padding_right: None,
29471                                    data: None,
29472                                }]))
29473                            }
29474                        },
29475                    );
29476                }
29477            })),
29478            ..FakeLspAdapter::default()
29479        },
29480    );
29481
29482    cx.run_until_parked();
29483
29484    let worktree_id = project.read_with(cx, |project, cx| {
29485        project
29486            .worktrees(cx)
29487            .next()
29488            .map(|wt| wt.read(cx).id())
29489            .expect("should have a worktree")
29490    });
29491
29492    let trusted_worktrees =
29493        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
29494
29495    let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29496    assert!(!can_trust, "worktree should be restricted initially");
29497
29498    let buffer_before_approval = project
29499        .update(cx, |project, cx| {
29500            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
29501        })
29502        .await
29503        .unwrap();
29504
29505    let (editor, cx) = cx.add_window_view(|window, cx| {
29506        Editor::new(
29507            EditorMode::full(),
29508            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
29509            Some(project.clone()),
29510            window,
29511            cx,
29512        )
29513    });
29514    cx.run_until_parked();
29515    let fake_language_server = fake_language_servers.next();
29516
29517    cx.read(|cx| {
29518        let file = buffer_before_approval.read(cx).file();
29519        assert_eq!(
29520            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29521                .language_servers,
29522            ["...".to_string()],
29523            "local .zed/settings.json must not apply before trust approval"
29524        )
29525    });
29526
29527    editor.update_in(cx, |editor, window, cx| {
29528        editor.handle_input("1", window, cx);
29529    });
29530    cx.run_until_parked();
29531    cx.executor()
29532        .advance_clock(std::time::Duration::from_secs(1));
29533    assert_eq!(
29534        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
29535        0,
29536        "inlay hints must not be queried before trust approval"
29537    );
29538
29539    trusted_worktrees.update(cx, |store, cx| {
29540        store.trust(
29541            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
29542            None,
29543            cx,
29544        );
29545    });
29546    cx.run_until_parked();
29547
29548    cx.read(|cx| {
29549        let file = buffer_before_approval.read(cx).file();
29550        assert_eq!(
29551            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29552                .language_servers,
29553            ["override-rust-analyzer".to_string()],
29554            "local .zed/settings.json should apply after trust approval"
29555        )
29556    });
29557    let _fake_language_server = fake_language_server.await.unwrap();
29558    editor.update_in(cx, |editor, window, cx| {
29559        editor.handle_input("1", window, cx);
29560    });
29561    cx.run_until_parked();
29562    cx.executor()
29563        .advance_clock(std::time::Duration::from_secs(1));
29564    assert!(
29565        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
29566        "inlay hints should be queried after trust approval"
29567    );
29568
29569    let can_trust_after =
29570        trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29571    assert!(can_trust_after, "worktree should be trusted after trust()");
29572}
29573
29574#[gpui::test]
29575fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
29576    // This test reproduces a bug where drawing an editor at a position above the viewport
29577    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
29578    // causes an infinite loop in blocks_in_range.
29579    //
29580    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
29581    // the content mask intersection produces visible_bounds with origin at the viewport top.
29582    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
29583    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
29584    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
29585    init_test(cx, |_| {});
29586
29587    let window = cx.add_window(|_, _| gpui::Empty);
29588    let mut cx = VisualTestContext::from_window(*window, cx);
29589
29590    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
29591    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
29592
29593    // Simulate a small viewport (500x500 pixels at origin 0,0)
29594    cx.simulate_resize(gpui::size(px(500.), px(500.)));
29595
29596    // Draw the editor at a very negative Y position, simulating an editor that's been
29597    // scrolled way above the visible viewport (like in a List that has scrolled past it).
29598    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
29599    // This should NOT hang - it should just render nothing.
29600    cx.draw(
29601        gpui::point(px(0.), px(-10000.)),
29602        gpui::size(px(500.), px(3000.)),
29603        |_, _| editor.clone(),
29604    );
29605
29606    // If we get here without hanging, the test passes
29607}