editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  859    let pane = workspace
  860        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  861        .unwrap();
  862
  863    _ = workspace.update(cx, |_v, window, cx| {
  864        cx.new(|cx| {
  865            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  866            let mut editor = build_editor(buffer, window, cx);
  867            let handle = cx.entity();
  868            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  869
  870            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  871                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  872            }
  873
  874            // Move the cursor a small distance.
  875            // Nothing is added to the navigation history.
  876            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  877                s.select_display_ranges([
  878                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  879                ])
  880            });
  881            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  882                s.select_display_ranges([
  883                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  884                ])
  885            });
  886            assert!(pop_history(&mut editor, cx).is_none());
  887
  888            // Move the cursor a large distance.
  889            // The history can jump back to the previous position.
  890            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  891                s.select_display_ranges([
  892                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  893                ])
  894            });
  895            let nav_entry = pop_history(&mut editor, cx).unwrap();
  896            editor.navigate(nav_entry.data.unwrap(), window, cx);
  897            assert_eq!(nav_entry.item.id(), cx.entity_id());
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  903            );
  904            assert!(pop_history(&mut editor, cx).is_none());
  905
  906            // Move the cursor a small distance via the mouse.
  907            // Nothing is added to the navigation history.
  908            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  909            editor.end_selection(window, cx);
  910            assert_eq!(
  911                editor
  912                    .selections
  913                    .display_ranges(&editor.display_snapshot(cx)),
  914                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  915            );
  916            assert!(pop_history(&mut editor, cx).is_none());
  917
  918            // Move the cursor a large distance via the mouse.
  919            // The history can jump back to the previous position.
  920            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  921            editor.end_selection(window, cx);
  922            assert_eq!(
  923                editor
  924                    .selections
  925                    .display_ranges(&editor.display_snapshot(cx)),
  926                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  927            );
  928            let nav_entry = pop_history(&mut editor, cx).unwrap();
  929            editor.navigate(nav_entry.data.unwrap(), window, cx);
  930            assert_eq!(nav_entry.item.id(), cx.entity_id());
  931            assert_eq!(
  932                editor
  933                    .selections
  934                    .display_ranges(&editor.display_snapshot(cx)),
  935                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  936            );
  937            assert!(pop_history(&mut editor, cx).is_none());
  938
  939            // Set scroll position to check later
  940            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  941            let original_scroll_position = editor.scroll_manager.anchor();
  942
  943            // Jump to the end of the document and adjust scroll
  944            editor.move_to_end(&MoveToEnd, window, cx);
  945            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  946            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  947
  948            let nav_entry = pop_history(&mut editor, cx).unwrap();
  949            editor.navigate(nav_entry.data.unwrap(), window, cx);
  950            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  951
  952            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  953            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  954            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  955            let invalid_point = Point::new(9999, 0);
  956            editor.navigate(
  957                Arc::new(NavigationData {
  958                    cursor_anchor: invalid_anchor,
  959                    cursor_position: invalid_point,
  960                    scroll_anchor: ScrollAnchor {
  961                        anchor: invalid_anchor,
  962                        offset: Default::default(),
  963                    },
  964                    scroll_top_row: invalid_point.row,
  965                }),
  966                window,
  967                cx,
  968            );
  969            assert_eq!(
  970                editor
  971                    .selections
  972                    .display_ranges(&editor.display_snapshot(cx)),
  973                &[editor.max_point(cx)..editor.max_point(cx)]
  974            );
  975            assert_eq!(
  976                editor.scroll_position(cx),
  977                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  978            );
  979
  980            editor
  981        })
  982    });
  983}
  984
  985#[gpui::test]
  986fn test_cancel(cx: &mut TestAppContext) {
  987    init_test(cx, |_| {});
  988
  989    let editor = cx.add_window(|window, cx| {
  990        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  991        build_editor(buffer, window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  996        editor.update_selection(
  997            DisplayPoint::new(DisplayRow(1), 1),
  998            0,
  999            gpui::Point::<f32>::default(),
 1000            window,
 1001            cx,
 1002        );
 1003        editor.end_selection(window, cx);
 1004
 1005        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1006        editor.update_selection(
 1007            DisplayPoint::new(DisplayRow(0), 3),
 1008            0,
 1009            gpui::Point::<f32>::default(),
 1010            window,
 1011            cx,
 1012        );
 1013        editor.end_selection(window, cx);
 1014        assert_eq!(
 1015            display_ranges(editor, cx),
 1016            [
 1017                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1018                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1019            ]
 1020        );
 1021    });
 1022
 1023    _ = editor.update(cx, |editor, window, cx| {
 1024        editor.cancel(&Cancel, window, cx);
 1025        assert_eq!(
 1026            display_ranges(editor, cx),
 1027            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1028        );
 1029    });
 1030
 1031    _ = editor.update(cx, |editor, window, cx| {
 1032        editor.cancel(&Cancel, window, cx);
 1033        assert_eq!(
 1034            display_ranges(editor, cx),
 1035            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                impl Foo {
 1048                    // Hello!
 1049
 1050                    fn a() {
 1051                        1
 1052                    }
 1053
 1054                    fn b() {
 1055                        2
 1056                    }
 1057
 1058                    fn c() {
 1059                        3
 1060                    }
 1061                }
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer, window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                impl Foo {
 1080                    // Hello!
 1081
 1082                    fn a() {
 1083                        1
 1084                    }
 1085
 1086                    fn b() {⋯
 1087                    }
 1088
 1089                    fn c() {⋯
 1090                    }
 1091                }
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                impl Foo {⋯
 1101                }
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                impl Foo {
 1111                    // Hello!
 1112
 1113                    fn a() {
 1114                        1
 1115                    }
 1116
 1117                    fn b() {⋯
 1118                    }
 1119
 1120                    fn c() {⋯
 1121                    }
 1122                }
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151                    def c():
 1152                        print(3)
 1153            "
 1154            .unindent(),
 1155            cx,
 1156        );
 1157        build_editor(buffer, window, cx)
 1158    });
 1159
 1160    _ = editor.update(cx, |editor, window, cx| {
 1161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1162            s.select_display_ranges([
 1163                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1164            ]);
 1165        });
 1166        editor.fold(&Fold, window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():⋯
 1177
 1178                    def c():⋯
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:⋯
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.unfold_lines(&UnfoldLines, window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:
 1197                    # Hello!
 1198
 1199                    def a():
 1200                        print(1)
 1201
 1202                    def b():⋯
 1203
 1204                    def c():⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            editor.buffer.read(cx).read(cx).text()
 1213        );
 1214    });
 1215}
 1216
 1217#[gpui::test]
 1218fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1219    init_test(cx, |_| {});
 1220
 1221    let editor = cx.add_window(|window, cx| {
 1222        let buffer = MultiBuffer::build_simple(
 1223            &"
 1224                class Foo:
 1225                    # Hello!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234                    def c():
 1235                        print(3)
 1236
 1237
 1238            "
 1239            .unindent(),
 1240            cx,
 1241        );
 1242        build_editor(buffer, window, cx)
 1243    });
 1244
 1245    _ = editor.update(cx, |editor, window, cx| {
 1246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1247            s.select_display_ranges([
 1248                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1249            ]);
 1250        });
 1251        editor.fold(&Fold, window, cx);
 1252        assert_eq!(
 1253            editor.display_text(cx),
 1254            "
 1255                class Foo:
 1256                    # Hello!
 1257
 1258                    def a():
 1259                        print(1)
 1260
 1261                    def b():⋯
 1262
 1263
 1264                    def c():⋯
 1265
 1266
 1267            "
 1268            .unindent(),
 1269        );
 1270
 1271        editor.fold(&Fold, window, cx);
 1272        assert_eq!(
 1273            editor.display_text(cx),
 1274            "
 1275                class Foo:⋯
 1276
 1277
 1278            "
 1279            .unindent(),
 1280        );
 1281
 1282        editor.unfold_lines(&UnfoldLines, window, cx);
 1283        assert_eq!(
 1284            editor.display_text(cx),
 1285            "
 1286                class Foo:
 1287                    # Hello!
 1288
 1289                    def a():
 1290                        print(1)
 1291
 1292                    def b():⋯
 1293
 1294
 1295                    def c():⋯
 1296
 1297
 1298            "
 1299            .unindent(),
 1300        );
 1301
 1302        editor.unfold_lines(&UnfoldLines, window, cx);
 1303        assert_eq!(
 1304            editor.display_text(cx),
 1305            editor.buffer.read(cx).read(cx).text()
 1306        );
 1307    });
 1308}
 1309
 1310#[gpui::test]
 1311fn test_fold_at_level(cx: &mut TestAppContext) {
 1312    init_test(cx, |_| {});
 1313
 1314    let editor = cx.add_window(|window, cx| {
 1315        let buffer = MultiBuffer::build_simple(
 1316            &"
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339            cx,
 1340        );
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:
 1350                    # Hello!
 1351
 1352                    def a():⋯
 1353
 1354                    def b():⋯
 1355
 1356
 1357                class Bar:
 1358                    # World!
 1359
 1360                    def a():⋯
 1361
 1362                    def b():⋯
 1363
 1364
 1365            "
 1366            .unindent(),
 1367        );
 1368
 1369        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1370        assert_eq!(
 1371            editor.display_text(cx),
 1372            "
 1373                class Foo:⋯
 1374
 1375
 1376                class Bar:⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.unfold_all(&UnfoldAll, window, cx);
 1384        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1385        assert_eq!(
 1386            editor.display_text(cx),
 1387            "
 1388                class Foo:
 1389                    # Hello!
 1390
 1391                    def a():
 1392                        print(1)
 1393
 1394                    def b():
 1395                        print(2)
 1396
 1397
 1398                class Bar:
 1399                    # World!
 1400
 1401                    def a():
 1402                        print(1)
 1403
 1404                    def b():
 1405                        print(2)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410        );
 1411
 1412        assert_eq!(
 1413            editor.display_text(cx),
 1414            editor.buffer.read(cx).read(cx).text()
 1415        );
 1416        let (_, positions) = marked_text_ranges(
 1417            &"
 1418                       class Foo:
 1419                           # Hello!
 1420
 1421                           def a():
 1422                              print(1)
 1423
 1424                           def b():
 1425                               p«riˇ»nt(2)
 1426
 1427
 1428                       class Bar:
 1429                           # World!
 1430
 1431                           def a():
 1432                               «ˇprint(1)
 1433
 1434                           def b():
 1435                               print(2)»
 1436
 1437
 1438                   "
 1439            .unindent(),
 1440            true,
 1441        );
 1442
 1443        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1444            s.select_ranges(
 1445                positions
 1446                    .iter()
 1447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1448            )
 1449        });
 1450
 1451        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1452        assert_eq!(
 1453            editor.display_text(cx),
 1454            "
 1455                class Foo:
 1456                    # Hello!
 1457
 1458                    def a():⋯
 1459
 1460                    def b():
 1461                        print(2)
 1462
 1463
 1464                class Bar:
 1465                    # World!
 1466
 1467                    def a():
 1468                        print(1)
 1469
 1470                    def b():
 1471                        print(2)
 1472
 1473
 1474            "
 1475            .unindent(),
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481fn test_move_cursor(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1485    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1486
 1487    buffer.update(cx, |buffer, cx| {
 1488        buffer.edit(
 1489            vec![
 1490                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1491                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1492            ],
 1493            None,
 1494            cx,
 1495        );
 1496    });
 1497    _ = editor.update(cx, |editor, window, cx| {
 1498        assert_eq!(
 1499            display_ranges(editor, cx),
 1500            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            display_ranges(editor, cx),
 1506            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1507        );
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            display_ranges(editor, cx),
 1512            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1513        );
 1514
 1515        editor.move_left(&MoveLeft, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1525        );
 1526
 1527        editor.move_to_end(&MoveToEnd, window, cx);
 1528        assert_eq!(
 1529            display_ranges(editor, cx),
 1530            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1531        );
 1532
 1533        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1534        assert_eq!(
 1535            display_ranges(editor, cx),
 1536            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1537        );
 1538
 1539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1542            ]);
 1543        });
 1544        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1548        );
 1549
 1550        editor.select_to_end(&SelectToEnd, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1554        );
 1555    });
 1556}
 1557
 1558#[gpui::test]
 1559fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1560    init_test(cx, |_| {});
 1561
 1562    let editor = cx.add_window(|window, cx| {
 1563        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1564        build_editor(buffer, window, cx)
 1565    });
 1566
 1567    assert_eq!('🟥'.len_utf8(), 4);
 1568    assert_eq!('α'.len_utf8(), 2);
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.fold_creases(
 1572            vec![
 1573                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1576            ],
 1577            true,
 1578            window,
 1579            cx,
 1580        );
 1581        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1582
 1583        editor.move_right(&MoveRight, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1585        editor.move_right(&MoveRight, window, cx);
 1586        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1587        editor.move_right(&MoveRight, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1589
 1590        editor.move_down(&MoveDown, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1594        editor.move_left(&MoveLeft, window, cx);
 1595        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1596        editor.move_left(&MoveLeft, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1598
 1599        editor.move_down(&MoveDown, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1605        editor.move_right(&MoveRight, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1610        editor.move_down(&MoveDown, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1614
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1617        editor.move_left(&MoveLeft, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1621    });
 1622}
 1623
 1624#[gpui::test]
 1625fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1626    init_test(cx, |_| {});
 1627
 1628    let editor = cx.add_window(|window, cx| {
 1629        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1630        build_editor(buffer, window, cx)
 1631    });
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1634            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1635        });
 1636
 1637        // moving above start of document should move selection to start of document,
 1638        // but the next move down should still be at the original goal_x
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1650
 1651        editor.move_down(&MoveDown, window, cx);
 1652        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1653
 1654        // moving past end of document should not change goal_x
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_up(&MoveUp, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1669    });
 1670}
 1671
 1672#[gpui::test]
 1673fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1674    init_test(cx, |_| {});
 1675    let move_to_beg = MoveToBeginningOfLine {
 1676        stop_at_soft_wraps: true,
 1677        stop_at_indent: true,
 1678    };
 1679
 1680    let delete_to_beg = DeleteToBeginningOfLine {
 1681        stop_at_indent: false,
 1682    };
 1683
 1684    let move_to_end = MoveToEndOfLine {
 1685        stop_at_soft_wraps: true,
 1686    };
 1687
 1688    let editor = cx.add_window(|window, cx| {
 1689        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1690        build_editor(buffer, window, cx)
 1691    });
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1694            s.select_display_ranges([
 1695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1696                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1697            ]);
 1698        });
 1699    });
 1700
 1701    _ = editor.update(cx, |editor, window, cx| {
 1702        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1703        assert_eq!(
 1704            display_ranges(editor, cx),
 1705            &[
 1706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1708            ]
 1709        );
 1710    });
 1711
 1712    _ = editor.update(cx, |editor, window, cx| {
 1713        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1714        assert_eq!(
 1715            display_ranges(editor, cx),
 1716            &[
 1717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1719            ]
 1720        );
 1721    });
 1722
 1723    _ = editor.update(cx, |editor, window, cx| {
 1724        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1725        assert_eq!(
 1726            display_ranges(editor, cx),
 1727            &[
 1728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1729                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1730            ]
 1731        );
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.move_to_end_of_line(&move_to_end, window, cx);
 1736        assert_eq!(
 1737            display_ranges(editor, cx),
 1738            &[
 1739                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1740                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1741            ]
 1742        );
 1743    });
 1744
 1745    // Moving to the end of line again is a no-op.
 1746    _ = editor.update(cx, |editor, window, cx| {
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            display_ranges(editor, cx),
 1750            &[
 1751                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1752                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1753            ]
 1754        );
 1755    });
 1756
 1757    _ = editor.update(cx, |editor, window, cx| {
 1758        editor.move_left(&MoveLeft, window, cx);
 1759        editor.select_to_beginning_of_line(
 1760            &SelectToBeginningOfLine {
 1761                stop_at_soft_wraps: true,
 1762                stop_at_indent: true,
 1763            },
 1764            window,
 1765            cx,
 1766        );
 1767        assert_eq!(
 1768            display_ranges(editor, cx),
 1769            &[
 1770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1771                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1772            ]
 1773        );
 1774    });
 1775
 1776    _ = editor.update(cx, |editor, window, cx| {
 1777        editor.select_to_beginning_of_line(
 1778            &SelectToBeginningOfLine {
 1779                stop_at_soft_wraps: true,
 1780                stop_at_indent: true,
 1781            },
 1782            window,
 1783            cx,
 1784        );
 1785        assert_eq!(
 1786            display_ranges(editor, cx),
 1787            &[
 1788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1789                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1790            ]
 1791        );
 1792    });
 1793
 1794    _ = editor.update(cx, |editor, window, cx| {
 1795        editor.select_to_beginning_of_line(
 1796            &SelectToBeginningOfLine {
 1797                stop_at_soft_wraps: true,
 1798                stop_at_indent: true,
 1799            },
 1800            window,
 1801            cx,
 1802        );
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.select_to_end_of_line(
 1814            &SelectToEndOfLine {
 1815                stop_at_soft_wraps: true,
 1816            },
 1817            window,
 1818            cx,
 1819        );
 1820        assert_eq!(
 1821            display_ranges(editor, cx),
 1822            &[
 1823                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1831        assert_eq!(editor.display_text(cx), "ab\n  de");
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]
 1838        );
 1839    });
 1840
 1841    _ = editor.update(cx, |editor, window, cx| {
 1842        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1843        assert_eq!(editor.display_text(cx), "\n");
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851    });
 1852}
 1853
 1854#[gpui::test]
 1855fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1856    init_test(cx, |_| {});
 1857    let move_to_beg = MoveToBeginningOfLine {
 1858        stop_at_soft_wraps: false,
 1859        stop_at_indent: false,
 1860    };
 1861
 1862    let move_to_end = MoveToEndOfLine {
 1863        stop_at_soft_wraps: false,
 1864    };
 1865
 1866    let editor = cx.add_window(|window, cx| {
 1867        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1868        build_editor(buffer, window, cx)
 1869    });
 1870
 1871    _ = editor.update(cx, |editor, window, cx| {
 1872        editor.set_wrap_width(Some(140.0.into()), cx);
 1873
 1874        // We expect the following lines after wrapping
 1875        // ```
 1876        // thequickbrownfox
 1877        // jumpedoverthelazydo
 1878        // gs
 1879        // ```
 1880        // The final `gs` was soft-wrapped onto a new line.
 1881        assert_eq!(
 1882            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1883            editor.display_text(cx),
 1884        );
 1885
 1886        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1887        // Start the cursor at the `k` on the first line
 1888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1889            s.select_display_ranges([
 1890                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1891            ]);
 1892        });
 1893
 1894        // Moving to the beginning of the line should put us at the beginning of the line.
 1895        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1896        assert_eq!(
 1897            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1898            display_ranges(editor, cx)
 1899        );
 1900
 1901        // Moving to the end of the line should put us at the end of the line.
 1902        editor.move_to_end_of_line(&move_to_end, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1905            display_ranges(editor, cx)
 1906        );
 1907
 1908        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1909        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1911            s.select_display_ranges([
 1912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1913            ]);
 1914        });
 1915
 1916        // Moving to the beginning of the line should put us at the start of the second line of
 1917        // display text, i.e., the `j`.
 1918        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Moving to the beginning of the line again should be a no-op.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            display_ranges(editor, cx)
 1929        );
 1930
 1931        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1932        // next display line.
 1933        editor.move_to_end_of_line(&move_to_end, window, cx);
 1934        assert_eq!(
 1935            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1936            display_ranges(editor, cx)
 1937        );
 1938
 1939        // Moving to the end of the line again should be a no-op.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            display_ranges(editor, cx)
 1944        );
 1945    });
 1946}
 1947
 1948#[gpui::test]
 1949fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1950    init_test(cx, |_| {});
 1951
 1952    let move_to_beg = MoveToBeginningOfLine {
 1953        stop_at_soft_wraps: true,
 1954        stop_at_indent: true,
 1955    };
 1956
 1957    let select_to_beg = SelectToBeginningOfLine {
 1958        stop_at_soft_wraps: true,
 1959        stop_at_indent: true,
 1960    };
 1961
 1962    let delete_to_beg = DeleteToBeginningOfLine {
 1963        stop_at_indent: true,
 1964    };
 1965
 1966    let move_to_end = MoveToEndOfLine {
 1967        stop_at_soft_wraps: false,
 1968    };
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1977            s.select_display_ranges([
 1978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1979                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1984        // and the second cursor at the first non-whitespace character in the line.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            display_ranges(editor, cx),
 1988            &[
 1989                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1990                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1991            ]
 1992        );
 1993
 1994        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1995        // and should move the second cursor to the beginning of the line.
 1996        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1997        assert_eq!(
 1998            display_ranges(editor, cx),
 1999            &[
 2000                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2001                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2002            ]
 2003        );
 2004
 2005        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2006        // and should move the second cursor back to the first non-whitespace character in the line.
 2007        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2008        assert_eq!(
 2009            display_ranges(editor, cx),
 2010            &[
 2011                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2012                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2013            ]
 2014        );
 2015
 2016        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2017        // and to the first non-whitespace character in the line for the second cursor.
 2018        editor.move_to_end_of_line(&move_to_end, window, cx);
 2019        editor.move_left(&MoveLeft, window, cx);
 2020        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2021        assert_eq!(
 2022            display_ranges(editor, cx),
 2023            &[
 2024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2025                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2026            ]
 2027        );
 2028
 2029        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2030        // and should select to the beginning of the line for the second cursor.
 2031        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2032        assert_eq!(
 2033            display_ranges(editor, cx),
 2034            &[
 2035                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2036                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2037            ]
 2038        );
 2039
 2040        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2041        // and should delete to the first non-whitespace character in the line for the second cursor.
 2042        editor.move_to_end_of_line(&move_to_end, window, cx);
 2043        editor.move_left(&MoveLeft, window, cx);
 2044        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2045        assert_eq!(editor.text(cx), "c\n  f");
 2046    });
 2047}
 2048
 2049#[gpui::test]
 2050fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2051    init_test(cx, |_| {});
 2052
 2053    let move_to_beg = MoveToBeginningOfLine {
 2054        stop_at_soft_wraps: true,
 2055        stop_at_indent: true,
 2056    };
 2057
 2058    let editor = cx.add_window(|window, cx| {
 2059        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2060        build_editor(buffer, window, cx)
 2061    });
 2062
 2063    _ = editor.update(cx, |editor, window, cx| {
 2064        // test cursor between line_start and indent_start
 2065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2066            s.select_display_ranges([
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2068            ]);
 2069        });
 2070
 2071        // cursor should move to line_start
 2072        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2073        assert_eq!(
 2074            display_ranges(editor, cx),
 2075            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2076        );
 2077
 2078        // cursor should move to indent_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            display_ranges(editor, cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2083        );
 2084
 2085        // cursor should move to back to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091    });
 2092}
 2093
 2094#[gpui::test]
 2095fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2096    init_test(cx, |_| {});
 2097
 2098    let editor = cx.add_window(|window, cx| {
 2099        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2100        build_editor(buffer, window, cx)
 2101    });
 2102    _ = editor.update(cx, |editor, window, cx| {
 2103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2104            s.select_display_ranges([
 2105                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2106                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2107            ])
 2108        });
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_right(&MoveRight, window, cx);
 2134        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2135        assert_selection_ranges(
 2136            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2137            editor,
 2138            cx,
 2139        );
 2140
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154    });
 2155}
 2156
 2157#[gpui::test]
 2158fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2159    init_test(cx, |_| {});
 2160
 2161    let editor = cx.add_window(|window, cx| {
 2162        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2163        build_editor(buffer, window, cx)
 2164    });
 2165
 2166    _ = editor.update(cx, |editor, window, cx| {
 2167        editor.set_wrap_width(Some(140.0.into()), cx);
 2168        assert_eq!(
 2169            editor.display_text(cx),
 2170            "use one::{\n    two::three::\n    four::five\n};"
 2171        );
 2172
 2173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2174            s.select_display_ranges([
 2175                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2176            ]);
 2177        });
 2178
 2179        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2180        assert_eq!(
 2181            display_ranges(editor, cx),
 2182            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2183        );
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            display_ranges(editor, cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            display_ranges(editor, cx),
 2194            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            display_ranges(editor, cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2201        );
 2202
 2203        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            display_ranges(editor, cx),
 2212            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2213        );
 2214    });
 2215}
 2216
 2217#[gpui::test]
 2218async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2219    init_test(cx, |_| {});
 2220    let mut cx = EditorTestContext::new(cx).await;
 2221
 2222    let line_height = cx.update_editor(|editor, window, cx| {
 2223        editor
 2224            .style(cx)
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.update_editor(|editor, window, cx| {
 2338        editor
 2339            .style(cx)
 2340            .text
 2341            .line_height_in_pixels(window.rem_size())
 2342    });
 2343    let window = cx.window;
 2344    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2345
 2346    cx.set_state(
 2347        r#"ˇone
 2348        two
 2349        three
 2350        four
 2351        five
 2352        six
 2353        seven
 2354        eight
 2355        nine
 2356        ten
 2357        "#,
 2358    );
 2359
 2360    cx.update_editor(|editor, window, cx| {
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 0.)
 2364        );
 2365        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2366        assert_eq!(
 2367            editor.snapshot(window, cx).scroll_position(),
 2368            gpui::Point::new(0., 3.)
 2369        );
 2370        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2371        assert_eq!(
 2372            editor.snapshot(window, cx).scroll_position(),
 2373            gpui::Point::new(0., 6.)
 2374        );
 2375        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2376        assert_eq!(
 2377            editor.snapshot(window, cx).scroll_position(),
 2378            gpui::Point::new(0., 3.)
 2379        );
 2380
 2381        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2382        assert_eq!(
 2383            editor.snapshot(window, cx).scroll_position(),
 2384            gpui::Point::new(0., 1.)
 2385        );
 2386        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2387        assert_eq!(
 2388            editor.snapshot(window, cx).scroll_position(),
 2389            gpui::Point::new(0., 3.)
 2390        );
 2391    });
 2392}
 2393
 2394#[gpui::test]
 2395async fn test_autoscroll(cx: &mut TestAppContext) {
 2396    init_test(cx, |_| {});
 2397    let mut cx = EditorTestContext::new(cx).await;
 2398
 2399    let line_height = cx.update_editor(|editor, window, cx| {
 2400        editor.set_vertical_scroll_margin(2, cx);
 2401        editor
 2402            .style(cx)
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.update_editor(|editor, window, cx| {
 2482        editor
 2483            .style(cx)
 2484            .text
 2485            .line_height_in_pixels(window.rem_size())
 2486    });
 2487    let window = cx.window;
 2488    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2489    cx.set_state(
 2490        &r#"
 2491        ˇone
 2492        two
 2493        threeˇ
 2494        four
 2495        five
 2496        six
 2497        seven
 2498        eight
 2499        nine
 2500        ten
 2501        "#
 2502        .unindent(),
 2503    );
 2504
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.move_page_down(&MovePageDown::default(), window, cx)
 2507    });
 2508    cx.assert_editor_state(
 2509        &r#"
 2510        one
 2511        two
 2512        three
 2513        ˇfour
 2514        five
 2515        sixˇ
 2516        seven
 2517        eight
 2518        nine
 2519        ten
 2520        "#
 2521        .unindent(),
 2522    );
 2523
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.move_page_down(&MovePageDown::default(), window, cx)
 2526    });
 2527    cx.assert_editor_state(
 2528        &r#"
 2529        one
 2530        two
 2531        three
 2532        four
 2533        five
 2534        six
 2535        ˇseven
 2536        eight
 2537        nineˇ
 2538        ten
 2539        "#
 2540        .unindent(),
 2541    );
 2542
 2543    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2561    cx.assert_editor_state(
 2562        &r#"
 2563        ˇone
 2564        two
 2565        threeˇ
 2566        four
 2567        five
 2568        six
 2569        seven
 2570        eight
 2571        nine
 2572        ten
 2573        "#
 2574        .unindent(),
 2575    );
 2576
 2577    // Test select collapsing
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582    });
 2583    cx.assert_editor_state(
 2584        &r#"
 2585        one
 2586        two
 2587        three
 2588        four
 2589        five
 2590        six
 2591        seven
 2592        eight
 2593        nine
 2594        ˇten
 2595        ˇ"#
 2596        .unindent(),
 2597    );
 2598}
 2599
 2600#[gpui::test]
 2601async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2602    init_test(cx, |_| {});
 2603    let mut cx = EditorTestContext::new(cx).await;
 2604    cx.set_state("one «two threeˇ» four");
 2605    cx.update_editor(|editor, window, cx| {
 2606        editor.delete_to_beginning_of_line(
 2607            &DeleteToBeginningOfLine {
 2608                stop_at_indent: false,
 2609            },
 2610            window,
 2611            cx,
 2612        );
 2613        assert_eq!(editor.text(cx), " four");
 2614    });
 2615}
 2616
 2617#[gpui::test]
 2618async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2619    init_test(cx, |_| {});
 2620
 2621    let mut cx = EditorTestContext::new(cx).await;
 2622
 2623    // For an empty selection, the preceding word fragment is deleted.
 2624    // For non-empty selections, only selected characters are deleted.
 2625    cx.set_state("onˇe two t«hreˇ»e four");
 2626    cx.update_editor(|editor, window, cx| {
 2627        editor.delete_to_previous_word_start(
 2628            &DeleteToPreviousWordStart {
 2629                ignore_newlines: false,
 2630                ignore_brackets: false,
 2631            },
 2632            window,
 2633            cx,
 2634        );
 2635    });
 2636    cx.assert_editor_state("ˇe two tˇe four");
 2637
 2638    cx.set_state("e tˇwo te «fˇ»our");
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.delete_to_next_word_end(
 2641            &DeleteToNextWordEnd {
 2642                ignore_newlines: false,
 2643                ignore_brackets: false,
 2644            },
 2645            window,
 2646            cx,
 2647        );
 2648    });
 2649    cx.assert_editor_state("e tˇ te ˇour");
 2650}
 2651
 2652#[gpui::test]
 2653async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2654    init_test(cx, |_| {});
 2655
 2656    let mut cx = EditorTestContext::new(cx).await;
 2657
 2658    cx.set_state("here is some text    ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: false,
 2663                ignore_brackets: true,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2670    cx.assert_editor_state("here is some textˇwith a space");
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: false,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    cx.assert_editor_state("here is some textˇwith a space");
 2684
 2685    cx.set_state("here is some textˇ    with a space");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_next_word_end(
 2688            &DeleteToNextWordEnd {
 2689                ignore_newlines: false,
 2690                ignore_brackets: true,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    // Same happens in the other direction.
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some textˇwith a space");
 2711
 2712    cx.set_state("here is some textˇ    with a space");
 2713    cx.update_editor(|editor, window, cx| {
 2714        editor.delete_to_next_word_end(
 2715            &DeleteToNextWordEnd {
 2716                ignore_newlines: true,
 2717                ignore_brackets: false,
 2718            },
 2719            window,
 2720            cx,
 2721        );
 2722    });
 2723    cx.assert_editor_state("here is some textˇwith a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_previous_word_start(
 2726            &DeleteToPreviousWordStart {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some ˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    // Single whitespaces are removed with the word behind them.
 2746    cx.assert_editor_state("here is ˇwith a space");
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    cx.assert_editor_state("here ˇwith a space");
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    cx.assert_editor_state("ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_next_word_end(
 2782            &DeleteToNextWordEnd {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    // Same happens in the other direction.
 2791    cx.assert_editor_state("ˇ a space");
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_next_word_end(
 2794            &DeleteToNextWordEnd {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state("ˇ space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_previous_word_start(
 2827            &DeleteToPreviousWordStart {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836}
 2837
 2838#[gpui::test]
 2839async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2840    init_test(cx, |_| {});
 2841
 2842    let language = Arc::new(
 2843        Language::new(
 2844            LanguageConfig {
 2845                brackets: BracketPairConfig {
 2846                    pairs: vec![
 2847                        BracketPair {
 2848                            start: "\"".to_string(),
 2849                            end: "\"".to_string(),
 2850                            close: true,
 2851                            surround: true,
 2852                            newline: false,
 2853                        },
 2854                        BracketPair {
 2855                            start: "(".to_string(),
 2856                            end: ")".to_string(),
 2857                            close: true,
 2858                            surround: true,
 2859                            newline: true,
 2860                        },
 2861                    ],
 2862                    ..BracketPairConfig::default()
 2863                },
 2864                ..LanguageConfig::default()
 2865            },
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_brackets_query(
 2869            r#"
 2870                ("(" @open ")" @close)
 2871                ("\"" @open "\"" @close)
 2872            "#,
 2873        )
 2874        .unwrap(),
 2875    );
 2876
 2877    let mut cx = EditorTestContext::new(cx).await;
 2878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2879
 2880    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    // Deletion stops before brackets if asked to not ignore them.
 2892    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    // Deletion has to remove a single bracket and then stop again.
 2904    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_previous_word_start(
 2920            &DeleteToPreviousWordStart {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2953    cx.assert_editor_state(r#"ˇ");"#);
 2954
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_next_word_end(
 2957            &DeleteToNextWordEnd {
 2958                ignore_newlines: true,
 2959                ignore_brackets: false,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"ˇ"#);
 2966
 2967    cx.update_editor(|editor, window, cx| {
 2968        editor.delete_to_next_word_end(
 2969            &DeleteToNextWordEnd {
 2970                ignore_newlines: true,
 2971                ignore_brackets: false,
 2972            },
 2973            window,
 2974            cx,
 2975        );
 2976    });
 2977    cx.assert_editor_state(r#"ˇ"#);
 2978
 2979    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2980    cx.update_editor(|editor, window, cx| {
 2981        editor.delete_to_previous_word_start(
 2982            &DeleteToPreviousWordStart {
 2983                ignore_newlines: true,
 2984                ignore_brackets: true,
 2985            },
 2986            window,
 2987            cx,
 2988        );
 2989    });
 2990    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2991}
 2992
 2993#[gpui::test]
 2994fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2999        build_editor(buffer, window, cx)
 3000    });
 3001    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3002        ignore_newlines: false,
 3003        ignore_brackets: false,
 3004    };
 3005    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3006        ignore_newlines: true,
 3007        ignore_brackets: false,
 3008    };
 3009
 3010    _ = editor.update(cx, |editor, window, cx| {
 3011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3012            s.select_display_ranges([
 3013                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3014            ])
 3015        });
 3016        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3017        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3018        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3020        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3021        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3022        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3023        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3024        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3025        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3028    });
 3029}
 3030
 3031#[gpui::test]
 3032fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3033    init_test(cx, |_| {});
 3034
 3035    let editor = cx.add_window(|window, cx| {
 3036        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3037        build_editor(buffer, window, cx)
 3038    });
 3039    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3040        ignore_newlines: false,
 3041        ignore_brackets: false,
 3042    };
 3043    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3044        ignore_newlines: true,
 3045        ignore_brackets: false,
 3046    };
 3047
 3048    _ = editor.update(cx, |editor, window, cx| {
 3049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3050            s.select_display_ranges([
 3051                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3052            ])
 3053        });
 3054        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3056        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3058        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3060        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3062        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3064        editor.delete_to_previous_subword_start(
 3065            &del_to_prev_sub_word_start_ignore_newlines,
 3066            window,
 3067            cx,
 3068        );
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3070    });
 3071}
 3072
 3073#[gpui::test]
 3074fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3075    init_test(cx, |_| {});
 3076
 3077    let editor = cx.add_window(|window, cx| {
 3078        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3079        build_editor(buffer, window, cx)
 3080    });
 3081    let del_to_next_word_end = DeleteToNextWordEnd {
 3082        ignore_newlines: false,
 3083        ignore_brackets: false,
 3084    };
 3085    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3086        ignore_newlines: true,
 3087        ignore_brackets: false,
 3088    };
 3089
 3090    _ = editor.update(cx, |editor, window, cx| {
 3091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3092            s.select_display_ranges([
 3093                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3094            ])
 3095        });
 3096        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3097        assert_eq!(
 3098            editor.buffer.read(cx).read(cx).text(),
 3099            "one\n   two\nthree\n   four"
 3100        );
 3101        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3102        assert_eq!(
 3103            editor.buffer.read(cx).read(cx).text(),
 3104            "\n   two\nthree\n   four"
 3105        );
 3106        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3107        assert_eq!(
 3108            editor.buffer.read(cx).read(cx).text(),
 3109            "two\nthree\n   four"
 3110        );
 3111        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3113        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3114        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3115        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3116        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3117        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3118        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3119    });
 3120}
 3121
 3122#[gpui::test]
 3123fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3128        build_editor(buffer, window, cx)
 3129    });
 3130    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3131        ignore_newlines: false,
 3132        ignore_brackets: false,
 3133    };
 3134    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3135        ignore_newlines: true,
 3136        ignore_brackets: false,
 3137    };
 3138
 3139    _ = editor.update(cx, |editor, window, cx| {
 3140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3141            s.select_display_ranges([
 3142                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3143            ])
 3144        });
 3145        // Delete "\n" (empty line)
 3146        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3147        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3148        // Delete "foo" (subword boundary)
 3149        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3150        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3151        // Delete "Bar"
 3152        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3153        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3154        // Delete "\n   " (newline + leading whitespace)
 3155        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3156        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3157        // Delete "baz" (subword boundary)
 3158        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3159        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3160        // With ignore_newlines, delete "Qux"
 3161        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3162        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3163    });
 3164}
 3165
 3166#[gpui::test]
 3167fn test_newline(cx: &mut TestAppContext) {
 3168    init_test(cx, |_| {});
 3169
 3170    let editor = cx.add_window(|window, cx| {
 3171        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3172        build_editor(buffer, window, cx)
 3173    });
 3174
 3175    _ = editor.update(cx, |editor, window, cx| {
 3176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3177            s.select_display_ranges([
 3178                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3179                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3180                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3181            ])
 3182        });
 3183
 3184        editor.newline(&Newline, window, cx);
 3185        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3186    });
 3187}
 3188
 3189#[gpui::test]
 3190async fn test_newline_yaml(cx: &mut TestAppContext) {
 3191    init_test(cx, |_| {});
 3192
 3193    let mut cx = EditorTestContext::new(cx).await;
 3194    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3196
 3197    // Object (between 2 fields)
 3198    cx.set_state(indoc! {"
 3199    test:ˇ
 3200    hello: bye"});
 3201    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203    test:
 3204        ˇ
 3205    hello: bye"});
 3206
 3207    // Object (first and single line)
 3208    cx.set_state(indoc! {"
 3209    test:ˇ"});
 3210    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212    test:
 3213        ˇ"});
 3214
 3215    // Array with objects (after first element)
 3216    cx.set_state(indoc! {"
 3217    test:
 3218        - foo: barˇ"});
 3219    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221    test:
 3222        - foo: bar
 3223        ˇ"});
 3224
 3225    // Array with objects and comment
 3226    cx.set_state(indoc! {"
 3227    test:
 3228        - foo: bar
 3229        - bar: # testˇ"});
 3230    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3231    cx.assert_editor_state(indoc! {"
 3232    test:
 3233        - foo: bar
 3234        - bar: # test
 3235            ˇ"});
 3236
 3237    // Array with objects (after second element)
 3238    cx.set_state(indoc! {"
 3239    test:
 3240        - foo: bar
 3241        - bar: fooˇ"});
 3242    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244    test:
 3245        - foo: bar
 3246        - bar: foo
 3247        ˇ"});
 3248
 3249    // Array with strings (after first element)
 3250    cx.set_state(indoc! {"
 3251    test:
 3252        - fooˇ"});
 3253    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3254    cx.assert_editor_state(indoc! {"
 3255    test:
 3256        - foo
 3257        ˇ"});
 3258}
 3259
 3260#[gpui::test]
 3261fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3262    init_test(cx, |_| {});
 3263
 3264    let editor = cx.add_window(|window, cx| {
 3265        let buffer = MultiBuffer::build_simple(
 3266            "
 3267                a
 3268                b(
 3269                    X
 3270                )
 3271                c(
 3272                    X
 3273                )
 3274            "
 3275            .unindent()
 3276            .as_str(),
 3277            cx,
 3278        );
 3279        let mut editor = build_editor(buffer, window, cx);
 3280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3281            s.select_ranges([
 3282                Point::new(2, 4)..Point::new(2, 5),
 3283                Point::new(5, 4)..Point::new(5, 5),
 3284            ])
 3285        });
 3286        editor
 3287    });
 3288
 3289    _ = editor.update(cx, |editor, window, cx| {
 3290        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3291        editor.buffer.update(cx, |buffer, cx| {
 3292            buffer.edit(
 3293                [
 3294                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3295                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3296                ],
 3297                None,
 3298                cx,
 3299            );
 3300            assert_eq!(
 3301                buffer.read(cx).text(),
 3302                "
 3303                    a
 3304                    b()
 3305                    c()
 3306                "
 3307                .unindent()
 3308            );
 3309        });
 3310        assert_eq!(
 3311            editor.selections.ranges(&editor.display_snapshot(cx)),
 3312            &[
 3313                Point::new(1, 2)..Point::new(1, 2),
 3314                Point::new(2, 2)..Point::new(2, 2),
 3315            ],
 3316        );
 3317
 3318        editor.newline(&Newline, window, cx);
 3319        assert_eq!(
 3320            editor.text(cx),
 3321            "
 3322                a
 3323                b(
 3324                )
 3325                c(
 3326                )
 3327            "
 3328            .unindent()
 3329        );
 3330
 3331        // The selections are moved after the inserted newlines
 3332        assert_eq!(
 3333            editor.selections.ranges(&editor.display_snapshot(cx)),
 3334            &[
 3335                Point::new(2, 0)..Point::new(2, 0),
 3336                Point::new(4, 0)..Point::new(4, 0),
 3337            ],
 3338        );
 3339    });
 3340}
 3341
 3342#[gpui::test]
 3343async fn test_newline_above(cx: &mut TestAppContext) {
 3344    init_test(cx, |settings| {
 3345        settings.defaults.tab_size = NonZeroU32::new(4)
 3346    });
 3347
 3348    let language = Arc::new(
 3349        Language::new(
 3350            LanguageConfig::default(),
 3351            Some(tree_sitter_rust::LANGUAGE.into()),
 3352        )
 3353        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3354        .unwrap(),
 3355    );
 3356
 3357    let mut cx = EditorTestContext::new(cx).await;
 3358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3359    cx.set_state(indoc! {"
 3360        const a: ˇA = (
 3361 3362                «const_functionˇ»(ˇ),
 3363                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3364 3365        ˇ);ˇ
 3366    "});
 3367
 3368    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3369    cx.assert_editor_state(indoc! {"
 3370        ˇ
 3371        const a: A = (
 3372            ˇ
 3373            (
 3374                ˇ
 3375                ˇ
 3376                const_function(),
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380                ˇ
 3381                something_else,
 3382                ˇ
 3383            )
 3384            ˇ
 3385            ˇ
 3386        );
 3387    "});
 3388}
 3389
 3390#[gpui::test]
 3391async fn test_newline_below(cx: &mut TestAppContext) {
 3392    init_test(cx, |settings| {
 3393        settings.defaults.tab_size = NonZeroU32::new(4)
 3394    });
 3395
 3396    let language = Arc::new(
 3397        Language::new(
 3398            LanguageConfig::default(),
 3399            Some(tree_sitter_rust::LANGUAGE.into()),
 3400        )
 3401        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3402        .unwrap(),
 3403    );
 3404
 3405    let mut cx = EditorTestContext::new(cx).await;
 3406    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3407    cx.set_state(indoc! {"
 3408        const a: ˇA = (
 3409 3410                «const_functionˇ»(ˇ),
 3411                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3412 3413        ˇ);ˇ
 3414    "});
 3415
 3416    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3417    cx.assert_editor_state(indoc! {"
 3418        const a: A = (
 3419            ˇ
 3420            (
 3421                ˇ
 3422                const_function(),
 3423                ˇ
 3424                ˇ
 3425                something_else,
 3426                ˇ
 3427                ˇ
 3428                ˇ
 3429                ˇ
 3430            )
 3431            ˇ
 3432        );
 3433        ˇ
 3434        ˇ
 3435    "});
 3436}
 3437
 3438#[gpui::test]
 3439async fn test_newline_comments(cx: &mut TestAppContext) {
 3440    init_test(cx, |settings| {
 3441        settings.defaults.tab_size = NonZeroU32::new(4)
 3442    });
 3443
 3444    let language = Arc::new(Language::new(
 3445        LanguageConfig {
 3446            line_comments: vec!["// ".into()],
 3447            ..LanguageConfig::default()
 3448        },
 3449        None,
 3450    ));
 3451    {
 3452        let mut cx = EditorTestContext::new(cx).await;
 3453        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3454        cx.set_state(indoc! {"
 3455        // Fooˇ
 3456    "});
 3457
 3458        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3459        cx.assert_editor_state(indoc! {"
 3460        // Foo
 3461        // ˇ
 3462    "});
 3463        // Ensure that we add comment prefix when existing line contains space
 3464        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3465        cx.assert_editor_state(
 3466            indoc! {"
 3467        // Foo
 3468        //s
 3469        // ˇ
 3470    "}
 3471            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3472            .as_str(),
 3473        );
 3474        // Ensure that we add comment prefix when existing line does not contain space
 3475        cx.set_state(indoc! {"
 3476        // Foo
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        // Foo
 3482        //
 3483        // ˇ
 3484    "});
 3485        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3486        cx.set_state(indoc! {"
 3487        ˇ// Foo
 3488    "});
 3489        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3490        cx.assert_editor_state(indoc! {"
 3491
 3492        ˇ// Foo
 3493    "});
 3494    }
 3495    // Ensure that comment continuations can be disabled.
 3496    update_test_language_settings(cx, |settings| {
 3497        settings.defaults.extend_comment_on_newline = Some(false);
 3498    });
 3499    let mut cx = EditorTestContext::new(cx).await;
 3500    cx.set_state(indoc! {"
 3501        // Fooˇ
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        // Foo
 3506        ˇ
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3512    init_test(cx, |settings| {
 3513        settings.defaults.tab_size = NonZeroU32::new(4)
 3514    });
 3515
 3516    let language = Arc::new(Language::new(
 3517        LanguageConfig {
 3518            line_comments: vec!["// ".into(), "/// ".into()],
 3519            ..LanguageConfig::default()
 3520        },
 3521        None,
 3522    ));
 3523    {
 3524        let mut cx = EditorTestContext::new(cx).await;
 3525        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3526        cx.set_state(indoc! {"
 3527        //ˇ
 3528    "});
 3529        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3530        cx.assert_editor_state(indoc! {"
 3531        //
 3532        // ˇ
 3533    "});
 3534
 3535        cx.set_state(indoc! {"
 3536        ///ˇ
 3537    "});
 3538        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3539        cx.assert_editor_state(indoc! {"
 3540        ///
 3541        /// ˇ
 3542    "});
 3543    }
 3544}
 3545
 3546#[gpui::test]
 3547async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3548    init_test(cx, |settings| {
 3549        settings.defaults.tab_size = NonZeroU32::new(4)
 3550    });
 3551
 3552    let language = Arc::new(
 3553        Language::new(
 3554            LanguageConfig {
 3555                documentation_comment: Some(language::BlockCommentConfig {
 3556                    start: "/**".into(),
 3557                    end: "*/".into(),
 3558                    prefix: "* ".into(),
 3559                    tab_size: 1,
 3560                }),
 3561
 3562                ..LanguageConfig::default()
 3563            },
 3564            Some(tree_sitter_rust::LANGUAGE.into()),
 3565        )
 3566        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3567        .unwrap(),
 3568    );
 3569
 3570    {
 3571        let mut cx = EditorTestContext::new(cx).await;
 3572        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3573        cx.set_state(indoc! {"
 3574        /**ˇ
 3575    "});
 3576
 3577        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3578        cx.assert_editor_state(indoc! {"
 3579        /**
 3580         * ˇ
 3581    "});
 3582        // Ensure that if cursor is before the comment start,
 3583        // we do not actually insert a comment prefix.
 3584        cx.set_state(indoc! {"
 3585        ˇ/**
 3586    "});
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(indoc! {"
 3589
 3590        ˇ/**
 3591    "});
 3592        // Ensure that if cursor is between it doesn't add comment prefix.
 3593        cx.set_state(indoc! {"
 3594        /*ˇ*
 3595    "});
 3596        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3597        cx.assert_editor_state(indoc! {"
 3598        /*
 3599        ˇ*
 3600    "});
 3601        // Ensure that if suffix exists on same line after cursor it adds new line.
 3602        cx.set_state(indoc! {"
 3603        /**ˇ*/
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         * ˇ
 3609         */
 3610    "});
 3611        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3612        cx.set_state(indoc! {"
 3613        /**ˇ */
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         * ˇ
 3619         */
 3620    "});
 3621        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3622        cx.set_state(indoc! {"
 3623        /** ˇ*/
 3624    "});
 3625        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3626        cx.assert_editor_state(
 3627            indoc! {"
 3628        /**s
 3629         * ˇ
 3630         */
 3631    "}
 3632            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3633            .as_str(),
 3634        );
 3635        // Ensure that delimiter space is preserved when newline on already
 3636        // spaced delimiter.
 3637        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3638        cx.assert_editor_state(
 3639            indoc! {"
 3640        /**s
 3641         *s
 3642         * ˇ
 3643         */
 3644    "}
 3645            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3646            .as_str(),
 3647        );
 3648        // Ensure that delimiter space is preserved when space is not
 3649        // on existing delimiter.
 3650        cx.set_state(indoc! {"
 3651        /**
 3652 3653         */
 3654    "});
 3655        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3656        cx.assert_editor_state(indoc! {"
 3657        /**
 3658         *
 3659         * ˇ
 3660         */
 3661    "});
 3662        // Ensure that if suffix exists on same line after cursor it
 3663        // doesn't add extra new line if prefix is not on same line.
 3664        cx.set_state(indoc! {"
 3665        /**
 3666        ˇ*/
 3667    "});
 3668        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3669        cx.assert_editor_state(indoc! {"
 3670        /**
 3671
 3672        ˇ*/
 3673    "});
 3674        // Ensure that it detects suffix after existing prefix.
 3675        cx.set_state(indoc! {"
 3676        /**ˇ/
 3677    "});
 3678        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3679        cx.assert_editor_state(indoc! {"
 3680        /**
 3681        ˇ/
 3682    "});
 3683        // Ensure that if suffix exists on same line before
 3684        // cursor it does not add comment prefix.
 3685        cx.set_state(indoc! {"
 3686        /** */ˇ
 3687    "});
 3688        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3689        cx.assert_editor_state(indoc! {"
 3690        /** */
 3691        ˇ
 3692    "});
 3693        // Ensure that if suffix exists on same line before
 3694        // cursor it does not add comment prefix.
 3695        cx.set_state(indoc! {"
 3696        /**
 3697         *
 3698         */ˇ
 3699    "});
 3700        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3701        cx.assert_editor_state(indoc! {"
 3702        /**
 3703         *
 3704         */
 3705         ˇ
 3706    "});
 3707
 3708        // Ensure that inline comment followed by code
 3709        // doesn't add comment prefix on newline
 3710        cx.set_state(indoc! {"
 3711        /** */ textˇ
 3712    "});
 3713        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3714        cx.assert_editor_state(indoc! {"
 3715        /** */ text
 3716        ˇ
 3717    "});
 3718
 3719        // Ensure that text after comment end tag
 3720        // doesn't add comment prefix on newline
 3721        cx.set_state(indoc! {"
 3722        /**
 3723         *
 3724         */ˇtext
 3725    "});
 3726        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3727        cx.assert_editor_state(indoc! {"
 3728        /**
 3729         *
 3730         */
 3731         ˇtext
 3732    "});
 3733
 3734        // Ensure if not comment block it doesn't
 3735        // add comment prefix on newline
 3736        cx.set_state(indoc! {"
 3737        * textˇ
 3738    "});
 3739        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3740        cx.assert_editor_state(indoc! {"
 3741        * text
 3742        ˇ
 3743    "});
 3744    }
 3745    // Ensure that comment continuations can be disabled.
 3746    update_test_language_settings(cx, |settings| {
 3747        settings.defaults.extend_comment_on_newline = Some(false);
 3748    });
 3749    let mut cx = EditorTestContext::new(cx).await;
 3750    cx.set_state(indoc! {"
 3751        /**ˇ
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        /**
 3756        ˇ
 3757    "});
 3758}
 3759
 3760#[gpui::test]
 3761async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3762    init_test(cx, |settings| {
 3763        settings.defaults.tab_size = NonZeroU32::new(4)
 3764    });
 3765
 3766    let lua_language = Arc::new(Language::new(
 3767        LanguageConfig {
 3768            line_comments: vec!["--".into()],
 3769            block_comment: Some(language::BlockCommentConfig {
 3770                start: "--[[".into(),
 3771                prefix: "".into(),
 3772                end: "]]".into(),
 3773                tab_size: 0,
 3774            }),
 3775            ..LanguageConfig::default()
 3776        },
 3777        None,
 3778    ));
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3782
 3783    // Line with line comment should extend
 3784    cx.set_state(indoc! {"
 3785        --ˇ
 3786    "});
 3787    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3788    cx.assert_editor_state(indoc! {"
 3789        --
 3790        --ˇ
 3791    "});
 3792
 3793    // Line with block comment that matches line comment should not extend
 3794    cx.set_state(indoc! {"
 3795        --[[ˇ
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        --[[
 3800        ˇ
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let editor = cx.add_window(|window, cx| {
 3809        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3810        let mut editor = build_editor(buffer, window, cx);
 3811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3812            s.select_ranges([
 3813                MultiBufferOffset(3)..MultiBufferOffset(4),
 3814                MultiBufferOffset(11)..MultiBufferOffset(12),
 3815                MultiBufferOffset(19)..MultiBufferOffset(20),
 3816            ])
 3817        });
 3818        editor
 3819    });
 3820
 3821    _ = editor.update(cx, |editor, window, cx| {
 3822        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3823        editor.buffer.update(cx, |buffer, cx| {
 3824            buffer.edit(
 3825                [
 3826                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3827                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3828                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3829                ],
 3830                None,
 3831                cx,
 3832            );
 3833            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3834        });
 3835        assert_eq!(
 3836            editor.selections.ranges(&editor.display_snapshot(cx)),
 3837            &[
 3838                MultiBufferOffset(2)..MultiBufferOffset(2),
 3839                MultiBufferOffset(7)..MultiBufferOffset(7),
 3840                MultiBufferOffset(12)..MultiBufferOffset(12)
 3841            ],
 3842        );
 3843
 3844        editor.insert("Z", window, cx);
 3845        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3846
 3847        // The selections are moved after the inserted characters
 3848        assert_eq!(
 3849            editor.selections.ranges(&editor.display_snapshot(cx)),
 3850            &[
 3851                MultiBufferOffset(3)..MultiBufferOffset(3),
 3852                MultiBufferOffset(9)..MultiBufferOffset(9),
 3853                MultiBufferOffset(15)..MultiBufferOffset(15)
 3854            ],
 3855        );
 3856    });
 3857}
 3858
 3859#[gpui::test]
 3860async fn test_tab(cx: &mut TestAppContext) {
 3861    init_test(cx, |settings| {
 3862        settings.defaults.tab_size = NonZeroU32::new(3)
 3863    });
 3864
 3865    let mut cx = EditorTestContext::new(cx).await;
 3866    cx.set_state(indoc! {"
 3867        ˇabˇc
 3868        ˇ🏀ˇ🏀ˇefg
 3869 3870    "});
 3871    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3872    cx.assert_editor_state(indoc! {"
 3873           ˇab ˇc
 3874           ˇ🏀  ˇ🏀  ˇefg
 3875        d  ˇ
 3876    "});
 3877
 3878    cx.set_state(indoc! {"
 3879        a
 3880        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        a
 3885           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3886    "});
 3887}
 3888
 3889#[gpui::test]
 3890async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3891    init_test(cx, |_| {});
 3892
 3893    let mut cx = EditorTestContext::new(cx).await;
 3894    let language = Arc::new(
 3895        Language::new(
 3896            LanguageConfig::default(),
 3897            Some(tree_sitter_rust::LANGUAGE.into()),
 3898        )
 3899        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3900        .unwrap(),
 3901    );
 3902    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3903
 3904    // test when all cursors are not at suggested indent
 3905    // then simply move to their suggested indent location
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909        ˇ
 3910        ˇ    )
 3911        );
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        const a: B = (
 3916            c(
 3917                ˇ
 3918            ˇ)
 3919        );
 3920    "});
 3921
 3922    // test cursor already at suggested indent not moving when
 3923    // other cursors are yet to reach their suggested indents
 3924    cx.set_state(indoc! {"
 3925        ˇ
 3926        const a: B = (
 3927            c(
 3928                d(
 3929        ˇ
 3930                )
 3931        ˇ
 3932        ˇ    )
 3933        );
 3934    "});
 3935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3936    cx.assert_editor_state(indoc! {"
 3937        ˇ
 3938        const a: B = (
 3939            c(
 3940                d(
 3941                    ˇ
 3942                )
 3943                ˇ
 3944            ˇ)
 3945        );
 3946    "});
 3947    // test when all cursors are at suggested indent then tab is inserted
 3948    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3949    cx.assert_editor_state(indoc! {"
 3950            ˇ
 3951        const a: B = (
 3952            c(
 3953                d(
 3954                        ˇ
 3955                )
 3956                    ˇ
 3957                ˇ)
 3958        );
 3959    "});
 3960
 3961    // test when current indent is less than suggested indent,
 3962    // we adjust line to match suggested indent and move cursor to it
 3963    //
 3964    // when no other cursor is at word boundary, all of them should move
 3965    cx.set_state(indoc! {"
 3966        const a: B = (
 3967            c(
 3968                d(
 3969        ˇ
 3970        ˇ   )
 3971        ˇ   )
 3972        );
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        const a: B = (
 3977            c(
 3978                d(
 3979                    ˇ
 3980                ˇ)
 3981            ˇ)
 3982        );
 3983    "});
 3984
 3985    // test when current indent is less than suggested indent,
 3986    // we adjust line to match suggested indent and move cursor to it
 3987    //
 3988    // when some other cursor is at word boundary, it should not move
 3989    cx.set_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                d(
 3993        ˇ
 3994        ˇ   )
 3995           ˇ)
 3996        );
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        const a: B = (
 4001            c(
 4002                d(
 4003                    ˇ
 4004                ˇ)
 4005            ˇ)
 4006        );
 4007    "});
 4008
 4009    // test when current indent is more than suggested indent,
 4010    // we just move cursor to current indent instead of suggested indent
 4011    //
 4012    // when no other cursor is at word boundary, all of them should move
 4013    cx.set_state(indoc! {"
 4014        const a: B = (
 4015            c(
 4016                d(
 4017        ˇ
 4018        ˇ                )
 4019        ˇ   )
 4020        );
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        const a: B = (
 4025            c(
 4026                d(
 4027                    ˇ
 4028                        ˇ)
 4029            ˇ)
 4030        );
 4031    "});
 4032    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4033    cx.assert_editor_state(indoc! {"
 4034        const a: B = (
 4035            c(
 4036                d(
 4037                        ˇ
 4038                            ˇ)
 4039                ˇ)
 4040        );
 4041    "});
 4042
 4043    // test when current indent is more than suggested indent,
 4044    // we just move cursor to current indent instead of suggested indent
 4045    //
 4046    // when some other cursor is at word boundary, it doesn't move
 4047    cx.set_state(indoc! {"
 4048        const a: B = (
 4049            c(
 4050                d(
 4051        ˇ
 4052        ˇ                )
 4053            ˇ)
 4054        );
 4055    "});
 4056    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4057    cx.assert_editor_state(indoc! {"
 4058        const a: B = (
 4059            c(
 4060                d(
 4061                    ˇ
 4062                        ˇ)
 4063            ˇ)
 4064        );
 4065    "});
 4066
 4067    // handle auto-indent when there are multiple cursors on the same line
 4068    cx.set_state(indoc! {"
 4069        const a: B = (
 4070            c(
 4071        ˇ    ˇ
 4072        ˇ    )
 4073        );
 4074    "});
 4075    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4076    cx.assert_editor_state(indoc! {"
 4077        const a: B = (
 4078            c(
 4079                ˇ
 4080            ˇ)
 4081        );
 4082    "});
 4083}
 4084
 4085#[gpui::test]
 4086async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4087    init_test(cx, |settings| {
 4088        settings.defaults.tab_size = NonZeroU32::new(3)
 4089    });
 4090
 4091    let mut cx = EditorTestContext::new(cx).await;
 4092    cx.set_state(indoc! {"
 4093         ˇ
 4094        \t ˇ
 4095        \t  ˇ
 4096        \t   ˇ
 4097         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4098    "});
 4099
 4100    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4101    cx.assert_editor_state(indoc! {"
 4102           ˇ
 4103        \t   ˇ
 4104        \t   ˇ
 4105        \t      ˇ
 4106         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4107    "});
 4108}
 4109
 4110#[gpui::test]
 4111async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4112    init_test(cx, |settings| {
 4113        settings.defaults.tab_size = NonZeroU32::new(4)
 4114    });
 4115
 4116    let language = Arc::new(
 4117        Language::new(
 4118            LanguageConfig::default(),
 4119            Some(tree_sitter_rust::LANGUAGE.into()),
 4120        )
 4121        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4122        .unwrap(),
 4123    );
 4124
 4125    let mut cx = EditorTestContext::new(cx).await;
 4126    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4127    cx.set_state(indoc! {"
 4128        fn a() {
 4129            if b {
 4130        \t ˇc
 4131            }
 4132        }
 4133    "});
 4134
 4135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        fn a() {
 4138            if b {
 4139                ˇc
 4140            }
 4141        }
 4142    "});
 4143}
 4144
 4145#[gpui::test]
 4146async fn test_indent_outdent(cx: &mut TestAppContext) {
 4147    init_test(cx, |settings| {
 4148        settings.defaults.tab_size = NonZeroU32::new(4);
 4149    });
 4150
 4151    let mut cx = EditorTestContext::new(cx).await;
 4152
 4153    cx.set_state(indoc! {"
 4154          «oneˇ» «twoˇ»
 4155        three
 4156         four
 4157    "});
 4158    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4159    cx.assert_editor_state(indoc! {"
 4160            «oneˇ» «twoˇ»
 4161        three
 4162         four
 4163    "});
 4164
 4165    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4166    cx.assert_editor_state(indoc! {"
 4167        «oneˇ» «twoˇ»
 4168        three
 4169         four
 4170    "});
 4171
 4172    // select across line ending
 4173    cx.set_state(indoc! {"
 4174        one two
 4175        t«hree
 4176        ˇ» four
 4177    "});
 4178    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4179    cx.assert_editor_state(indoc! {"
 4180        one two
 4181            t«hree
 4182        ˇ» four
 4183    "});
 4184
 4185    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4186    cx.assert_editor_state(indoc! {"
 4187        one two
 4188        t«hree
 4189        ˇ» four
 4190    "});
 4191
 4192    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4193    cx.set_state(indoc! {"
 4194        one two
 4195        ˇthree
 4196            four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201            ˇthree
 4202            four
 4203    "});
 4204
 4205    cx.set_state(indoc! {"
 4206        one two
 4207        ˇ    three
 4208            four
 4209    "});
 4210    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4211    cx.assert_editor_state(indoc! {"
 4212        one two
 4213        ˇthree
 4214            four
 4215    "});
 4216}
 4217
 4218#[gpui::test]
 4219async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4220    // This is a regression test for issue #33761
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4225    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4226
 4227    cx.set_state(
 4228        r#"ˇ#     ingress:
 4229ˇ#         api:
 4230ˇ#             enabled: false
 4231ˇ#             pathType: Prefix
 4232ˇ#           console:
 4233ˇ#               enabled: false
 4234ˇ#               pathType: Prefix
 4235"#,
 4236    );
 4237
 4238    // Press tab to indent all lines
 4239    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4240
 4241    cx.assert_editor_state(
 4242        r#"    ˇ#     ingress:
 4243    ˇ#         api:
 4244    ˇ#             enabled: false
 4245    ˇ#             pathType: Prefix
 4246    ˇ#           console:
 4247    ˇ#               enabled: false
 4248    ˇ#               pathType: Prefix
 4249"#,
 4250    );
 4251}
 4252
 4253#[gpui::test]
 4254async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4255    // This is a test to make sure our fix for issue #33761 didn't break anything
 4256    init_test(cx, |_| {});
 4257
 4258    let mut cx = EditorTestContext::new(cx).await;
 4259    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4261
 4262    cx.set_state(
 4263        r#"ˇingress:
 4264ˇ  api:
 4265ˇ    enabled: false
 4266ˇ    pathType: Prefix
 4267"#,
 4268    );
 4269
 4270    // Press tab to indent all lines
 4271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4272
 4273    cx.assert_editor_state(
 4274        r#"ˇingress:
 4275    ˇapi:
 4276        ˇenabled: false
 4277        ˇpathType: Prefix
 4278"#,
 4279    );
 4280}
 4281
 4282#[gpui::test]
 4283async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4284    init_test(cx, |settings| {
 4285        settings.defaults.hard_tabs = Some(true);
 4286    });
 4287
 4288    let mut cx = EditorTestContext::new(cx).await;
 4289
 4290    // select two ranges on one line
 4291    cx.set_state(indoc! {"
 4292        «oneˇ» «twoˇ»
 4293        three
 4294        four
 4295    "});
 4296    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4297    cx.assert_editor_state(indoc! {"
 4298        \t«oneˇ» «twoˇ»
 4299        three
 4300        four
 4301    "});
 4302    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4303    cx.assert_editor_state(indoc! {"
 4304        \t\t«oneˇ» «twoˇ»
 4305        three
 4306        four
 4307    "});
 4308    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4309    cx.assert_editor_state(indoc! {"
 4310        \t«oneˇ» «twoˇ»
 4311        three
 4312        four
 4313    "});
 4314    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4315    cx.assert_editor_state(indoc! {"
 4316        «oneˇ» «twoˇ»
 4317        three
 4318        four
 4319    "});
 4320
 4321    // select across a line ending
 4322    cx.set_state(indoc! {"
 4323        one two
 4324        t«hree
 4325        ˇ»four
 4326    "});
 4327    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4328    cx.assert_editor_state(indoc! {"
 4329        one two
 4330        \tt«hree
 4331        ˇ»four
 4332    "});
 4333    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4334    cx.assert_editor_state(indoc! {"
 4335        one two
 4336        \t\tt«hree
 4337        ˇ»four
 4338    "});
 4339    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4340    cx.assert_editor_state(indoc! {"
 4341        one two
 4342        \tt«hree
 4343        ˇ»four
 4344    "});
 4345    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4346    cx.assert_editor_state(indoc! {"
 4347        one two
 4348        t«hree
 4349        ˇ»four
 4350    "});
 4351
 4352    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4353    cx.set_state(indoc! {"
 4354        one two
 4355        ˇthree
 4356        four
 4357    "});
 4358    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4359    cx.assert_editor_state(indoc! {"
 4360        one two
 4361        ˇthree
 4362        four
 4363    "});
 4364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4365    cx.assert_editor_state(indoc! {"
 4366        one two
 4367        \tˇthree
 4368        four
 4369    "});
 4370    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4371    cx.assert_editor_state(indoc! {"
 4372        one two
 4373        ˇthree
 4374        four
 4375    "});
 4376}
 4377
 4378#[gpui::test]
 4379fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4380    init_test(cx, |settings| {
 4381        settings.languages.0.extend([
 4382            (
 4383                "TOML".into(),
 4384                LanguageSettingsContent {
 4385                    tab_size: NonZeroU32::new(2),
 4386                    ..Default::default()
 4387                },
 4388            ),
 4389            (
 4390                "Rust".into(),
 4391                LanguageSettingsContent {
 4392                    tab_size: NonZeroU32::new(4),
 4393                    ..Default::default()
 4394                },
 4395            ),
 4396        ]);
 4397    });
 4398
 4399    let toml_language = Arc::new(Language::new(
 4400        LanguageConfig {
 4401            name: "TOML".into(),
 4402            ..Default::default()
 4403        },
 4404        None,
 4405    ));
 4406    let rust_language = Arc::new(Language::new(
 4407        LanguageConfig {
 4408            name: "Rust".into(),
 4409            ..Default::default()
 4410        },
 4411        None,
 4412    ));
 4413
 4414    let toml_buffer =
 4415        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4416    let rust_buffer =
 4417        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4418    let multibuffer = cx.new(|cx| {
 4419        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4420        multibuffer.push_excerpts(
 4421            toml_buffer.clone(),
 4422            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4423            cx,
 4424        );
 4425        multibuffer.push_excerpts(
 4426            rust_buffer.clone(),
 4427            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4428            cx,
 4429        );
 4430        multibuffer
 4431    });
 4432
 4433    cx.add_window(|window, cx| {
 4434        let mut editor = build_editor(multibuffer, window, cx);
 4435
 4436        assert_eq!(
 4437            editor.text(cx),
 4438            indoc! {"
 4439                a = 1
 4440                b = 2
 4441
 4442                const c: usize = 3;
 4443            "}
 4444        );
 4445
 4446        select_ranges(
 4447            &mut editor,
 4448            indoc! {"
 4449                «aˇ» = 1
 4450                b = 2
 4451
 4452                «const c:ˇ» usize = 3;
 4453            "},
 4454            window,
 4455            cx,
 4456        );
 4457
 4458        editor.tab(&Tab, window, cx);
 4459        assert_text_with_selections(
 4460            &mut editor,
 4461            indoc! {"
 4462                  «aˇ» = 1
 4463                b = 2
 4464
 4465                    «const c:ˇ» usize = 3;
 4466            "},
 4467            cx,
 4468        );
 4469        editor.backtab(&Backtab, window, cx);
 4470        assert_text_with_selections(
 4471            &mut editor,
 4472            indoc! {"
 4473                «aˇ» = 1
 4474                b = 2
 4475
 4476                «const c:ˇ» usize = 3;
 4477            "},
 4478            cx,
 4479        );
 4480
 4481        editor
 4482    });
 4483}
 4484
 4485#[gpui::test]
 4486async fn test_backspace(cx: &mut TestAppContext) {
 4487    init_test(cx, |_| {});
 4488
 4489    let mut cx = EditorTestContext::new(cx).await;
 4490
 4491    // Basic backspace
 4492    cx.set_state(indoc! {"
 4493        onˇe two three
 4494        fou«rˇ» five six
 4495        seven «ˇeight nine
 4496        »ten
 4497    "});
 4498    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4499    cx.assert_editor_state(indoc! {"
 4500        oˇe two three
 4501        fouˇ five six
 4502        seven ˇten
 4503    "});
 4504
 4505    // Test backspace inside and around indents
 4506    cx.set_state(indoc! {"
 4507        zero
 4508            ˇone
 4509                ˇtwo
 4510            ˇ ˇ ˇ  three
 4511        ˇ  ˇ  four
 4512    "});
 4513    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4514    cx.assert_editor_state(indoc! {"
 4515        zero
 4516        ˇone
 4517            ˇtwo
 4518        ˇ  threeˇ  four
 4519    "});
 4520}
 4521
 4522#[gpui::test]
 4523async fn test_delete(cx: &mut TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    let mut cx = EditorTestContext::new(cx).await;
 4527    cx.set_state(indoc! {"
 4528        onˇe two three
 4529        fou«rˇ» five six
 4530        seven «ˇeight nine
 4531        »ten
 4532    "});
 4533    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4534    cx.assert_editor_state(indoc! {"
 4535        onˇ two three
 4536        fouˇ five six
 4537        seven ˇten
 4538    "});
 4539}
 4540
 4541#[gpui::test]
 4542fn test_delete_line(cx: &mut TestAppContext) {
 4543    init_test(cx, |_| {});
 4544
 4545    let editor = cx.add_window(|window, cx| {
 4546        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4547        build_editor(buffer, window, cx)
 4548    });
 4549    _ = editor.update(cx, |editor, window, cx| {
 4550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4551            s.select_display_ranges([
 4552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4553                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4554                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4555            ])
 4556        });
 4557        editor.delete_line(&DeleteLine, window, cx);
 4558        assert_eq!(editor.display_text(cx), "ghi");
 4559        assert_eq!(
 4560            display_ranges(editor, cx),
 4561            vec![
 4562                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4564            ]
 4565        );
 4566    });
 4567
 4568    let editor = cx.add_window(|window, cx| {
 4569        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4570        build_editor(buffer, window, cx)
 4571    });
 4572    _ = editor.update(cx, |editor, window, cx| {
 4573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4574            s.select_display_ranges([
 4575                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4576            ])
 4577        });
 4578        editor.delete_line(&DeleteLine, window, cx);
 4579        assert_eq!(editor.display_text(cx), "ghi\n");
 4580        assert_eq!(
 4581            display_ranges(editor, cx),
 4582            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4583        );
 4584    });
 4585
 4586    let editor = cx.add_window(|window, cx| {
 4587        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4588        build_editor(buffer, window, cx)
 4589    });
 4590    _ = editor.update(cx, |editor, window, cx| {
 4591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4592            s.select_display_ranges([
 4593                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4594            ])
 4595        });
 4596        editor.delete_line(&DeleteLine, window, cx);
 4597        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4598        assert_eq!(
 4599            display_ranges(editor, cx),
 4600            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4601        );
 4602    });
 4603}
 4604
 4605#[gpui::test]
 4606fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4607    init_test(cx, |_| {});
 4608
 4609    cx.add_window(|window, cx| {
 4610        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4611        let mut editor = build_editor(buffer.clone(), window, cx);
 4612        let buffer = buffer.read(cx).as_singleton().unwrap();
 4613
 4614        assert_eq!(
 4615            editor
 4616                .selections
 4617                .ranges::<Point>(&editor.display_snapshot(cx)),
 4618            &[Point::new(0, 0)..Point::new(0, 0)]
 4619        );
 4620
 4621        // When on single line, replace newline at end by space
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4624        assert_eq!(
 4625            editor
 4626                .selections
 4627                .ranges::<Point>(&editor.display_snapshot(cx)),
 4628            &[Point::new(0, 3)..Point::new(0, 3)]
 4629        );
 4630
 4631        // When multiple lines are selected, remove newlines that are spanned by the selection
 4632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4633            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4634        });
 4635        editor.join_lines(&JoinLines, window, cx);
 4636        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4637        assert_eq!(
 4638            editor
 4639                .selections
 4640                .ranges::<Point>(&editor.display_snapshot(cx)),
 4641            &[Point::new(0, 11)..Point::new(0, 11)]
 4642        );
 4643
 4644        // Undo should be transactional
 4645        editor.undo(&Undo, window, cx);
 4646        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4647        assert_eq!(
 4648            editor
 4649                .selections
 4650                .ranges::<Point>(&editor.display_snapshot(cx)),
 4651            &[Point::new(0, 5)..Point::new(2, 2)]
 4652        );
 4653
 4654        // When joining an empty line don't insert a space
 4655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4656            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4657        });
 4658        editor.join_lines(&JoinLines, window, cx);
 4659        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4660        assert_eq!(
 4661            editor
 4662                .selections
 4663                .ranges::<Point>(&editor.display_snapshot(cx)),
 4664            [Point::new(2, 3)..Point::new(2, 3)]
 4665        );
 4666
 4667        // We can remove trailing newlines
 4668        editor.join_lines(&JoinLines, window, cx);
 4669        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4670        assert_eq!(
 4671            editor
 4672                .selections
 4673                .ranges::<Point>(&editor.display_snapshot(cx)),
 4674            [Point::new(2, 3)..Point::new(2, 3)]
 4675        );
 4676
 4677        // We don't blow up on the last line
 4678        editor.join_lines(&JoinLines, window, cx);
 4679        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4680        assert_eq!(
 4681            editor
 4682                .selections
 4683                .ranges::<Point>(&editor.display_snapshot(cx)),
 4684            [Point::new(2, 3)..Point::new(2, 3)]
 4685        );
 4686
 4687        // reset to test indentation
 4688        editor.buffer.update(cx, |buffer, cx| {
 4689            buffer.edit(
 4690                [
 4691                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4692                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4693                ],
 4694                None,
 4695                cx,
 4696            )
 4697        });
 4698
 4699        // We remove any leading spaces
 4700        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4702            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4703        });
 4704        editor.join_lines(&JoinLines, window, cx);
 4705        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4706
 4707        // We don't insert a space for a line containing only spaces
 4708        editor.join_lines(&JoinLines, window, cx);
 4709        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4710
 4711        // We ignore any leading tabs
 4712        editor.join_lines(&JoinLines, window, cx);
 4713        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4714
 4715        editor
 4716    });
 4717}
 4718
 4719#[gpui::test]
 4720fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4721    init_test(cx, |_| {});
 4722
 4723    cx.add_window(|window, cx| {
 4724        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4725        let mut editor = build_editor(buffer.clone(), window, cx);
 4726        let buffer = buffer.read(cx).as_singleton().unwrap();
 4727
 4728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4729            s.select_ranges([
 4730                Point::new(0, 2)..Point::new(1, 1),
 4731                Point::new(1, 2)..Point::new(1, 2),
 4732                Point::new(3, 1)..Point::new(3, 2),
 4733            ])
 4734        });
 4735
 4736        editor.join_lines(&JoinLines, window, cx);
 4737        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4738
 4739        assert_eq!(
 4740            editor
 4741                .selections
 4742                .ranges::<Point>(&editor.display_snapshot(cx)),
 4743            [
 4744                Point::new(0, 7)..Point::new(0, 7),
 4745                Point::new(1, 3)..Point::new(1, 3)
 4746            ]
 4747        );
 4748        editor
 4749    });
 4750}
 4751
 4752#[gpui::test]
 4753async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4754    init_test(cx, |_| {});
 4755
 4756    let mut cx = EditorTestContext::new(cx).await;
 4757
 4758    let diff_base = r#"
 4759        Line 0
 4760        Line 1
 4761        Line 2
 4762        Line 3
 4763        "#
 4764    .unindent();
 4765
 4766    cx.set_state(
 4767        &r#"
 4768        ˇLine 0
 4769        Line 1
 4770        Line 2
 4771        Line 3
 4772        "#
 4773        .unindent(),
 4774    );
 4775
 4776    cx.set_head_text(&diff_base);
 4777    executor.run_until_parked();
 4778
 4779    // Join lines
 4780    cx.update_editor(|editor, window, cx| {
 4781        editor.join_lines(&JoinLines, window, cx);
 4782    });
 4783    executor.run_until_parked();
 4784
 4785    cx.assert_editor_state(
 4786        &r#"
 4787        Line 0ˇ Line 1
 4788        Line 2
 4789        Line 3
 4790        "#
 4791        .unindent(),
 4792    );
 4793    // Join again
 4794    cx.update_editor(|editor, window, cx| {
 4795        editor.join_lines(&JoinLines, window, cx);
 4796    });
 4797    executor.run_until_parked();
 4798
 4799    cx.assert_editor_state(
 4800        &r#"
 4801        Line 0 Line 1ˇ Line 2
 4802        Line 3
 4803        "#
 4804        .unindent(),
 4805    );
 4806}
 4807
 4808#[gpui::test]
 4809async fn test_custom_newlines_cause_no_false_positive_diffs(
 4810    executor: BackgroundExecutor,
 4811    cx: &mut TestAppContext,
 4812) {
 4813    init_test(cx, |_| {});
 4814    let mut cx = EditorTestContext::new(cx).await;
 4815    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4816    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4817    executor.run_until_parked();
 4818
 4819    cx.update_editor(|editor, window, cx| {
 4820        let snapshot = editor.snapshot(window, cx);
 4821        assert_eq!(
 4822            snapshot
 4823                .buffer_snapshot()
 4824                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4825                .collect::<Vec<_>>(),
 4826            Vec::new(),
 4827            "Should not have any diffs for files with custom newlines"
 4828        );
 4829    });
 4830}
 4831
 4832#[gpui::test]
 4833async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4834    init_test(cx, |_| {});
 4835
 4836    let mut cx = EditorTestContext::new(cx).await;
 4837
 4838    // Test sort_lines_case_insensitive()
 4839    cx.set_state(indoc! {"
 4840        «z
 4841        y
 4842        x
 4843        Z
 4844        Y
 4845        Xˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «x
 4852        X
 4853        y
 4854        Y
 4855        z
 4856        Zˇ»
 4857    "});
 4858
 4859    // Test sort_lines_by_length()
 4860    //
 4861    // Demonstrates:
 4862    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4863    // - sort is stable
 4864    cx.set_state(indoc! {"
 4865        «123
 4866        æ
 4867        12
 4868 4869        1
 4870        æˇ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «æ
 4875 4876        1
 4877        æ
 4878        12
 4879        123ˇ»
 4880    "});
 4881
 4882    // Test reverse_lines()
 4883    cx.set_state(indoc! {"
 4884        «5
 4885        4
 4886        3
 4887        2
 4888        1ˇ»
 4889    "});
 4890    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4891    cx.assert_editor_state(indoc! {"
 4892        «1
 4893        2
 4894        3
 4895        4
 4896        5ˇ»
 4897    "});
 4898
 4899    // Skip testing shuffle_line()
 4900
 4901    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4902    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4903
 4904    // Don't manipulate when cursor is on single line, but expand the selection
 4905    cx.set_state(indoc! {"
 4906        ddˇdd
 4907        ccc
 4908        bb
 4909        a
 4910    "});
 4911    cx.update_editor(|e, window, cx| {
 4912        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4913    });
 4914    cx.assert_editor_state(indoc! {"
 4915        «ddddˇ»
 4916        ccc
 4917        bb
 4918        a
 4919    "});
 4920
 4921    // Basic manipulate case
 4922    // Start selection moves to column 0
 4923    // End of selection shrinks to fit shorter line
 4924    cx.set_state(indoc! {"
 4925        dd«d
 4926        ccc
 4927        bb
 4928        aaaaaˇ»
 4929    "});
 4930    cx.update_editor(|e, window, cx| {
 4931        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4932    });
 4933    cx.assert_editor_state(indoc! {"
 4934        «aaaaa
 4935        bb
 4936        ccc
 4937        dddˇ»
 4938    "});
 4939
 4940    // Manipulate case with newlines
 4941    cx.set_state(indoc! {"
 4942        dd«d
 4943        ccc
 4944
 4945        bb
 4946        aaaaa
 4947
 4948        ˇ»
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «
 4955
 4956        aaaaa
 4957        bb
 4958        ccc
 4959        dddˇ»
 4960
 4961    "});
 4962
 4963    // Adding new line
 4964    cx.set_state(indoc! {"
 4965        aa«a
 4966        bbˇ»b
 4967    "});
 4968    cx.update_editor(|e, window, cx| {
 4969        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4970    });
 4971    cx.assert_editor_state(indoc! {"
 4972        «aaa
 4973        bbb
 4974        added_lineˇ»
 4975    "});
 4976
 4977    // Removing line
 4978    cx.set_state(indoc! {"
 4979        aa«a
 4980        bbbˇ»
 4981    "});
 4982    cx.update_editor(|e, window, cx| {
 4983        e.manipulate_immutable_lines(window, cx, |lines| {
 4984            lines.pop();
 4985        })
 4986    });
 4987    cx.assert_editor_state(indoc! {"
 4988        «aaaˇ»
 4989    "});
 4990
 4991    // Removing all lines
 4992    cx.set_state(indoc! {"
 4993        aa«a
 4994        bbbˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| {
 4997        e.manipulate_immutable_lines(window, cx, |lines| {
 4998            lines.drain(..);
 4999        })
 5000    });
 5001    cx.assert_editor_state(indoc! {"
 5002        ˇ
 5003    "});
 5004}
 5005
 5006#[gpui::test]
 5007async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5008    init_test(cx, |_| {});
 5009
 5010    let mut cx = EditorTestContext::new(cx).await;
 5011
 5012    // Consider continuous selection as single selection
 5013    cx.set_state(indoc! {"
 5014        Aaa«aa
 5015        cˇ»c«c
 5016        bb
 5017        aaaˇ»aa
 5018    "});
 5019    cx.update_editor(|e, window, cx| {
 5020        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5021    });
 5022    cx.assert_editor_state(indoc! {"
 5023        «Aaaaa
 5024        ccc
 5025        bb
 5026        aaaaaˇ»
 5027    "});
 5028
 5029    cx.set_state(indoc! {"
 5030        Aaa«aa
 5031        cˇ»c«c
 5032        bb
 5033        aaaˇ»aa
 5034    "});
 5035    cx.update_editor(|e, window, cx| {
 5036        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5037    });
 5038    cx.assert_editor_state(indoc! {"
 5039        «Aaaaa
 5040        ccc
 5041        bbˇ»
 5042    "});
 5043
 5044    // Consider non continuous selection as distinct dedup operations
 5045    cx.set_state(indoc! {"
 5046        «aaaaa
 5047        bb
 5048        aaaaa
 5049        aaaaaˇ»
 5050
 5051        aaa«aaˇ»
 5052    "});
 5053    cx.update_editor(|e, window, cx| {
 5054        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5055    });
 5056    cx.assert_editor_state(indoc! {"
 5057        «aaaaa
 5058        bbˇ»
 5059
 5060        «aaaaaˇ»
 5061    "});
 5062}
 5063
 5064#[gpui::test]
 5065async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5066    init_test(cx, |_| {});
 5067
 5068    let mut cx = EditorTestContext::new(cx).await;
 5069
 5070    cx.set_state(indoc! {"
 5071        «Aaa
 5072        aAa
 5073        Aaaˇ»
 5074    "});
 5075    cx.update_editor(|e, window, cx| {
 5076        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5077    });
 5078    cx.assert_editor_state(indoc! {"
 5079        «Aaa
 5080        aAaˇ»
 5081    "});
 5082
 5083    cx.set_state(indoc! {"
 5084        «Aaa
 5085        aAa
 5086        aaAˇ»
 5087    "});
 5088    cx.update_editor(|e, window, cx| {
 5089        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5090    });
 5091    cx.assert_editor_state(indoc! {"
 5092        «Aaaˇ»
 5093    "});
 5094}
 5095
 5096#[gpui::test]
 5097async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5098    init_test(cx, |_| {});
 5099
 5100    let mut cx = EditorTestContext::new(cx).await;
 5101
 5102    let js_language = Arc::new(Language::new(
 5103        LanguageConfig {
 5104            name: "JavaScript".into(),
 5105            wrap_characters: Some(language::WrapCharactersConfig {
 5106                start_prefix: "<".into(),
 5107                start_suffix: ">".into(),
 5108                end_prefix: "</".into(),
 5109                end_suffix: ">".into(),
 5110            }),
 5111            ..LanguageConfig::default()
 5112        },
 5113        None,
 5114    ));
 5115
 5116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5117
 5118    cx.set_state(indoc! {"
 5119        «testˇ»
 5120    "});
 5121    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5122    cx.assert_editor_state(indoc! {"
 5123        <«ˇ»>test</«ˇ»>
 5124    "});
 5125
 5126    cx.set_state(indoc! {"
 5127        «test
 5128         testˇ»
 5129    "});
 5130    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5131    cx.assert_editor_state(indoc! {"
 5132        <«ˇ»>test
 5133         test</«ˇ»>
 5134    "});
 5135
 5136    cx.set_state(indoc! {"
 5137        teˇst
 5138    "});
 5139    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5140    cx.assert_editor_state(indoc! {"
 5141        te<«ˇ»></«ˇ»>st
 5142    "});
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5147    init_test(cx, |_| {});
 5148
 5149    let mut cx = EditorTestContext::new(cx).await;
 5150
 5151    let js_language = Arc::new(Language::new(
 5152        LanguageConfig {
 5153            name: "JavaScript".into(),
 5154            wrap_characters: Some(language::WrapCharactersConfig {
 5155                start_prefix: "<".into(),
 5156                start_suffix: ">".into(),
 5157                end_prefix: "</".into(),
 5158                end_suffix: ">".into(),
 5159            }),
 5160            ..LanguageConfig::default()
 5161        },
 5162        None,
 5163    ));
 5164
 5165    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5166
 5167    cx.set_state(indoc! {"
 5168        «testˇ»
 5169        «testˇ» «testˇ»
 5170        «testˇ»
 5171    "});
 5172    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5173    cx.assert_editor_state(indoc! {"
 5174        <«ˇ»>test</«ˇ»>
 5175        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5176        <«ˇ»>test</«ˇ»>
 5177    "});
 5178
 5179    cx.set_state(indoc! {"
 5180        «test
 5181         testˇ»
 5182        «test
 5183         testˇ»
 5184    "});
 5185    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5186    cx.assert_editor_state(indoc! {"
 5187        <«ˇ»>test
 5188         test</«ˇ»>
 5189        <«ˇ»>test
 5190         test</«ˇ»>
 5191    "});
 5192}
 5193
 5194#[gpui::test]
 5195async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5196    init_test(cx, |_| {});
 5197
 5198    let mut cx = EditorTestContext::new(cx).await;
 5199
 5200    let plaintext_language = Arc::new(Language::new(
 5201        LanguageConfig {
 5202            name: "Plain Text".into(),
 5203            ..LanguageConfig::default()
 5204        },
 5205        None,
 5206    ));
 5207
 5208    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5209
 5210    cx.set_state(indoc! {"
 5211        «testˇ»
 5212    "});
 5213    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5214    cx.assert_editor_state(indoc! {"
 5215      «testˇ»
 5216    "});
 5217}
 5218
 5219#[gpui::test]
 5220async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5221    init_test(cx, |_| {});
 5222
 5223    let mut cx = EditorTestContext::new(cx).await;
 5224
 5225    // Manipulate with multiple selections on a single line
 5226    cx.set_state(indoc! {"
 5227        dd«dd
 5228        cˇ»c«c
 5229        bb
 5230        aaaˇ»aa
 5231    "});
 5232    cx.update_editor(|e, window, cx| {
 5233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «aaaaa
 5237        bb
 5238        ccc
 5239        ddddˇ»
 5240    "});
 5241
 5242    // Manipulate with multiple disjoin selections
 5243    cx.set_state(indoc! {"
 5244 5245        4
 5246        3
 5247        2
 5248        1ˇ»
 5249
 5250        dd«dd
 5251        ccc
 5252        bb
 5253        aaaˇ»aa
 5254    "});
 5255    cx.update_editor(|e, window, cx| {
 5256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5257    });
 5258    cx.assert_editor_state(indoc! {"
 5259        «1
 5260        2
 5261        3
 5262        4
 5263        5ˇ»
 5264
 5265        «aaaaa
 5266        bb
 5267        ccc
 5268        ddddˇ»
 5269    "});
 5270
 5271    // Adding lines on each selection
 5272    cx.set_state(indoc! {"
 5273 5274        1ˇ»
 5275
 5276        bb«bb
 5277        aaaˇ»aa
 5278    "});
 5279    cx.update_editor(|e, window, cx| {
 5280        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5281    });
 5282    cx.assert_editor_state(indoc! {"
 5283        «2
 5284        1
 5285        added lineˇ»
 5286
 5287        «bbbb
 5288        aaaaa
 5289        added lineˇ»
 5290    "});
 5291
 5292    // Removing lines on each selection
 5293    cx.set_state(indoc! {"
 5294 5295        1ˇ»
 5296
 5297        bb«bb
 5298        aaaˇ»aa
 5299    "});
 5300    cx.update_editor(|e, window, cx| {
 5301        e.manipulate_immutable_lines(window, cx, |lines| {
 5302            lines.pop();
 5303        })
 5304    });
 5305    cx.assert_editor_state(indoc! {"
 5306        «2ˇ»
 5307
 5308        «bbbbˇ»
 5309    "});
 5310}
 5311
 5312#[gpui::test]
 5313async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5314    init_test(cx, |settings| {
 5315        settings.defaults.tab_size = NonZeroU32::new(3)
 5316    });
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319
 5320    // MULTI SELECTION
 5321    // Ln.1 "«" tests empty lines
 5322    // Ln.9 tests just leading whitespace
 5323    cx.set_state(indoc! {"
 5324        «
 5325        abc                 // No indentationˇ»
 5326        «\tabc              // 1 tabˇ»
 5327        \t\tabc «      ˇ»   // 2 tabs
 5328        \t ab«c             // Tab followed by space
 5329         \tabc              // Space followed by tab (3 spaces should be the result)
 5330        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5331           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5332        \t
 5333        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| {
 5336        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5337    });
 5338    cx.assert_editor_state(
 5339        indoc! {"
 5340            «
 5341            abc                 // No indentation
 5342               abc              // 1 tab
 5343                  abc          // 2 tabs
 5344                abc             // Tab followed by space
 5345               abc              // Space followed by tab (3 spaces should be the result)
 5346                           abc   // Mixed indentation (tab conversion depends on the column)
 5347               abc         // Already space indented
 5348               ·
 5349               abc\tdef          // Only the leading tab is manipulatedˇ»
 5350        "}
 5351        .replace("·", "")
 5352        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5353    );
 5354
 5355    // Test on just a few lines, the others should remain unchanged
 5356    // Only lines (3, 5, 10, 11) should change
 5357    cx.set_state(
 5358        indoc! {"
 5359            ·
 5360            abc                 // No indentation
 5361            \tabcˇ               // 1 tab
 5362            \t\tabc             // 2 tabs
 5363            \t abcˇ              // Tab followed by space
 5364             \tabc              // Space followed by tab (3 spaces should be the result)
 5365            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5366               abc              // Already space indented
 5367            «\t
 5368            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5369        "}
 5370        .replace("·", "")
 5371        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5372    );
 5373    cx.update_editor(|e, window, cx| {
 5374        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5375    });
 5376    cx.assert_editor_state(
 5377        indoc! {"
 5378            ·
 5379            abc                 // No indentation
 5380            «   abc               // 1 tabˇ»
 5381            \t\tabc             // 2 tabs
 5382            «    abc              // Tab followed by spaceˇ»
 5383             \tabc              // Space followed by tab (3 spaces should be the result)
 5384            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5385               abc              // Already space indented
 5386            «   ·
 5387               abc\tdef          // Only the leading tab is manipulatedˇ»
 5388        "}
 5389        .replace("·", "")
 5390        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5391    );
 5392
 5393    // SINGLE SELECTION
 5394    // Ln.1 "«" tests empty lines
 5395    // Ln.9 tests just leading whitespace
 5396    cx.set_state(indoc! {"
 5397        «
 5398        abc                 // No indentation
 5399        \tabc               // 1 tab
 5400        \t\tabc             // 2 tabs
 5401        \t abc              // Tab followed by space
 5402         \tabc              // Space followed by tab (3 spaces should be the result)
 5403        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5404           abc              // Already space indented
 5405        \t
 5406        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5407    "});
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5410    });
 5411    cx.assert_editor_state(
 5412        indoc! {"
 5413            «
 5414            abc                 // No indentation
 5415               abc               // 1 tab
 5416                  abc             // 2 tabs
 5417                abc              // Tab followed by space
 5418               abc              // Space followed by tab (3 spaces should be the result)
 5419                           abc   // Mixed indentation (tab conversion depends on the column)
 5420               abc              // Already space indented
 5421               ·
 5422               abc\tdef          // Only the leading tab is manipulatedˇ»
 5423        "}
 5424        .replace("·", "")
 5425        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5426    );
 5427}
 5428
 5429#[gpui::test]
 5430async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5431    init_test(cx, |settings| {
 5432        settings.defaults.tab_size = NonZeroU32::new(3)
 5433    });
 5434
 5435    let mut cx = EditorTestContext::new(cx).await;
 5436
 5437    // MULTI SELECTION
 5438    // Ln.1 "«" tests empty lines
 5439    // Ln.11 tests just leading whitespace
 5440    cx.set_state(indoc! {"
 5441        «
 5442        abˇ»ˇc                 // No indentation
 5443         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5444          abc  «             // 2 spaces (< 3 so dont convert)
 5445           abc              // 3 spaces (convert)
 5446             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5447        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5448        «\t abc              // Tab followed by space
 5449         \tabc              // Space followed by tab (should be consumed due to tab)
 5450        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5451           \tˇ»  «\t
 5452           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5453    "});
 5454    cx.update_editor(|e, window, cx| {
 5455        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5456    });
 5457    cx.assert_editor_state(indoc! {"
 5458        «
 5459        abc                 // No indentation
 5460         abc                // 1 space (< 3 so dont convert)
 5461          abc               // 2 spaces (< 3 so dont convert)
 5462        \tabc              // 3 spaces (convert)
 5463        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5464        \t\t\tabc           // Already tab indented
 5465        \t abc              // Tab followed by space
 5466        \tabc              // Space followed by tab (should be consumed due to tab)
 5467        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5468        \t\t\t
 5469        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5470    "});
 5471
 5472    // Test on just a few lines, the other should remain unchanged
 5473    // Only lines (4, 8, 11, 12) should change
 5474    cx.set_state(
 5475        indoc! {"
 5476            ·
 5477            abc                 // No indentation
 5478             abc                // 1 space (< 3 so dont convert)
 5479              abc               // 2 spaces (< 3 so dont convert)
 5480            «   abc              // 3 spaces (convert)ˇ»
 5481                 abc            // 5 spaces (1 tab + 2 spaces)
 5482            \t\t\tabc           // Already tab indented
 5483            \t abc              // Tab followed by space
 5484             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5485               \t\t  \tabc      // Mixed indentation
 5486            \t \t  \t   \tabc   // Mixed indentation
 5487               \t  \tˇ
 5488            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5489        "}
 5490        .replace("·", "")
 5491        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5492    );
 5493    cx.update_editor(|e, window, cx| {
 5494        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5495    });
 5496    cx.assert_editor_state(
 5497        indoc! {"
 5498            ·
 5499            abc                 // No indentation
 5500             abc                // 1 space (< 3 so dont convert)
 5501              abc               // 2 spaces (< 3 so dont convert)
 5502            «\tabc              // 3 spaces (convert)ˇ»
 5503                 abc            // 5 spaces (1 tab + 2 spaces)
 5504            \t\t\tabc           // Already tab indented
 5505            \t abc              // Tab followed by space
 5506            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5507               \t\t  \tabc      // Mixed indentation
 5508            \t \t  \t   \tabc   // Mixed indentation
 5509            «\t\t\t
 5510            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5511        "}
 5512        .replace("·", "")
 5513        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5514    );
 5515
 5516    // SINGLE SELECTION
 5517    // Ln.1 "«" tests empty lines
 5518    // Ln.11 tests just leading whitespace
 5519    cx.set_state(indoc! {"
 5520        «
 5521        abc                 // No indentation
 5522         abc                // 1 space (< 3 so dont convert)
 5523          abc               // 2 spaces (< 3 so dont convert)
 5524           abc              // 3 spaces (convert)
 5525             abc            // 5 spaces (1 tab + 2 spaces)
 5526        \t\t\tabc           // Already tab indented
 5527        \t abc              // Tab followed by space
 5528         \tabc              // Space followed by tab (should be consumed due to tab)
 5529        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5530           \t  \t
 5531           abc   \t         // Only the leading spaces should be convertedˇ»
 5532    "});
 5533    cx.update_editor(|e, window, cx| {
 5534        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5535    });
 5536    cx.assert_editor_state(indoc! {"
 5537        «
 5538        abc                 // No indentation
 5539         abc                // 1 space (< 3 so dont convert)
 5540          abc               // 2 spaces (< 3 so dont convert)
 5541        \tabc              // 3 spaces (convert)
 5542        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5543        \t\t\tabc           // Already tab indented
 5544        \t abc              // Tab followed by space
 5545        \tabc              // Space followed by tab (should be consumed due to tab)
 5546        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5547        \t\t\t
 5548        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5549    "});
 5550}
 5551
 5552#[gpui::test]
 5553async fn test_toggle_case(cx: &mut TestAppContext) {
 5554    init_test(cx, |_| {});
 5555
 5556    let mut cx = EditorTestContext::new(cx).await;
 5557
 5558    // If all lower case -> upper case
 5559    cx.set_state(indoc! {"
 5560        «hello worldˇ»
 5561    "});
 5562    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5563    cx.assert_editor_state(indoc! {"
 5564        «HELLO WORLDˇ»
 5565    "});
 5566
 5567    // If all upper case -> lower case
 5568    cx.set_state(indoc! {"
 5569        «HELLO WORLDˇ»
 5570    "});
 5571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5572    cx.assert_editor_state(indoc! {"
 5573        «hello worldˇ»
 5574    "});
 5575
 5576    // If any upper case characters are identified -> lower case
 5577    // This matches JetBrains IDEs
 5578    cx.set_state(indoc! {"
 5579        «hEllo worldˇ»
 5580    "});
 5581    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5582    cx.assert_editor_state(indoc! {"
 5583        «hello worldˇ»
 5584    "});
 5585}
 5586
 5587#[gpui::test]
 5588async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5589    init_test(cx, |_| {});
 5590
 5591    let mut cx = EditorTestContext::new(cx).await;
 5592
 5593    cx.set_state(indoc! {"
 5594        «implement-windows-supportˇ»
 5595    "});
 5596    cx.update_editor(|e, window, cx| {
 5597        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5598    });
 5599    cx.assert_editor_state(indoc! {"
 5600        «Implement windows supportˇ»
 5601    "});
 5602}
 5603
 5604#[gpui::test]
 5605async fn test_manipulate_text(cx: &mut TestAppContext) {
 5606    init_test(cx, |_| {});
 5607
 5608    let mut cx = EditorTestContext::new(cx).await;
 5609
 5610    // Test convert_to_upper_case()
 5611    cx.set_state(indoc! {"
 5612        «hello worldˇ»
 5613    "});
 5614    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5615    cx.assert_editor_state(indoc! {"
 5616        «HELLO WORLDˇ»
 5617    "});
 5618
 5619    // Test convert_to_lower_case()
 5620    cx.set_state(indoc! {"
 5621        «HELLO WORLDˇ»
 5622    "});
 5623    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5624    cx.assert_editor_state(indoc! {"
 5625        «hello worldˇ»
 5626    "});
 5627
 5628    // Test multiple line, single selection case
 5629    cx.set_state(indoc! {"
 5630        «The quick brown
 5631        fox jumps over
 5632        the lazy dogˇ»
 5633    "});
 5634    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5635    cx.assert_editor_state(indoc! {"
 5636        «The Quick Brown
 5637        Fox Jumps Over
 5638        The Lazy Dogˇ»
 5639    "});
 5640
 5641    // Test multiple line, single selection case
 5642    cx.set_state(indoc! {"
 5643        «The quick brown
 5644        fox jumps over
 5645        the lazy dogˇ»
 5646    "});
 5647    cx.update_editor(|e, window, cx| {
 5648        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5649    });
 5650    cx.assert_editor_state(indoc! {"
 5651        «TheQuickBrown
 5652        FoxJumpsOver
 5653        TheLazyDogˇ»
 5654    "});
 5655
 5656    // From here on out, test more complex cases of manipulate_text()
 5657
 5658    // Test no selection case - should affect words cursors are in
 5659    // Cursor at beginning, middle, and end of word
 5660    cx.set_state(indoc! {"
 5661        ˇhello big beauˇtiful worldˇ
 5662    "});
 5663    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5664    cx.assert_editor_state(indoc! {"
 5665        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5666    "});
 5667
 5668    // Test multiple selections on a single line and across multiple lines
 5669    cx.set_state(indoc! {"
 5670        «Theˇ» quick «brown
 5671        foxˇ» jumps «overˇ»
 5672        the «lazyˇ» dog
 5673    "});
 5674    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5675    cx.assert_editor_state(indoc! {"
 5676        «THEˇ» quick «BROWN
 5677        FOXˇ» jumps «OVERˇ»
 5678        the «LAZYˇ» dog
 5679    "});
 5680
 5681    // Test case where text length grows
 5682    cx.set_state(indoc! {"
 5683        «tschüߡ»
 5684    "});
 5685    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5686    cx.assert_editor_state(indoc! {"
 5687        «TSCHÜSSˇ»
 5688    "});
 5689
 5690    // Test to make sure we don't crash when text shrinks
 5691    cx.set_state(indoc! {"
 5692        aaa_bbbˇ
 5693    "});
 5694    cx.update_editor(|e, window, cx| {
 5695        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5696    });
 5697    cx.assert_editor_state(indoc! {"
 5698        «aaaBbbˇ»
 5699    "});
 5700
 5701    // Test to make sure we all aware of the fact that each word can grow and shrink
 5702    // Final selections should be aware of this fact
 5703    cx.set_state(indoc! {"
 5704        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5705    "});
 5706    cx.update_editor(|e, window, cx| {
 5707        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5708    });
 5709    cx.assert_editor_state(indoc! {"
 5710        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5711    "});
 5712
 5713    cx.set_state(indoc! {"
 5714        «hElLo, WoRld!ˇ»
 5715    "});
 5716    cx.update_editor(|e, window, cx| {
 5717        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5718    });
 5719    cx.assert_editor_state(indoc! {"
 5720        «HeLlO, wOrLD!ˇ»
 5721    "});
 5722
 5723    // Test selections with `line_mode() = true`.
 5724    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5725    cx.set_state(indoc! {"
 5726        «The quick brown
 5727        fox jumps over
 5728        tˇ»he lazy dog
 5729    "});
 5730    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5731    cx.assert_editor_state(indoc! {"
 5732        «THE QUICK BROWN
 5733        FOX JUMPS OVER
 5734        THE LAZY DOGˇ»
 5735    "});
 5736}
 5737
 5738#[gpui::test]
 5739fn test_duplicate_line(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    let editor = cx.add_window(|window, cx| {
 5743        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5744        build_editor(buffer, window, cx)
 5745    });
 5746    _ = editor.update(cx, |editor, window, cx| {
 5747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5748            s.select_display_ranges([
 5749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5750                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5751                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5752                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5753            ])
 5754        });
 5755        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5756        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5757        assert_eq!(
 5758            display_ranges(editor, cx),
 5759            vec![
 5760                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5762                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5763                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5764            ]
 5765        );
 5766    });
 5767
 5768    let editor = cx.add_window(|window, cx| {
 5769        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5770        build_editor(buffer, window, cx)
 5771    });
 5772    _ = editor.update(cx, |editor, window, cx| {
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_display_ranges([
 5775                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5776                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5777            ])
 5778        });
 5779        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5780        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5781        assert_eq!(
 5782            display_ranges(editor, cx),
 5783            vec![
 5784                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5785                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5786            ]
 5787        );
 5788    });
 5789
 5790    // With `duplicate_line_up` the selections move to the duplicated lines,
 5791    // which are inserted above the original lines
 5792    let editor = cx.add_window(|window, cx| {
 5793        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5794        build_editor(buffer, window, cx)
 5795    });
 5796    _ = editor.update(cx, |editor, window, cx| {
 5797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5798            s.select_display_ranges([
 5799                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5800                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5801                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5802                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5803            ])
 5804        });
 5805        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5806        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5807        assert_eq!(
 5808            display_ranges(editor, cx),
 5809            vec![
 5810                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5811                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5812                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5813                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5814            ]
 5815        );
 5816    });
 5817
 5818    let editor = cx.add_window(|window, cx| {
 5819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5820        build_editor(buffer, window, cx)
 5821    });
 5822    _ = editor.update(cx, |editor, window, cx| {
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_display_ranges([
 5825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5827            ])
 5828        });
 5829        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5830        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5831        assert_eq!(
 5832            display_ranges(editor, cx),
 5833            vec![
 5834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5835                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5836            ]
 5837        );
 5838    });
 5839
 5840    let editor = cx.add_window(|window, cx| {
 5841        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5842        build_editor(buffer, window, cx)
 5843    });
 5844    _ = editor.update(cx, |editor, window, cx| {
 5845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5846            s.select_display_ranges([
 5847                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5848                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5849            ])
 5850        });
 5851        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5852        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5853        assert_eq!(
 5854            display_ranges(editor, cx),
 5855            vec![
 5856                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5857                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5858            ]
 5859        );
 5860    });
 5861}
 5862
 5863#[gpui::test]
 5864async fn test_rotate_selections(cx: &mut TestAppContext) {
 5865    init_test(cx, |_| {});
 5866
 5867    let mut cx = EditorTestContext::new(cx).await;
 5868
 5869    // Rotate text selections (horizontal)
 5870    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5871    cx.update_editor(|e, window, cx| {
 5872        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5873    });
 5874    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5875    cx.update_editor(|e, window, cx| {
 5876        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5877    });
 5878    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5879
 5880    // Rotate text selections (vertical)
 5881    cx.set_state(indoc! {"
 5882        x=«1ˇ»
 5883        y=«2ˇ»
 5884        z=«3ˇ»
 5885    "});
 5886    cx.update_editor(|e, window, cx| {
 5887        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5888    });
 5889    cx.assert_editor_state(indoc! {"
 5890        x=«3ˇ»
 5891        y=«1ˇ»
 5892        z=«2ˇ»
 5893    "});
 5894    cx.update_editor(|e, window, cx| {
 5895        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5896    });
 5897    cx.assert_editor_state(indoc! {"
 5898        x=«1ˇ»
 5899        y=«2ˇ»
 5900        z=«3ˇ»
 5901    "});
 5902
 5903    // Rotate text selections (vertical, different lengths)
 5904    cx.set_state(indoc! {"
 5905        x=\"«ˇ»\"
 5906        y=\"«aˇ»\"
 5907        z=\"«aaˇ»\"
 5908    "});
 5909    cx.update_editor(|e, window, cx| {
 5910        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5911    });
 5912    cx.assert_editor_state(indoc! {"
 5913        x=\"«aaˇ»\"
 5914        y=\"«ˇ»\"
 5915        z=\"«aˇ»\"
 5916    "});
 5917    cx.update_editor(|e, window, cx| {
 5918        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5919    });
 5920    cx.assert_editor_state(indoc! {"
 5921        x=\"«ˇ»\"
 5922        y=\"«aˇ»\"
 5923        z=\"«aaˇ»\"
 5924    "});
 5925
 5926    // Rotate whole lines (cursor positions preserved)
 5927    cx.set_state(indoc! {"
 5928        ˇline123
 5929        liˇne23
 5930        line3ˇ
 5931    "});
 5932    cx.update_editor(|e, window, cx| {
 5933        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5934    });
 5935    cx.assert_editor_state(indoc! {"
 5936        line3ˇ
 5937        ˇline123
 5938        liˇne23
 5939    "});
 5940    cx.update_editor(|e, window, cx| {
 5941        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5942    });
 5943    cx.assert_editor_state(indoc! {"
 5944        ˇline123
 5945        liˇne23
 5946        line3ˇ
 5947    "});
 5948
 5949    // Rotate whole lines, multiple cursors per line (positions preserved)
 5950    cx.set_state(indoc! {"
 5951        ˇliˇne123
 5952        ˇline23
 5953        ˇline3
 5954    "});
 5955    cx.update_editor(|e, window, cx| {
 5956        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5957    });
 5958    cx.assert_editor_state(indoc! {"
 5959        ˇline3
 5960        ˇliˇne123
 5961        ˇline23
 5962    "});
 5963    cx.update_editor(|e, window, cx| {
 5964        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5965    });
 5966    cx.assert_editor_state(indoc! {"
 5967        ˇliˇne123
 5968        ˇline23
 5969        ˇline3
 5970    "});
 5971}
 5972
 5973#[gpui::test]
 5974fn test_move_line_up_down(cx: &mut TestAppContext) {
 5975    init_test(cx, |_| {});
 5976
 5977    let editor = cx.add_window(|window, cx| {
 5978        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5979        build_editor(buffer, window, cx)
 5980    });
 5981    _ = editor.update(cx, |editor, window, cx| {
 5982        editor.fold_creases(
 5983            vec![
 5984                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5985                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5986                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5987            ],
 5988            true,
 5989            window,
 5990            cx,
 5991        );
 5992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5993            s.select_display_ranges([
 5994                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5995                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5996                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5997                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5998            ])
 5999        });
 6000        assert_eq!(
 6001            editor.display_text(cx),
 6002            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6003        );
 6004
 6005        editor.move_line_up(&MoveLineUp, window, cx);
 6006        assert_eq!(
 6007            editor.display_text(cx),
 6008            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6009        );
 6010        assert_eq!(
 6011            display_ranges(editor, cx),
 6012            vec![
 6013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6014                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6015                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6016                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6017            ]
 6018        );
 6019    });
 6020
 6021    _ = editor.update(cx, |editor, window, cx| {
 6022        editor.move_line_down(&MoveLineDown, window, cx);
 6023        assert_eq!(
 6024            editor.display_text(cx),
 6025            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6026        );
 6027        assert_eq!(
 6028            display_ranges(editor, cx),
 6029            vec![
 6030                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6031                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6032                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6033                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6034            ]
 6035        );
 6036    });
 6037
 6038    _ = editor.update(cx, |editor, window, cx| {
 6039        editor.move_line_down(&MoveLineDown, window, cx);
 6040        assert_eq!(
 6041            editor.display_text(cx),
 6042            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6043        );
 6044        assert_eq!(
 6045            display_ranges(editor, cx),
 6046            vec![
 6047                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6048                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6049                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6050                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6051            ]
 6052        );
 6053    });
 6054
 6055    _ = editor.update(cx, |editor, window, cx| {
 6056        editor.move_line_up(&MoveLineUp, window, cx);
 6057        assert_eq!(
 6058            editor.display_text(cx),
 6059            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6060        );
 6061        assert_eq!(
 6062            display_ranges(editor, cx),
 6063            vec![
 6064                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6065                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6066                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6067                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6068            ]
 6069        );
 6070    });
 6071}
 6072
 6073#[gpui::test]
 6074fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6075    init_test(cx, |_| {});
 6076    let editor = cx.add_window(|window, cx| {
 6077        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6078        build_editor(buffer, window, cx)
 6079    });
 6080    _ = editor.update(cx, |editor, window, cx| {
 6081        editor.fold_creases(
 6082            vec![Crease::simple(
 6083                Point::new(6, 4)..Point::new(7, 4),
 6084                FoldPlaceholder::test(),
 6085            )],
 6086            true,
 6087            window,
 6088            cx,
 6089        );
 6090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6091            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6092        });
 6093        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6094        editor.move_line_up(&MoveLineUp, window, cx);
 6095        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6096        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6097    });
 6098}
 6099
 6100#[gpui::test]
 6101fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6102    init_test(cx, |_| {});
 6103
 6104    let editor = cx.add_window(|window, cx| {
 6105        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6106        build_editor(buffer, window, cx)
 6107    });
 6108    _ = editor.update(cx, |editor, window, cx| {
 6109        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6110        editor.insert_blocks(
 6111            [BlockProperties {
 6112                style: BlockStyle::Fixed,
 6113                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6114                height: Some(1),
 6115                render: Arc::new(|_| div().into_any()),
 6116                priority: 0,
 6117            }],
 6118            Some(Autoscroll::fit()),
 6119            cx,
 6120        );
 6121        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6122            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6123        });
 6124        editor.move_line_down(&MoveLineDown, window, cx);
 6125    });
 6126}
 6127
 6128#[gpui::test]
 6129async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6130    init_test(cx, |_| {});
 6131
 6132    let mut cx = EditorTestContext::new(cx).await;
 6133    cx.set_state(
 6134        &"
 6135            ˇzero
 6136            one
 6137            two
 6138            three
 6139            four
 6140            five
 6141        "
 6142        .unindent(),
 6143    );
 6144
 6145    // Create a four-line block that replaces three lines of text.
 6146    cx.update_editor(|editor, window, cx| {
 6147        let snapshot = editor.snapshot(window, cx);
 6148        let snapshot = &snapshot.buffer_snapshot();
 6149        let placement = BlockPlacement::Replace(
 6150            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6151        );
 6152        editor.insert_blocks(
 6153            [BlockProperties {
 6154                placement,
 6155                height: Some(4),
 6156                style: BlockStyle::Sticky,
 6157                render: Arc::new(|_| gpui::div().into_any_element()),
 6158                priority: 0,
 6159            }],
 6160            None,
 6161            cx,
 6162        );
 6163    });
 6164
 6165    // Move down so that the cursor touches the block.
 6166    cx.update_editor(|editor, window, cx| {
 6167        editor.move_down(&Default::default(), window, cx);
 6168    });
 6169    cx.assert_editor_state(
 6170        &"
 6171            zero
 6172            «one
 6173            two
 6174            threeˇ»
 6175            four
 6176            five
 6177        "
 6178        .unindent(),
 6179    );
 6180
 6181    // Move down past the block.
 6182    cx.update_editor(|editor, window, cx| {
 6183        editor.move_down(&Default::default(), window, cx);
 6184    });
 6185    cx.assert_editor_state(
 6186        &"
 6187            zero
 6188            one
 6189            two
 6190            three
 6191            ˇfour
 6192            five
 6193        "
 6194        .unindent(),
 6195    );
 6196}
 6197
 6198#[gpui::test]
 6199fn test_transpose(cx: &mut TestAppContext) {
 6200    init_test(cx, |_| {});
 6201
 6202    _ = cx.add_window(|window, cx| {
 6203        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6204        editor.set_style(EditorStyle::default(), window, cx);
 6205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6206            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6207        });
 6208        editor.transpose(&Default::default(), window, cx);
 6209        assert_eq!(editor.text(cx), "bac");
 6210        assert_eq!(
 6211            editor.selections.ranges(&editor.display_snapshot(cx)),
 6212            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6213        );
 6214
 6215        editor.transpose(&Default::default(), window, cx);
 6216        assert_eq!(editor.text(cx), "bca");
 6217        assert_eq!(
 6218            editor.selections.ranges(&editor.display_snapshot(cx)),
 6219            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6220        );
 6221
 6222        editor.transpose(&Default::default(), window, cx);
 6223        assert_eq!(editor.text(cx), "bac");
 6224        assert_eq!(
 6225            editor.selections.ranges(&editor.display_snapshot(cx)),
 6226            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6227        );
 6228
 6229        editor
 6230    });
 6231
 6232    _ = cx.add_window(|window, cx| {
 6233        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6234        editor.set_style(EditorStyle::default(), window, cx);
 6235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6236            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6237        });
 6238        editor.transpose(&Default::default(), window, cx);
 6239        assert_eq!(editor.text(cx), "acb\nde");
 6240        assert_eq!(
 6241            editor.selections.ranges(&editor.display_snapshot(cx)),
 6242            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6243        );
 6244
 6245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6246            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6247        });
 6248        editor.transpose(&Default::default(), window, cx);
 6249        assert_eq!(editor.text(cx), "acbd\ne");
 6250        assert_eq!(
 6251            editor.selections.ranges(&editor.display_snapshot(cx)),
 6252            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6253        );
 6254
 6255        editor.transpose(&Default::default(), window, cx);
 6256        assert_eq!(editor.text(cx), "acbde\n");
 6257        assert_eq!(
 6258            editor.selections.ranges(&editor.display_snapshot(cx)),
 6259            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6260        );
 6261
 6262        editor.transpose(&Default::default(), window, cx);
 6263        assert_eq!(editor.text(cx), "acbd\ne");
 6264        assert_eq!(
 6265            editor.selections.ranges(&editor.display_snapshot(cx)),
 6266            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6267        );
 6268
 6269        editor
 6270    });
 6271
 6272    _ = cx.add_window(|window, cx| {
 6273        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6274        editor.set_style(EditorStyle::default(), window, cx);
 6275        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6276            s.select_ranges([
 6277                MultiBufferOffset(1)..MultiBufferOffset(1),
 6278                MultiBufferOffset(2)..MultiBufferOffset(2),
 6279                MultiBufferOffset(4)..MultiBufferOffset(4),
 6280            ])
 6281        });
 6282        editor.transpose(&Default::default(), window, cx);
 6283        assert_eq!(editor.text(cx), "bacd\ne");
 6284        assert_eq!(
 6285            editor.selections.ranges(&editor.display_snapshot(cx)),
 6286            [
 6287                MultiBufferOffset(2)..MultiBufferOffset(2),
 6288                MultiBufferOffset(3)..MultiBufferOffset(3),
 6289                MultiBufferOffset(5)..MultiBufferOffset(5)
 6290            ]
 6291        );
 6292
 6293        editor.transpose(&Default::default(), window, cx);
 6294        assert_eq!(editor.text(cx), "bcade\n");
 6295        assert_eq!(
 6296            editor.selections.ranges(&editor.display_snapshot(cx)),
 6297            [
 6298                MultiBufferOffset(3)..MultiBufferOffset(3),
 6299                MultiBufferOffset(4)..MultiBufferOffset(4),
 6300                MultiBufferOffset(6)..MultiBufferOffset(6)
 6301            ]
 6302        );
 6303
 6304        editor.transpose(&Default::default(), window, cx);
 6305        assert_eq!(editor.text(cx), "bcda\ne");
 6306        assert_eq!(
 6307            editor.selections.ranges(&editor.display_snapshot(cx)),
 6308            [
 6309                MultiBufferOffset(4)..MultiBufferOffset(4),
 6310                MultiBufferOffset(6)..MultiBufferOffset(6)
 6311            ]
 6312        );
 6313
 6314        editor.transpose(&Default::default(), window, cx);
 6315        assert_eq!(editor.text(cx), "bcade\n");
 6316        assert_eq!(
 6317            editor.selections.ranges(&editor.display_snapshot(cx)),
 6318            [
 6319                MultiBufferOffset(4)..MultiBufferOffset(4),
 6320                MultiBufferOffset(6)..MultiBufferOffset(6)
 6321            ]
 6322        );
 6323
 6324        editor.transpose(&Default::default(), window, cx);
 6325        assert_eq!(editor.text(cx), "bcaed\n");
 6326        assert_eq!(
 6327            editor.selections.ranges(&editor.display_snapshot(cx)),
 6328            [
 6329                MultiBufferOffset(5)..MultiBufferOffset(5),
 6330                MultiBufferOffset(6)..MultiBufferOffset(6)
 6331            ]
 6332        );
 6333
 6334        editor
 6335    });
 6336
 6337    _ = cx.add_window(|window, cx| {
 6338        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6339        editor.set_style(EditorStyle::default(), window, cx);
 6340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6341            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6342        });
 6343        editor.transpose(&Default::default(), window, cx);
 6344        assert_eq!(editor.text(cx), "🏀🍐✋");
 6345        assert_eq!(
 6346            editor.selections.ranges(&editor.display_snapshot(cx)),
 6347            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6348        );
 6349
 6350        editor.transpose(&Default::default(), window, cx);
 6351        assert_eq!(editor.text(cx), "🏀✋🍐");
 6352        assert_eq!(
 6353            editor.selections.ranges(&editor.display_snapshot(cx)),
 6354            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6355        );
 6356
 6357        editor.transpose(&Default::default(), window, cx);
 6358        assert_eq!(editor.text(cx), "🏀🍐✋");
 6359        assert_eq!(
 6360            editor.selections.ranges(&editor.display_snapshot(cx)),
 6361            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6362        );
 6363
 6364        editor
 6365    });
 6366}
 6367
 6368#[gpui::test]
 6369async fn test_rewrap(cx: &mut TestAppContext) {
 6370    init_test(cx, |settings| {
 6371        settings.languages.0.extend([
 6372            (
 6373                "Markdown".into(),
 6374                LanguageSettingsContent {
 6375                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6376                    preferred_line_length: Some(40),
 6377                    ..Default::default()
 6378                },
 6379            ),
 6380            (
 6381                "Plain Text".into(),
 6382                LanguageSettingsContent {
 6383                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6384                    preferred_line_length: Some(40),
 6385                    ..Default::default()
 6386                },
 6387            ),
 6388            (
 6389                "C++".into(),
 6390                LanguageSettingsContent {
 6391                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6392                    preferred_line_length: Some(40),
 6393                    ..Default::default()
 6394                },
 6395            ),
 6396            (
 6397                "Python".into(),
 6398                LanguageSettingsContent {
 6399                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6400                    preferred_line_length: Some(40),
 6401                    ..Default::default()
 6402                },
 6403            ),
 6404            (
 6405                "Rust".into(),
 6406                LanguageSettingsContent {
 6407                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6408                    preferred_line_length: Some(40),
 6409                    ..Default::default()
 6410                },
 6411            ),
 6412        ])
 6413    });
 6414
 6415    let mut cx = EditorTestContext::new(cx).await;
 6416
 6417    let cpp_language = Arc::new(Language::new(
 6418        LanguageConfig {
 6419            name: "C++".into(),
 6420            line_comments: vec!["// ".into()],
 6421            ..LanguageConfig::default()
 6422        },
 6423        None,
 6424    ));
 6425    let python_language = Arc::new(Language::new(
 6426        LanguageConfig {
 6427            name: "Python".into(),
 6428            line_comments: vec!["# ".into()],
 6429            ..LanguageConfig::default()
 6430        },
 6431        None,
 6432    ));
 6433    let markdown_language = Arc::new(Language::new(
 6434        LanguageConfig {
 6435            name: "Markdown".into(),
 6436            rewrap_prefixes: vec![
 6437                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6438                regex::Regex::new("[-*+]\\s+").unwrap(),
 6439            ],
 6440            ..LanguageConfig::default()
 6441        },
 6442        None,
 6443    ));
 6444    let rust_language = Arc::new(
 6445        Language::new(
 6446            LanguageConfig {
 6447                name: "Rust".into(),
 6448                line_comments: vec!["// ".into(), "/// ".into()],
 6449                ..LanguageConfig::default()
 6450            },
 6451            Some(tree_sitter_rust::LANGUAGE.into()),
 6452        )
 6453        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6454        .unwrap(),
 6455    );
 6456
 6457    let plaintext_language = Arc::new(Language::new(
 6458        LanguageConfig {
 6459            name: "Plain Text".into(),
 6460            ..LanguageConfig::default()
 6461        },
 6462        None,
 6463    ));
 6464
 6465    // Test basic rewrapping of a long line with a cursor
 6466    assert_rewrap(
 6467        indoc! {"
 6468            // ˇThis is a long comment that needs to be wrapped.
 6469        "},
 6470        indoc! {"
 6471            // ˇThis is a long comment that needs to
 6472            // be wrapped.
 6473        "},
 6474        cpp_language.clone(),
 6475        &mut cx,
 6476    );
 6477
 6478    // Test rewrapping a full selection
 6479    assert_rewrap(
 6480        indoc! {"
 6481            «// This selected long comment needs to be wrapped.ˇ»"
 6482        },
 6483        indoc! {"
 6484            «// This selected long comment needs to
 6485            // be wrapped.ˇ»"
 6486        },
 6487        cpp_language.clone(),
 6488        &mut cx,
 6489    );
 6490
 6491    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6492    assert_rewrap(
 6493        indoc! {"
 6494            // ˇThis is the first line.
 6495            // Thisˇ is the second line.
 6496            // This is the thirdˇ line, all part of one paragraph.
 6497         "},
 6498        indoc! {"
 6499            // ˇThis is the first line. Thisˇ is the
 6500            // second line. This is the thirdˇ line,
 6501            // all part of one paragraph.
 6502         "},
 6503        cpp_language.clone(),
 6504        &mut cx,
 6505    );
 6506
 6507    // Test multiple cursors in different paragraphs trigger separate rewraps
 6508    assert_rewrap(
 6509        indoc! {"
 6510            // ˇThis is the first paragraph, first line.
 6511            // ˇThis is the first paragraph, second line.
 6512
 6513            // ˇThis is the second paragraph, first line.
 6514            // ˇThis is the second paragraph, second line.
 6515        "},
 6516        indoc! {"
 6517            // ˇThis is the first paragraph, first
 6518            // line. ˇThis is the first paragraph,
 6519            // second line.
 6520
 6521            // ˇThis is the second paragraph, first
 6522            // line. ˇThis is the second paragraph,
 6523            // second line.
 6524        "},
 6525        cpp_language.clone(),
 6526        &mut cx,
 6527    );
 6528
 6529    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6530    assert_rewrap(
 6531        indoc! {"
 6532            «// A regular long long comment to be wrapped.
 6533            /// A documentation long comment to be wrapped.ˇ»
 6534          "},
 6535        indoc! {"
 6536            «// A regular long long comment to be
 6537            // wrapped.
 6538            /// A documentation long comment to be
 6539            /// wrapped.ˇ»
 6540          "},
 6541        rust_language.clone(),
 6542        &mut cx,
 6543    );
 6544
 6545    // Test that change in indentation level trigger seperate rewraps
 6546    assert_rewrap(
 6547        indoc! {"
 6548            fn foo() {
 6549                «// This is a long comment at the base indent.
 6550                    // This is a long comment at the next indent.ˇ»
 6551            }
 6552        "},
 6553        indoc! {"
 6554            fn foo() {
 6555                «// This is a long comment at the
 6556                // base indent.
 6557                    // This is a long comment at the
 6558                    // next indent.ˇ»
 6559            }
 6560        "},
 6561        rust_language.clone(),
 6562        &mut cx,
 6563    );
 6564
 6565    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6566    assert_rewrap(
 6567        indoc! {"
 6568            # ˇThis is a long comment using a pound sign.
 6569        "},
 6570        indoc! {"
 6571            # ˇThis is a long comment using a pound
 6572            # sign.
 6573        "},
 6574        python_language,
 6575        &mut cx,
 6576    );
 6577
 6578    // Test rewrapping only affects comments, not code even when selected
 6579    assert_rewrap(
 6580        indoc! {"
 6581            «/// This doc comment is long and should be wrapped.
 6582            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6583        "},
 6584        indoc! {"
 6585            «/// This doc comment is long and should
 6586            /// be wrapped.
 6587            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6588        "},
 6589        rust_language.clone(),
 6590        &mut cx,
 6591    );
 6592
 6593    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6594    assert_rewrap(
 6595        indoc! {"
 6596            # Header
 6597
 6598            A long long long line of markdown text to wrap.ˇ
 6599         "},
 6600        indoc! {"
 6601            # Header
 6602
 6603            A long long long line of markdown text
 6604            to wrap.ˇ
 6605         "},
 6606        markdown_language.clone(),
 6607        &mut cx,
 6608    );
 6609
 6610    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6611    assert_rewrap(
 6612        indoc! {"
 6613            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6614            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6615            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6616        "},
 6617        indoc! {"
 6618            «1. This is a numbered list item that is
 6619               very long and needs to be wrapped
 6620               properly.
 6621            2. This is a numbered list item that is
 6622               very long and needs to be wrapped
 6623               properly.
 6624            - This is an unordered list item that is
 6625              also very long and should not merge
 6626              with the numbered item.ˇ»
 6627        "},
 6628        markdown_language.clone(),
 6629        &mut cx,
 6630    );
 6631
 6632    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6633    assert_rewrap(
 6634        indoc! {"
 6635            «1. This is a numbered list item that is
 6636            very long and needs to be wrapped
 6637            properly.
 6638            2. This is a numbered list item that is
 6639            very long and needs to be wrapped
 6640            properly.
 6641            - This is an unordered list item that is
 6642            also very long and should not merge with
 6643            the numbered item.ˇ»
 6644        "},
 6645        indoc! {"
 6646            «1. This is a numbered list item that is
 6647               very long and needs to be wrapped
 6648               properly.
 6649            2. This is a numbered list item that is
 6650               very long and needs to be wrapped
 6651               properly.
 6652            - This is an unordered list item that is
 6653              also very long and should not merge
 6654              with the numbered item.ˇ»
 6655        "},
 6656        markdown_language.clone(),
 6657        &mut cx,
 6658    );
 6659
 6660    // Test that rewrapping maintain indents even when they already exists.
 6661    assert_rewrap(
 6662        indoc! {"
 6663            «1. This is a numbered list
 6664               item that is very long and needs to be wrapped properly.
 6665            2. This is a numbered list
 6666               item that is very long and needs to be wrapped properly.
 6667            - This is an unordered list item that is also very long and
 6668              should not merge with the numbered item.ˇ»
 6669        "},
 6670        indoc! {"
 6671            «1. This is a numbered list item that is
 6672               very long and needs to be wrapped
 6673               properly.
 6674            2. This is a numbered list item that is
 6675               very long and needs to be wrapped
 6676               properly.
 6677            - This is an unordered list item that is
 6678              also very long and should not merge
 6679              with the numbered item.ˇ»
 6680        "},
 6681        markdown_language,
 6682        &mut cx,
 6683    );
 6684
 6685    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6686    assert_rewrap(
 6687        indoc! {"
 6688            ˇThis is a very long line of plain text that will be wrapped.
 6689        "},
 6690        indoc! {"
 6691            ˇThis is a very long line of plain text
 6692            that will be wrapped.
 6693        "},
 6694        plaintext_language.clone(),
 6695        &mut cx,
 6696    );
 6697
 6698    // Test that non-commented code acts as a paragraph boundary within a selection
 6699    assert_rewrap(
 6700        indoc! {"
 6701               «// This is the first long comment block to be wrapped.
 6702               fn my_func(a: u32);
 6703               // This is the second long comment block to be wrapped.ˇ»
 6704           "},
 6705        indoc! {"
 6706               «// This is the first long comment block
 6707               // to be wrapped.
 6708               fn my_func(a: u32);
 6709               // This is the second long comment block
 6710               // to be wrapped.ˇ»
 6711           "},
 6712        rust_language,
 6713        &mut cx,
 6714    );
 6715
 6716    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6717    assert_rewrap(
 6718        indoc! {"
 6719            «ˇThis is a very long line that will be wrapped.
 6720
 6721            This is another paragraph in the same selection.»
 6722
 6723            «\tThis is a very long indented line that will be wrapped.ˇ»
 6724         "},
 6725        indoc! {"
 6726            «ˇThis is a very long line that will be
 6727            wrapped.
 6728
 6729            This is another paragraph in the same
 6730            selection.»
 6731
 6732            «\tThis is a very long indented line
 6733            \tthat will be wrapped.ˇ»
 6734         "},
 6735        plaintext_language,
 6736        &mut cx,
 6737    );
 6738
 6739    // Test that an empty comment line acts as a paragraph boundary
 6740    assert_rewrap(
 6741        indoc! {"
 6742            // ˇThis is a long comment that will be wrapped.
 6743            //
 6744            // And this is another long comment that will also be wrapped.ˇ
 6745         "},
 6746        indoc! {"
 6747            // ˇThis is a long comment that will be
 6748            // wrapped.
 6749            //
 6750            // And this is another long comment that
 6751            // will also be wrapped.ˇ
 6752         "},
 6753        cpp_language,
 6754        &mut cx,
 6755    );
 6756
 6757    #[track_caller]
 6758    fn assert_rewrap(
 6759        unwrapped_text: &str,
 6760        wrapped_text: &str,
 6761        language: Arc<Language>,
 6762        cx: &mut EditorTestContext,
 6763    ) {
 6764        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6765        cx.set_state(unwrapped_text);
 6766        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6767        cx.assert_editor_state(wrapped_text);
 6768    }
 6769}
 6770
 6771#[gpui::test]
 6772async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6773    init_test(cx, |settings| {
 6774        settings.languages.0.extend([(
 6775            "Rust".into(),
 6776            LanguageSettingsContent {
 6777                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6778                preferred_line_length: Some(40),
 6779                ..Default::default()
 6780            },
 6781        )])
 6782    });
 6783
 6784    let mut cx = EditorTestContext::new(cx).await;
 6785
 6786    let rust_lang = Arc::new(
 6787        Language::new(
 6788            LanguageConfig {
 6789                name: "Rust".into(),
 6790                line_comments: vec!["// ".into()],
 6791                block_comment: Some(BlockCommentConfig {
 6792                    start: "/*".into(),
 6793                    end: "*/".into(),
 6794                    prefix: "* ".into(),
 6795                    tab_size: 1,
 6796                }),
 6797                documentation_comment: Some(BlockCommentConfig {
 6798                    start: "/**".into(),
 6799                    end: "*/".into(),
 6800                    prefix: "* ".into(),
 6801                    tab_size: 1,
 6802                }),
 6803
 6804                ..LanguageConfig::default()
 6805            },
 6806            Some(tree_sitter_rust::LANGUAGE.into()),
 6807        )
 6808        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6809        .unwrap(),
 6810    );
 6811
 6812    // regular block comment
 6813    assert_rewrap(
 6814        indoc! {"
 6815            /*
 6816             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6817             */
 6818            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6819        "},
 6820        indoc! {"
 6821            /*
 6822             *ˇ Lorem ipsum dolor sit amet,
 6823             * consectetur adipiscing elit.
 6824             */
 6825            /*
 6826             *ˇ Lorem ipsum dolor sit amet,
 6827             * consectetur adipiscing elit.
 6828             */
 6829        "},
 6830        rust_lang.clone(),
 6831        &mut cx,
 6832    );
 6833
 6834    // indent is respected
 6835    assert_rewrap(
 6836        indoc! {"
 6837            {}
 6838                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6839        "},
 6840        indoc! {"
 6841            {}
 6842                /*
 6843                 *ˇ Lorem ipsum dolor sit amet,
 6844                 * consectetur adipiscing elit.
 6845                 */
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // short block comments with inline delimiters
 6852    assert_rewrap(
 6853        indoc! {"
 6854            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6856             */
 6857            /*
 6858             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6859        "},
 6860        indoc! {"
 6861            /*
 6862             *ˇ Lorem ipsum dolor sit amet,
 6863             * consectetur adipiscing elit.
 6864             */
 6865            /*
 6866             *ˇ Lorem ipsum dolor sit amet,
 6867             * consectetur adipiscing elit.
 6868             */
 6869            /*
 6870             *ˇ Lorem ipsum dolor sit amet,
 6871             * consectetur adipiscing elit.
 6872             */
 6873        "},
 6874        rust_lang.clone(),
 6875        &mut cx,
 6876    );
 6877
 6878    // multiline block comment with inline start/end delimiters
 6879    assert_rewrap(
 6880        indoc! {"
 6881            /*ˇ Lorem ipsum dolor sit amet,
 6882             * consectetur adipiscing elit. */
 6883        "},
 6884        indoc! {"
 6885            /*
 6886             *ˇ Lorem ipsum dolor sit amet,
 6887             * consectetur adipiscing elit.
 6888             */
 6889        "},
 6890        rust_lang.clone(),
 6891        &mut cx,
 6892    );
 6893
 6894    // block comment rewrap still respects paragraph bounds
 6895    assert_rewrap(
 6896        indoc! {"
 6897            /*
 6898             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6899             *
 6900             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6901             */
 6902        "},
 6903        indoc! {"
 6904            /*
 6905             *ˇ Lorem ipsum dolor sit amet,
 6906             * consectetur adipiscing elit.
 6907             *
 6908             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6909             */
 6910        "},
 6911        rust_lang.clone(),
 6912        &mut cx,
 6913    );
 6914
 6915    // documentation comments
 6916    assert_rewrap(
 6917        indoc! {"
 6918            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6919            /**
 6920             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6921             */
 6922        "},
 6923        indoc! {"
 6924            /**
 6925             *ˇ Lorem ipsum dolor sit amet,
 6926             * consectetur adipiscing elit.
 6927             */
 6928            /**
 6929             *ˇ Lorem ipsum dolor sit amet,
 6930             * consectetur adipiscing elit.
 6931             */
 6932        "},
 6933        rust_lang.clone(),
 6934        &mut cx,
 6935    );
 6936
 6937    // different, adjacent comments
 6938    assert_rewrap(
 6939        indoc! {"
 6940            /**
 6941             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6942             */
 6943            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6944            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6945        "},
 6946        indoc! {"
 6947            /**
 6948             *ˇ Lorem ipsum dolor sit amet,
 6949             * consectetur adipiscing elit.
 6950             */
 6951            /*
 6952             *ˇ Lorem ipsum dolor sit amet,
 6953             * consectetur adipiscing elit.
 6954             */
 6955            //ˇ Lorem ipsum dolor sit amet,
 6956            // consectetur adipiscing elit.
 6957        "},
 6958        rust_lang.clone(),
 6959        &mut cx,
 6960    );
 6961
 6962    // selection w/ single short block comment
 6963    assert_rewrap(
 6964        indoc! {"
 6965            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6966        "},
 6967        indoc! {"
 6968            «/*
 6969             * Lorem ipsum dolor sit amet,
 6970             * consectetur adipiscing elit.
 6971             */ˇ»
 6972        "},
 6973        rust_lang.clone(),
 6974        &mut cx,
 6975    );
 6976
 6977    // rewrapping a single comment w/ abutting comments
 6978    assert_rewrap(
 6979        indoc! {"
 6980            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6981            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6982        "},
 6983        indoc! {"
 6984            /*
 6985             * ˇLorem ipsum dolor sit amet,
 6986             * consectetur adipiscing elit.
 6987             */
 6988            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6989        "},
 6990        rust_lang.clone(),
 6991        &mut cx,
 6992    );
 6993
 6994    // selection w/ non-abutting short block comments
 6995    assert_rewrap(
 6996        indoc! {"
 6997            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6998
 6999            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7000        "},
 7001        indoc! {"
 7002            «/*
 7003             * Lorem ipsum dolor sit amet,
 7004             * consectetur adipiscing elit.
 7005             */
 7006
 7007            /*
 7008             * Lorem ipsum dolor sit amet,
 7009             * consectetur adipiscing elit.
 7010             */ˇ»
 7011        "},
 7012        rust_lang.clone(),
 7013        &mut cx,
 7014    );
 7015
 7016    // selection of multiline block comments
 7017    assert_rewrap(
 7018        indoc! {"
 7019            «/* Lorem ipsum dolor sit amet,
 7020             * consectetur adipiscing elit. */ˇ»
 7021        "},
 7022        indoc! {"
 7023            «/*
 7024             * Lorem ipsum dolor sit amet,
 7025             * consectetur adipiscing elit.
 7026             */ˇ»
 7027        "},
 7028        rust_lang.clone(),
 7029        &mut cx,
 7030    );
 7031
 7032    // partial selection of multiline block comments
 7033    assert_rewrap(
 7034        indoc! {"
 7035            «/* Lorem ipsum dolor sit amet,ˇ»
 7036             * consectetur adipiscing elit. */
 7037            /* Lorem ipsum dolor sit amet,
 7038             «* consectetur adipiscing elit. */ˇ»
 7039        "},
 7040        indoc! {"
 7041            «/*
 7042             * Lorem ipsum dolor sit amet,ˇ»
 7043             * consectetur adipiscing elit. */
 7044            /* Lorem ipsum dolor sit amet,
 7045             «* consectetur adipiscing elit.
 7046             */ˇ»
 7047        "},
 7048        rust_lang.clone(),
 7049        &mut cx,
 7050    );
 7051
 7052    // selection w/ abutting short block comments
 7053    // TODO: should not be combined; should rewrap as 2 comments
 7054    assert_rewrap(
 7055        indoc! {"
 7056            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7057            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7058        "},
 7059        // desired behavior:
 7060        // indoc! {"
 7061        //     «/*
 7062        //      * Lorem ipsum dolor sit amet,
 7063        //      * consectetur adipiscing elit.
 7064        //      */
 7065        //     /*
 7066        //      * Lorem ipsum dolor sit amet,
 7067        //      * consectetur adipiscing elit.
 7068        //      */ˇ»
 7069        // "},
 7070        // actual behaviour:
 7071        indoc! {"
 7072            «/*
 7073             * Lorem ipsum dolor sit amet,
 7074             * consectetur adipiscing elit. Lorem
 7075             * ipsum dolor sit amet, consectetur
 7076             * adipiscing elit.
 7077             */ˇ»
 7078        "},
 7079        rust_lang.clone(),
 7080        &mut cx,
 7081    );
 7082
 7083    // TODO: same as above, but with delimiters on separate line
 7084    // assert_rewrap(
 7085    //     indoc! {"
 7086    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7087    //          */
 7088    //         /*
 7089    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7090    //     "},
 7091    //     // desired:
 7092    //     // indoc! {"
 7093    //     //     «/*
 7094    //     //      * Lorem ipsum dolor sit amet,
 7095    //     //      * consectetur adipiscing elit.
 7096    //     //      */
 7097    //     //     /*
 7098    //     //      * Lorem ipsum dolor sit amet,
 7099    //     //      * consectetur adipiscing elit.
 7100    //     //      */ˇ»
 7101    //     // "},
 7102    //     // actual: (but with trailing w/s on the empty lines)
 7103    //     indoc! {"
 7104    //         «/*
 7105    //          * Lorem ipsum dolor sit amet,
 7106    //          * consectetur adipiscing elit.
 7107    //          *
 7108    //          */
 7109    //         /*
 7110    //          *
 7111    //          * Lorem ipsum dolor sit amet,
 7112    //          * consectetur adipiscing elit.
 7113    //          */ˇ»
 7114    //     "},
 7115    //     rust_lang.clone(),
 7116    //     &mut cx,
 7117    // );
 7118
 7119    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7120    assert_rewrap(
 7121        indoc! {"
 7122            /*
 7123             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7124             */
 7125            /*
 7126             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7127            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7128        "},
 7129        // desired:
 7130        // indoc! {"
 7131        //     /*
 7132        //      *ˇ Lorem ipsum dolor sit amet,
 7133        //      * consectetur adipiscing elit.
 7134        //      */
 7135        //     /*
 7136        //      *ˇ Lorem ipsum dolor sit amet,
 7137        //      * consectetur adipiscing elit.
 7138        //      */
 7139        //     /*
 7140        //      *ˇ Lorem ipsum dolor sit amet
 7141        //      */ /* consectetur adipiscing elit. */
 7142        // "},
 7143        // actual:
 7144        indoc! {"
 7145            /*
 7146             //ˇ Lorem ipsum dolor sit amet,
 7147             // consectetur adipiscing elit.
 7148             */
 7149            /*
 7150             * //ˇ Lorem ipsum dolor sit amet,
 7151             * consectetur adipiscing elit.
 7152             */
 7153            /*
 7154             *ˇ Lorem ipsum dolor sit amet */ /*
 7155             * consectetur adipiscing elit.
 7156             */
 7157        "},
 7158        rust_lang,
 7159        &mut cx,
 7160    );
 7161
 7162    #[track_caller]
 7163    fn assert_rewrap(
 7164        unwrapped_text: &str,
 7165        wrapped_text: &str,
 7166        language: Arc<Language>,
 7167        cx: &mut EditorTestContext,
 7168    ) {
 7169        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7170        cx.set_state(unwrapped_text);
 7171        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7172        cx.assert_editor_state(wrapped_text);
 7173    }
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_hard_wrap(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179    let mut cx = EditorTestContext::new(cx).await;
 7180
 7181    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7182    cx.update_editor(|editor, _, cx| {
 7183        editor.set_hard_wrap(Some(14), cx);
 7184    });
 7185
 7186    cx.set_state(indoc!(
 7187        "
 7188        one two three ˇ
 7189        "
 7190    ));
 7191    cx.simulate_input("four");
 7192    cx.run_until_parked();
 7193
 7194    cx.assert_editor_state(indoc!(
 7195        "
 7196        one two three
 7197        fourˇ
 7198        "
 7199    ));
 7200
 7201    cx.update_editor(|editor, window, cx| {
 7202        editor.newline(&Default::default(), window, cx);
 7203    });
 7204    cx.run_until_parked();
 7205    cx.assert_editor_state(indoc!(
 7206        "
 7207        one two three
 7208        four
 7209        ˇ
 7210        "
 7211    ));
 7212
 7213    cx.simulate_input("five");
 7214    cx.run_until_parked();
 7215    cx.assert_editor_state(indoc!(
 7216        "
 7217        one two three
 7218        four
 7219        fiveˇ
 7220        "
 7221    ));
 7222
 7223    cx.update_editor(|editor, window, cx| {
 7224        editor.newline(&Default::default(), window, cx);
 7225    });
 7226    cx.run_until_parked();
 7227    cx.simulate_input("# ");
 7228    cx.run_until_parked();
 7229    cx.assert_editor_state(indoc!(
 7230        "
 7231        one two three
 7232        four
 7233        five
 7234        # ˇ
 7235        "
 7236    ));
 7237
 7238    cx.update_editor(|editor, window, cx| {
 7239        editor.newline(&Default::default(), window, cx);
 7240    });
 7241    cx.run_until_parked();
 7242    cx.assert_editor_state(indoc!(
 7243        "
 7244        one two three
 7245        four
 7246        five
 7247        #\x20
 7248 7249        "
 7250    ));
 7251
 7252    cx.simulate_input(" 6");
 7253    cx.run_until_parked();
 7254    cx.assert_editor_state(indoc!(
 7255        "
 7256        one two three
 7257        four
 7258        five
 7259        #
 7260        # 6ˇ
 7261        "
 7262    ));
 7263}
 7264
 7265#[gpui::test]
 7266async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7267    init_test(cx, |_| {});
 7268
 7269    let mut cx = EditorTestContext::new(cx).await;
 7270
 7271    cx.set_state(indoc! {"The quick brownˇ"});
 7272    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7273    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7274
 7275    cx.set_state(indoc! {"The emacs foxˇ"});
 7276    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7277    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7278
 7279    cx.set_state(indoc! {"
 7280        The quick« brownˇ»
 7281        fox jumps overˇ
 7282        the lazy dog"});
 7283    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7284    cx.assert_editor_state(indoc! {"
 7285        The quickˇ
 7286        ˇthe lazy dog"});
 7287
 7288    cx.set_state(indoc! {"
 7289        The quick« brownˇ»
 7290        fox jumps overˇ
 7291        the lazy dog"});
 7292    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7293    cx.assert_editor_state(indoc! {"
 7294        The quickˇ
 7295        fox jumps overˇthe lazy dog"});
 7296
 7297    cx.set_state(indoc! {"
 7298        The quick« brownˇ»
 7299        fox jumps overˇ
 7300        the lazy dog"});
 7301    cx.update_editor(|e, window, cx| {
 7302        e.cut_to_end_of_line(
 7303            &CutToEndOfLine {
 7304                stop_at_newlines: true,
 7305            },
 7306            window,
 7307            cx,
 7308        )
 7309    });
 7310    cx.assert_editor_state(indoc! {"
 7311        The quickˇ
 7312        fox jumps overˇ
 7313        the lazy dog"});
 7314
 7315    cx.set_state(indoc! {"
 7316        The quick« brownˇ»
 7317        fox jumps overˇ
 7318        the lazy dog"});
 7319    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7320    cx.assert_editor_state(indoc! {"
 7321        The quickˇ
 7322        fox jumps overˇthe lazy dog"});
 7323}
 7324
 7325#[gpui::test]
 7326async fn test_clipboard(cx: &mut TestAppContext) {
 7327    init_test(cx, |_| {});
 7328
 7329    let mut cx = EditorTestContext::new(cx).await;
 7330
 7331    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7332    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7333    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7334
 7335    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7336    cx.set_state("two ˇfour ˇsix ˇ");
 7337    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7338    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7339
 7340    // Paste again but with only two cursors. Since the number of cursors doesn't
 7341    // match the number of slices in the clipboard, the entire clipboard text
 7342    // is pasted at each cursor.
 7343    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7344    cx.update_editor(|e, window, cx| {
 7345        e.handle_input("( ", window, cx);
 7346        e.paste(&Paste, window, cx);
 7347        e.handle_input(") ", window, cx);
 7348    });
 7349    cx.assert_editor_state(
 7350        &([
 7351            "( one✅ ",
 7352            "three ",
 7353            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7354            "three ",
 7355            "five ) ˇ",
 7356        ]
 7357        .join("\n")),
 7358    );
 7359
 7360    // Cut with three selections, one of which is full-line.
 7361    cx.set_state(indoc! {"
 7362        1«2ˇ»3
 7363        4ˇ567
 7364        «8ˇ»9"});
 7365    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7366    cx.assert_editor_state(indoc! {"
 7367        1ˇ3
 7368        ˇ9"});
 7369
 7370    // Paste with three selections, noticing how the copied selection that was full-line
 7371    // gets inserted before the second cursor.
 7372    cx.set_state(indoc! {"
 7373        1ˇ3
 7374 7375        «oˇ»ne"});
 7376    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7377    cx.assert_editor_state(indoc! {"
 7378        12ˇ3
 7379        4567
 7380 7381        8ˇne"});
 7382
 7383    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7384    cx.set_state(indoc! {"
 7385        The quick brown
 7386        fox juˇmps over
 7387        the lazy dog"});
 7388    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7389    assert_eq!(
 7390        cx.read_from_clipboard()
 7391            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7392        Some("fox jumps over\n".to_string())
 7393    );
 7394
 7395    // Paste with three selections, noticing how the copied full-line selection is inserted
 7396    // before the empty selections but replaces the selection that is non-empty.
 7397    cx.set_state(indoc! {"
 7398        Tˇhe quick brown
 7399        «foˇ»x jumps over
 7400        tˇhe lazy dog"});
 7401    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7402    cx.assert_editor_state(indoc! {"
 7403        fox jumps over
 7404        Tˇhe quick brown
 7405        fox jumps over
 7406        ˇx jumps over
 7407        fox jumps over
 7408        tˇhe lazy dog"});
 7409}
 7410
 7411#[gpui::test]
 7412async fn test_copy_trim(cx: &mut TestAppContext) {
 7413    init_test(cx, |_| {});
 7414
 7415    let mut cx = EditorTestContext::new(cx).await;
 7416    cx.set_state(
 7417        r#"            «for selection in selections.iter() {
 7418            let mut start = selection.start;
 7419            let mut end = selection.end;
 7420            let is_entire_line = selection.is_empty();
 7421            if is_entire_line {
 7422                start = Point::new(start.row, 0);ˇ»
 7423                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7424            }
 7425        "#,
 7426    );
 7427    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7428    assert_eq!(
 7429        cx.read_from_clipboard()
 7430            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7431        Some(
 7432            "for selection in selections.iter() {
 7433            let mut start = selection.start;
 7434            let mut end = selection.end;
 7435            let is_entire_line = selection.is_empty();
 7436            if is_entire_line {
 7437                start = Point::new(start.row, 0);"
 7438                .to_string()
 7439        ),
 7440        "Regular copying preserves all indentation selected",
 7441    );
 7442    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7443    assert_eq!(
 7444        cx.read_from_clipboard()
 7445            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7446        Some(
 7447            "for selection in selections.iter() {
 7448let mut start = selection.start;
 7449let mut end = selection.end;
 7450let is_entire_line = selection.is_empty();
 7451if is_entire_line {
 7452    start = Point::new(start.row, 0);"
 7453                .to_string()
 7454        ),
 7455        "Copying with stripping should strip all leading whitespaces"
 7456    );
 7457
 7458    cx.set_state(
 7459        r#"       «     for selection in selections.iter() {
 7460            let mut start = selection.start;
 7461            let mut end = selection.end;
 7462            let is_entire_line = selection.is_empty();
 7463            if is_entire_line {
 7464                start = Point::new(start.row, 0);ˇ»
 7465                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7466            }
 7467        "#,
 7468    );
 7469    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7470    assert_eq!(
 7471        cx.read_from_clipboard()
 7472            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7473        Some(
 7474            "     for selection in selections.iter() {
 7475            let mut start = selection.start;
 7476            let mut end = selection.end;
 7477            let is_entire_line = selection.is_empty();
 7478            if is_entire_line {
 7479                start = Point::new(start.row, 0);"
 7480                .to_string()
 7481        ),
 7482        "Regular copying preserves all indentation selected",
 7483    );
 7484    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7485    assert_eq!(
 7486        cx.read_from_clipboard()
 7487            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7488        Some(
 7489            "for selection in selections.iter() {
 7490let mut start = selection.start;
 7491let mut end = selection.end;
 7492let is_entire_line = selection.is_empty();
 7493if is_entire_line {
 7494    start = Point::new(start.row, 0);"
 7495                .to_string()
 7496        ),
 7497        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7498    );
 7499
 7500    cx.set_state(
 7501        r#"       «ˇ     for selection in selections.iter() {
 7502            let mut start = selection.start;
 7503            let mut end = selection.end;
 7504            let is_entire_line = selection.is_empty();
 7505            if is_entire_line {
 7506                start = Point::new(start.row, 0);»
 7507                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7508            }
 7509        "#,
 7510    );
 7511    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7512    assert_eq!(
 7513        cx.read_from_clipboard()
 7514            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7515        Some(
 7516            "     for selection in selections.iter() {
 7517            let mut start = selection.start;
 7518            let mut end = selection.end;
 7519            let is_entire_line = selection.is_empty();
 7520            if is_entire_line {
 7521                start = Point::new(start.row, 0);"
 7522                .to_string()
 7523        ),
 7524        "Regular copying for reverse selection works the same",
 7525    );
 7526    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7527    assert_eq!(
 7528        cx.read_from_clipboard()
 7529            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7530        Some(
 7531            "for selection in selections.iter() {
 7532let mut start = selection.start;
 7533let mut end = selection.end;
 7534let is_entire_line = selection.is_empty();
 7535if is_entire_line {
 7536    start = Point::new(start.row, 0);"
 7537                .to_string()
 7538        ),
 7539        "Copying with stripping for reverse selection works the same"
 7540    );
 7541
 7542    cx.set_state(
 7543        r#"            for selection «in selections.iter() {
 7544            let mut start = selection.start;
 7545            let mut end = selection.end;
 7546            let is_entire_line = selection.is_empty();
 7547            if is_entire_line {
 7548                start = Point::new(start.row, 0);ˇ»
 7549                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7550            }
 7551        "#,
 7552    );
 7553    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7554    assert_eq!(
 7555        cx.read_from_clipboard()
 7556            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7557        Some(
 7558            "in selections.iter() {
 7559            let mut start = selection.start;
 7560            let mut end = selection.end;
 7561            let is_entire_line = selection.is_empty();
 7562            if is_entire_line {
 7563                start = Point::new(start.row, 0);"
 7564                .to_string()
 7565        ),
 7566        "When selecting past the indent, the copying works as usual",
 7567    );
 7568    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7569    assert_eq!(
 7570        cx.read_from_clipboard()
 7571            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7572        Some(
 7573            "in selections.iter() {
 7574            let mut start = selection.start;
 7575            let mut end = selection.end;
 7576            let is_entire_line = selection.is_empty();
 7577            if is_entire_line {
 7578                start = Point::new(start.row, 0);"
 7579                .to_string()
 7580        ),
 7581        "When selecting past the indent, nothing is trimmed"
 7582    );
 7583
 7584    cx.set_state(
 7585        r#"            «for selection in selections.iter() {
 7586            let mut start = selection.start;
 7587
 7588            let mut end = selection.end;
 7589            let is_entire_line = selection.is_empty();
 7590            if is_entire_line {
 7591                start = Point::new(start.row, 0);
 7592ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7593            }
 7594        "#,
 7595    );
 7596    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7597    assert_eq!(
 7598        cx.read_from_clipboard()
 7599            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7600        Some(
 7601            "for selection in selections.iter() {
 7602let mut start = selection.start;
 7603
 7604let mut end = selection.end;
 7605let is_entire_line = selection.is_empty();
 7606if is_entire_line {
 7607    start = Point::new(start.row, 0);
 7608"
 7609            .to_string()
 7610        ),
 7611        "Copying with stripping should ignore empty lines"
 7612    );
 7613}
 7614
 7615#[gpui::test]
 7616async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7617    init_test(cx, |_| {});
 7618
 7619    let mut cx = EditorTestContext::new(cx).await;
 7620
 7621    cx.set_state(indoc! {"
 7622        «    a
 7623            bˇ»
 7624    "});
 7625    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7626    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7627
 7628    assert_eq!(
 7629        cx.read_from_clipboard().and_then(|item| item.text()),
 7630        Some("a\nb\n".to_string())
 7631    );
 7632}
 7633
 7634#[gpui::test]
 7635async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 7636    init_test(cx, |_| {});
 7637
 7638    let fs = FakeFs::new(cx.executor());
 7639    fs.insert_file(
 7640        path!("/file.txt"),
 7641        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 7642    )
 7643    .await;
 7644
 7645    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 7646
 7647    let buffer = project
 7648        .update(cx, |project, cx| {
 7649            project.open_local_buffer(path!("/file.txt"), cx)
 7650        })
 7651        .await
 7652        .unwrap();
 7653
 7654    let multibuffer = cx.new(|cx| {
 7655        let mut multibuffer = MultiBuffer::new(ReadWrite);
 7656        multibuffer.push_excerpts(
 7657            buffer.clone(),
 7658            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 7659            cx,
 7660        );
 7661        multibuffer
 7662    });
 7663
 7664    let (editor, cx) = cx.add_window_view(|window, cx| {
 7665        build_editor_with_project(project.clone(), multibuffer, window, cx)
 7666    });
 7667
 7668    editor.update_in(cx, |editor, window, cx| {
 7669        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 7670
 7671        editor.select_all(&SelectAll, window, cx);
 7672        editor.copy(&Copy, window, cx);
 7673    });
 7674
 7675    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 7676        .read_from_clipboard()
 7677        .and_then(|item| item.entries().first().cloned())
 7678        .and_then(|entry| match entry {
 7679            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 7680            _ => None,
 7681        });
 7682
 7683    let selections = clipboard_selections.expect("should have clipboard selections");
 7684    assert_eq!(selections.len(), 1);
 7685    let selection = &selections[0];
 7686    assert_eq!(
 7687        selection.line_range,
 7688        Some(2..=5),
 7689        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 7690    );
 7691}
 7692
 7693#[gpui::test]
 7694async fn test_paste_multiline(cx: &mut TestAppContext) {
 7695    init_test(cx, |_| {});
 7696
 7697    let mut cx = EditorTestContext::new(cx).await;
 7698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7699
 7700    // Cut an indented block, without the leading whitespace.
 7701    cx.set_state(indoc! {"
 7702        const a: B = (
 7703            c(),
 7704            «d(
 7705                e,
 7706                f
 7707            )ˇ»
 7708        );
 7709    "});
 7710    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7711    cx.assert_editor_state(indoc! {"
 7712        const a: B = (
 7713            c(),
 7714            ˇ
 7715        );
 7716    "});
 7717
 7718    // Paste it at the same position.
 7719    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7720    cx.assert_editor_state(indoc! {"
 7721        const a: B = (
 7722            c(),
 7723            d(
 7724                e,
 7725                f
 7726 7727        );
 7728    "});
 7729
 7730    // Paste it at a line with a lower indent level.
 7731    cx.set_state(indoc! {"
 7732        ˇ
 7733        const a: B = (
 7734            c(),
 7735        );
 7736    "});
 7737    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7738    cx.assert_editor_state(indoc! {"
 7739        d(
 7740            e,
 7741            f
 7742 7743        const a: B = (
 7744            c(),
 7745        );
 7746    "});
 7747
 7748    // Cut an indented block, with the leading whitespace.
 7749    cx.set_state(indoc! {"
 7750        const a: B = (
 7751            c(),
 7752        «    d(
 7753                e,
 7754                f
 7755            )
 7756        ˇ»);
 7757    "});
 7758    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7759    cx.assert_editor_state(indoc! {"
 7760        const a: B = (
 7761            c(),
 7762        ˇ);
 7763    "});
 7764
 7765    // Paste it at the same position.
 7766    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7767    cx.assert_editor_state(indoc! {"
 7768        const a: B = (
 7769            c(),
 7770            d(
 7771                e,
 7772                f
 7773            )
 7774        ˇ);
 7775    "});
 7776
 7777    // Paste it at a line with a higher indent level.
 7778    cx.set_state(indoc! {"
 7779        const a: B = (
 7780            c(),
 7781            d(
 7782                e,
 7783 7784            )
 7785        );
 7786    "});
 7787    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7788    cx.assert_editor_state(indoc! {"
 7789        const a: B = (
 7790            c(),
 7791            d(
 7792                e,
 7793                f    d(
 7794                    e,
 7795                    f
 7796                )
 7797        ˇ
 7798            )
 7799        );
 7800    "});
 7801
 7802    // Copy an indented block, starting mid-line
 7803    cx.set_state(indoc! {"
 7804        const a: B = (
 7805            c(),
 7806            somethin«g(
 7807                e,
 7808                f
 7809            )ˇ»
 7810        );
 7811    "});
 7812    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7813
 7814    // Paste it on a line with a lower indent level
 7815    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7816    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7817    cx.assert_editor_state(indoc! {"
 7818        const a: B = (
 7819            c(),
 7820            something(
 7821                e,
 7822                f
 7823            )
 7824        );
 7825        g(
 7826            e,
 7827            f
 7828"});
 7829}
 7830
 7831#[gpui::test]
 7832async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7833    init_test(cx, |_| {});
 7834
 7835    cx.write_to_clipboard(ClipboardItem::new_string(
 7836        "    d(\n        e\n    );\n".into(),
 7837    ));
 7838
 7839    let mut cx = EditorTestContext::new(cx).await;
 7840    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7841
 7842    cx.set_state(indoc! {"
 7843        fn a() {
 7844            b();
 7845            if c() {
 7846                ˇ
 7847            }
 7848        }
 7849    "});
 7850
 7851    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7852    cx.assert_editor_state(indoc! {"
 7853        fn a() {
 7854            b();
 7855            if c() {
 7856                d(
 7857                    e
 7858                );
 7859        ˇ
 7860            }
 7861        }
 7862    "});
 7863
 7864    cx.set_state(indoc! {"
 7865        fn a() {
 7866            b();
 7867            ˇ
 7868        }
 7869    "});
 7870
 7871    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7872    cx.assert_editor_state(indoc! {"
 7873        fn a() {
 7874            b();
 7875            d(
 7876                e
 7877            );
 7878        ˇ
 7879        }
 7880    "});
 7881}
 7882
 7883#[gpui::test]
 7884fn test_select_all(cx: &mut TestAppContext) {
 7885    init_test(cx, |_| {});
 7886
 7887    let editor = cx.add_window(|window, cx| {
 7888        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7889        build_editor(buffer, window, cx)
 7890    });
 7891    _ = editor.update(cx, |editor, window, cx| {
 7892        editor.select_all(&SelectAll, window, cx);
 7893        assert_eq!(
 7894            display_ranges(editor, cx),
 7895            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7896        );
 7897    });
 7898}
 7899
 7900#[gpui::test]
 7901fn test_select_line(cx: &mut TestAppContext) {
 7902    init_test(cx, |_| {});
 7903
 7904    let editor = cx.add_window(|window, cx| {
 7905        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7906        build_editor(buffer, window, cx)
 7907    });
 7908    _ = editor.update(cx, |editor, window, cx| {
 7909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7910            s.select_display_ranges([
 7911                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7912                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7913                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7914                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7915            ])
 7916        });
 7917        editor.select_line(&SelectLine, window, cx);
 7918        // Adjacent line selections should NOT merge (only overlapping ones do)
 7919        assert_eq!(
 7920            display_ranges(editor, cx),
 7921            vec![
 7922                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7923                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7924                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7925            ]
 7926        );
 7927    });
 7928
 7929    _ = editor.update(cx, |editor, window, cx| {
 7930        editor.select_line(&SelectLine, window, cx);
 7931        assert_eq!(
 7932            display_ranges(editor, cx),
 7933            vec![
 7934                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7936            ]
 7937        );
 7938    });
 7939
 7940    _ = editor.update(cx, |editor, window, cx| {
 7941        editor.select_line(&SelectLine, window, cx);
 7942        // Adjacent but not overlapping, so they stay separate
 7943        assert_eq!(
 7944            display_ranges(editor, cx),
 7945            vec![
 7946                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7947                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7948            ]
 7949        );
 7950    });
 7951}
 7952
 7953#[gpui::test]
 7954async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7955    init_test(cx, |_| {});
 7956    let mut cx = EditorTestContext::new(cx).await;
 7957
 7958    #[track_caller]
 7959    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7960        cx.set_state(initial_state);
 7961        cx.update_editor(|e, window, cx| {
 7962            e.split_selection_into_lines(&Default::default(), window, cx)
 7963        });
 7964        cx.assert_editor_state(expected_state);
 7965    }
 7966
 7967    // Selection starts and ends at the middle of lines, left-to-right
 7968    test(
 7969        &mut cx,
 7970        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7971        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7972    );
 7973    // Same thing, right-to-left
 7974    test(
 7975        &mut cx,
 7976        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7977        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7978    );
 7979
 7980    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7981    test(
 7982        &mut cx,
 7983        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7984        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7985    );
 7986    // Same thing, right-to-left
 7987    test(
 7988        &mut cx,
 7989        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7990        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7991    );
 7992
 7993    // Whole buffer, left-to-right, last line ends with newline
 7994    test(
 7995        &mut cx,
 7996        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7997        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7998    );
 7999    // Same thing, right-to-left
 8000    test(
 8001        &mut cx,
 8002        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8003        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8004    );
 8005
 8006    // Starts at the end of a line, ends at the start of another
 8007    test(
 8008        &mut cx,
 8009        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8010        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8011    );
 8012}
 8013
 8014#[gpui::test]
 8015async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8016    init_test(cx, |_| {});
 8017
 8018    let editor = cx.add_window(|window, cx| {
 8019        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8020        build_editor(buffer, window, cx)
 8021    });
 8022
 8023    // setup
 8024    _ = editor.update(cx, |editor, window, cx| {
 8025        editor.fold_creases(
 8026            vec![
 8027                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8028                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8029                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8030            ],
 8031            true,
 8032            window,
 8033            cx,
 8034        );
 8035        assert_eq!(
 8036            editor.display_text(cx),
 8037            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8038        );
 8039    });
 8040
 8041    _ = editor.update(cx, |editor, window, cx| {
 8042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8043            s.select_display_ranges([
 8044                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8045                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8046                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8047                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8048            ])
 8049        });
 8050        editor.split_selection_into_lines(&Default::default(), window, cx);
 8051        assert_eq!(
 8052            editor.display_text(cx),
 8053            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8054        );
 8055    });
 8056    EditorTestContext::for_editor(editor, cx)
 8057        .await
 8058        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8059
 8060    _ = editor.update(cx, |editor, window, cx| {
 8061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8062            s.select_display_ranges([
 8063                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8064            ])
 8065        });
 8066        editor.split_selection_into_lines(&Default::default(), window, cx);
 8067        assert_eq!(
 8068            editor.display_text(cx),
 8069            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8070        );
 8071        assert_eq!(
 8072            display_ranges(editor, cx),
 8073            [
 8074                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8075                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8076                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8077                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8078                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8079                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8080                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8081            ]
 8082        );
 8083    });
 8084    EditorTestContext::for_editor(editor, cx)
 8085        .await
 8086        .assert_editor_state(
 8087            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8088        );
 8089}
 8090
 8091#[gpui::test]
 8092async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8093    init_test(cx, |_| {});
 8094
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"abc
 8099           defˇghi
 8100
 8101           jk
 8102           nlmo
 8103           "#
 8104    ));
 8105
 8106    cx.update_editor(|editor, window, cx| {
 8107        editor.add_selection_above(&Default::default(), window, cx);
 8108    });
 8109
 8110    cx.assert_editor_state(indoc!(
 8111        r#"abcˇ
 8112           defˇghi
 8113
 8114           jk
 8115           nlmo
 8116           "#
 8117    ));
 8118
 8119    cx.update_editor(|editor, window, cx| {
 8120        editor.add_selection_above(&Default::default(), window, cx);
 8121    });
 8122
 8123    cx.assert_editor_state(indoc!(
 8124        r#"abcˇ
 8125            defˇghi
 8126
 8127            jk
 8128            nlmo
 8129            "#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_below(&Default::default(), window, cx);
 8134    });
 8135
 8136    cx.assert_editor_state(indoc!(
 8137        r#"abc
 8138           defˇghi
 8139
 8140           jk
 8141           nlmo
 8142           "#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.undo_selection(&Default::default(), window, cx);
 8147    });
 8148
 8149    cx.assert_editor_state(indoc!(
 8150        r#"abcˇ
 8151           defˇghi
 8152
 8153           jk
 8154           nlmo
 8155           "#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.redo_selection(&Default::default(), window, cx);
 8160    });
 8161
 8162    cx.assert_editor_state(indoc!(
 8163        r#"abc
 8164           defˇghi
 8165
 8166           jk
 8167           nlmo
 8168           "#
 8169    ));
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.add_selection_below(&Default::default(), window, cx);
 8173    });
 8174
 8175    cx.assert_editor_state(indoc!(
 8176        r#"abc
 8177           defˇghi
 8178           ˇ
 8179           jk
 8180           nlmo
 8181           "#
 8182    ));
 8183
 8184    cx.update_editor(|editor, window, cx| {
 8185        editor.add_selection_below(&Default::default(), window, cx);
 8186    });
 8187
 8188    cx.assert_editor_state(indoc!(
 8189        r#"abc
 8190           defˇghi
 8191           ˇ
 8192           jkˇ
 8193           nlmo
 8194           "#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199    });
 8200
 8201    cx.assert_editor_state(indoc!(
 8202        r#"abc
 8203           defˇghi
 8204           ˇ
 8205           jkˇ
 8206           nlmˇo
 8207           "#
 8208    ));
 8209
 8210    cx.update_editor(|editor, window, cx| {
 8211        editor.add_selection_below(&Default::default(), window, cx);
 8212    });
 8213
 8214    cx.assert_editor_state(indoc!(
 8215        r#"abc
 8216           defˇghi
 8217           ˇ
 8218           jkˇ
 8219           nlmˇo
 8220           ˇ"#
 8221    ));
 8222
 8223    // change selections
 8224    cx.set_state(indoc!(
 8225        r#"abc
 8226           def«ˇg»hi
 8227
 8228           jk
 8229           nlmo
 8230           "#
 8231    ));
 8232
 8233    cx.update_editor(|editor, window, cx| {
 8234        editor.add_selection_below(&Default::default(), window, cx);
 8235    });
 8236
 8237    cx.assert_editor_state(indoc!(
 8238        r#"abc
 8239           def«ˇg»hi
 8240
 8241           jk
 8242           nlm«ˇo»
 8243           "#
 8244    ));
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.add_selection_below(&Default::default(), window, cx);
 8248    });
 8249
 8250    cx.assert_editor_state(indoc!(
 8251        r#"abc
 8252           def«ˇg»hi
 8253
 8254           jk
 8255           nlm«ˇo»
 8256           "#
 8257    ));
 8258
 8259    cx.update_editor(|editor, window, cx| {
 8260        editor.add_selection_above(&Default::default(), window, cx);
 8261    });
 8262
 8263    cx.assert_editor_state(indoc!(
 8264        r#"abc
 8265           def«ˇg»hi
 8266
 8267           jk
 8268           nlmo
 8269           "#
 8270    ));
 8271
 8272    cx.update_editor(|editor, window, cx| {
 8273        editor.add_selection_above(&Default::default(), window, cx);
 8274    });
 8275
 8276    cx.assert_editor_state(indoc!(
 8277        r#"abc
 8278           def«ˇg»hi
 8279
 8280           jk
 8281           nlmo
 8282           "#
 8283    ));
 8284
 8285    // Change selections again
 8286    cx.set_state(indoc!(
 8287        r#"a«bc
 8288           defgˇ»hi
 8289
 8290           jk
 8291           nlmo
 8292           "#
 8293    ));
 8294
 8295    cx.update_editor(|editor, window, cx| {
 8296        editor.add_selection_below(&Default::default(), window, cx);
 8297    });
 8298
 8299    cx.assert_editor_state(indoc!(
 8300        r#"a«bcˇ»
 8301           d«efgˇ»hi
 8302
 8303           j«kˇ»
 8304           nlmo
 8305           "#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_below(&Default::default(), window, cx);
 8310    });
 8311    cx.assert_editor_state(indoc!(
 8312        r#"a«bcˇ»
 8313           d«efgˇ»hi
 8314
 8315           j«kˇ»
 8316           n«lmoˇ»
 8317           "#
 8318    ));
 8319    cx.update_editor(|editor, window, cx| {
 8320        editor.add_selection_above(&Default::default(), window, cx);
 8321    });
 8322
 8323    cx.assert_editor_state(indoc!(
 8324        r#"a«bcˇ»
 8325           d«efgˇ»hi
 8326
 8327           j«kˇ»
 8328           nlmo
 8329           "#
 8330    ));
 8331
 8332    // Change selections again
 8333    cx.set_state(indoc!(
 8334        r#"abc
 8335           d«ˇefghi
 8336
 8337           jk
 8338           nlm»o
 8339           "#
 8340    ));
 8341
 8342    cx.update_editor(|editor, window, cx| {
 8343        editor.add_selection_above(&Default::default(), window, cx);
 8344    });
 8345
 8346    cx.assert_editor_state(indoc!(
 8347        r#"a«ˇbc»
 8348           d«ˇef»ghi
 8349
 8350           j«ˇk»
 8351           n«ˇlm»o
 8352           "#
 8353    ));
 8354
 8355    cx.update_editor(|editor, window, cx| {
 8356        editor.add_selection_below(&Default::default(), window, cx);
 8357    });
 8358
 8359    cx.assert_editor_state(indoc!(
 8360        r#"abc
 8361           d«ˇef»ghi
 8362
 8363           j«ˇk»
 8364           n«ˇlm»o
 8365           "#
 8366    ));
 8367
 8368    // Assert that the oldest selection's goal column is used when adding more
 8369    // selections, not the most recently added selection's actual column.
 8370    cx.set_state(indoc! {"
 8371        foo bar bazˇ
 8372        foo
 8373        foo bar
 8374    "});
 8375
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.add_selection_below(
 8378            &AddSelectionBelow {
 8379                skip_soft_wrap: true,
 8380            },
 8381            window,
 8382            cx,
 8383        );
 8384    });
 8385
 8386    cx.assert_editor_state(indoc! {"
 8387        foo bar bazˇ
 8388        fooˇ
 8389        foo bar
 8390    "});
 8391
 8392    cx.update_editor(|editor, window, cx| {
 8393        editor.add_selection_below(
 8394            &AddSelectionBelow {
 8395                skip_soft_wrap: true,
 8396            },
 8397            window,
 8398            cx,
 8399        );
 8400    });
 8401
 8402    cx.assert_editor_state(indoc! {"
 8403        foo bar bazˇ
 8404        fooˇ
 8405        foo barˇ
 8406    "});
 8407
 8408    cx.set_state(indoc! {"
 8409        foo bar baz
 8410        foo
 8411        foo barˇ
 8412    "});
 8413
 8414    cx.update_editor(|editor, window, cx| {
 8415        editor.add_selection_above(
 8416            &AddSelectionAbove {
 8417                skip_soft_wrap: true,
 8418            },
 8419            window,
 8420            cx,
 8421        );
 8422    });
 8423
 8424    cx.assert_editor_state(indoc! {"
 8425        foo bar baz
 8426        fooˇ
 8427        foo barˇ
 8428    "});
 8429
 8430    cx.update_editor(|editor, window, cx| {
 8431        editor.add_selection_above(
 8432            &AddSelectionAbove {
 8433                skip_soft_wrap: true,
 8434            },
 8435            window,
 8436            cx,
 8437        );
 8438    });
 8439
 8440    cx.assert_editor_state(indoc! {"
 8441        foo barˇ baz
 8442        fooˇ
 8443        foo barˇ
 8444    "});
 8445}
 8446
 8447#[gpui::test]
 8448async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8449    init_test(cx, |_| {});
 8450    let mut cx = EditorTestContext::new(cx).await;
 8451
 8452    cx.set_state(indoc!(
 8453        r#"line onˇe
 8454           liˇne two
 8455           line three
 8456           line four"#
 8457    ));
 8458
 8459    cx.update_editor(|editor, window, cx| {
 8460        editor.add_selection_below(&Default::default(), window, cx);
 8461    });
 8462
 8463    // test multiple cursors expand in the same direction
 8464    cx.assert_editor_state(indoc!(
 8465        r#"line onˇe
 8466           liˇne twˇo
 8467           liˇne three
 8468           line four"#
 8469    ));
 8470
 8471    cx.update_editor(|editor, window, cx| {
 8472        editor.add_selection_below(&Default::default(), window, cx);
 8473    });
 8474
 8475    cx.update_editor(|editor, window, cx| {
 8476        editor.add_selection_below(&Default::default(), window, cx);
 8477    });
 8478
 8479    // test multiple cursors expand below overflow
 8480    cx.assert_editor_state(indoc!(
 8481        r#"line onˇe
 8482           liˇne twˇo
 8483           liˇne thˇree
 8484           liˇne foˇur"#
 8485    ));
 8486
 8487    cx.update_editor(|editor, window, cx| {
 8488        editor.add_selection_above(&Default::default(), window, cx);
 8489    });
 8490
 8491    // test multiple cursors retrieves back correctly
 8492    cx.assert_editor_state(indoc!(
 8493        r#"line onˇe
 8494           liˇne twˇo
 8495           liˇne thˇree
 8496           line four"#
 8497    ));
 8498
 8499    cx.update_editor(|editor, window, cx| {
 8500        editor.add_selection_above(&Default::default(), window, cx);
 8501    });
 8502
 8503    cx.update_editor(|editor, window, cx| {
 8504        editor.add_selection_above(&Default::default(), window, cx);
 8505    });
 8506
 8507    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8508    cx.assert_editor_state(indoc!(
 8509        r#"liˇne onˇe
 8510           liˇne two
 8511           line three
 8512           line four"#
 8513    ));
 8514
 8515    cx.update_editor(|editor, window, cx| {
 8516        editor.undo_selection(&Default::default(), window, cx);
 8517    });
 8518
 8519    // test undo
 8520    cx.assert_editor_state(indoc!(
 8521        r#"line onˇe
 8522           liˇne twˇo
 8523           line three
 8524           line four"#
 8525    ));
 8526
 8527    cx.update_editor(|editor, window, cx| {
 8528        editor.redo_selection(&Default::default(), window, cx);
 8529    });
 8530
 8531    // test redo
 8532    cx.assert_editor_state(indoc!(
 8533        r#"liˇne onˇe
 8534           liˇne two
 8535           line three
 8536           line four"#
 8537    ));
 8538
 8539    cx.set_state(indoc!(
 8540        r#"abcd
 8541           ef«ghˇ»
 8542           ijkl
 8543           «mˇ»nop"#
 8544    ));
 8545
 8546    cx.update_editor(|editor, window, cx| {
 8547        editor.add_selection_above(&Default::default(), window, cx);
 8548    });
 8549
 8550    // test multiple selections expand in the same direction
 8551    cx.assert_editor_state(indoc!(
 8552        r#"ab«cdˇ»
 8553           ef«ghˇ»
 8554           «iˇ»jkl
 8555           «mˇ»nop"#
 8556    ));
 8557
 8558    cx.update_editor(|editor, window, cx| {
 8559        editor.add_selection_above(&Default::default(), window, cx);
 8560    });
 8561
 8562    // test multiple selection upward overflow
 8563    cx.assert_editor_state(indoc!(
 8564        r#"ab«cdˇ»
 8565           «eˇ»f«ghˇ»
 8566           «iˇ»jkl
 8567           «mˇ»nop"#
 8568    ));
 8569
 8570    cx.update_editor(|editor, window, cx| {
 8571        editor.add_selection_below(&Default::default(), window, cx);
 8572    });
 8573
 8574    // test multiple selection retrieves back correctly
 8575    cx.assert_editor_state(indoc!(
 8576        r#"abcd
 8577           ef«ghˇ»
 8578           «iˇ»jkl
 8579           «mˇ»nop"#
 8580    ));
 8581
 8582    cx.update_editor(|editor, window, cx| {
 8583        editor.add_selection_below(&Default::default(), window, cx);
 8584    });
 8585
 8586    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8587    cx.assert_editor_state(indoc!(
 8588        r#"abcd
 8589           ef«ghˇ»
 8590           ij«klˇ»
 8591           «mˇ»nop"#
 8592    ));
 8593
 8594    cx.update_editor(|editor, window, cx| {
 8595        editor.undo_selection(&Default::default(), window, cx);
 8596    });
 8597
 8598    // test undo
 8599    cx.assert_editor_state(indoc!(
 8600        r#"abcd
 8601           ef«ghˇ»
 8602           «iˇ»jkl
 8603           «mˇ»nop"#
 8604    ));
 8605
 8606    cx.update_editor(|editor, window, cx| {
 8607        editor.redo_selection(&Default::default(), window, cx);
 8608    });
 8609
 8610    // test redo
 8611    cx.assert_editor_state(indoc!(
 8612        r#"abcd
 8613           ef«ghˇ»
 8614           ij«klˇ»
 8615           «mˇ»nop"#
 8616    ));
 8617}
 8618
 8619#[gpui::test]
 8620async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8621    init_test(cx, |_| {});
 8622    let mut cx = EditorTestContext::new(cx).await;
 8623
 8624    cx.set_state(indoc!(
 8625        r#"line onˇe
 8626           liˇne two
 8627           line three
 8628           line four"#
 8629    ));
 8630
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.add_selection_below(&Default::default(), window, cx);
 8633        editor.add_selection_below(&Default::default(), window, cx);
 8634        editor.add_selection_below(&Default::default(), window, cx);
 8635    });
 8636
 8637    // initial state with two multi cursor groups
 8638    cx.assert_editor_state(indoc!(
 8639        r#"line onˇe
 8640           liˇne twˇo
 8641           liˇne thˇree
 8642           liˇne foˇur"#
 8643    ));
 8644
 8645    // add single cursor in middle - simulate opt click
 8646    cx.update_editor(|editor, window, cx| {
 8647        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8648        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8649        editor.end_selection(window, cx);
 8650    });
 8651
 8652    cx.assert_editor_state(indoc!(
 8653        r#"line onˇe
 8654           liˇne twˇo
 8655           liˇneˇ thˇree
 8656           liˇne foˇur"#
 8657    ));
 8658
 8659    cx.update_editor(|editor, window, cx| {
 8660        editor.add_selection_above(&Default::default(), window, cx);
 8661    });
 8662
 8663    // test new added selection expands above and existing selection shrinks
 8664    cx.assert_editor_state(indoc!(
 8665        r#"line onˇe
 8666           liˇneˇ twˇo
 8667           liˇneˇ thˇree
 8668           line four"#
 8669    ));
 8670
 8671    cx.update_editor(|editor, window, cx| {
 8672        editor.add_selection_above(&Default::default(), window, cx);
 8673    });
 8674
 8675    // test new added selection expands above and existing selection shrinks
 8676    cx.assert_editor_state(indoc!(
 8677        r#"lineˇ onˇe
 8678           liˇneˇ twˇo
 8679           lineˇ three
 8680           line four"#
 8681    ));
 8682
 8683    // intial state with two selection groups
 8684    cx.set_state(indoc!(
 8685        r#"abcd
 8686           ef«ghˇ»
 8687           ijkl
 8688           «mˇ»nop"#
 8689    ));
 8690
 8691    cx.update_editor(|editor, window, cx| {
 8692        editor.add_selection_above(&Default::default(), window, cx);
 8693        editor.add_selection_above(&Default::default(), window, cx);
 8694    });
 8695
 8696    cx.assert_editor_state(indoc!(
 8697        r#"ab«cdˇ»
 8698           «eˇ»f«ghˇ»
 8699           «iˇ»jkl
 8700           «mˇ»nop"#
 8701    ));
 8702
 8703    // add single selection in middle - simulate opt drag
 8704    cx.update_editor(|editor, window, cx| {
 8705        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8706        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8707        editor.update_selection(
 8708            DisplayPoint::new(DisplayRow(2), 4),
 8709            0,
 8710            gpui::Point::<f32>::default(),
 8711            window,
 8712            cx,
 8713        );
 8714        editor.end_selection(window, cx);
 8715    });
 8716
 8717    cx.assert_editor_state(indoc!(
 8718        r#"ab«cdˇ»
 8719           «eˇ»f«ghˇ»
 8720           «iˇ»jk«lˇ»
 8721           «mˇ»nop"#
 8722    ));
 8723
 8724    cx.update_editor(|editor, window, cx| {
 8725        editor.add_selection_below(&Default::default(), window, cx);
 8726    });
 8727
 8728    // test new added selection expands below, others shrinks from above
 8729    cx.assert_editor_state(indoc!(
 8730        r#"abcd
 8731           ef«ghˇ»
 8732           «iˇ»jk«lˇ»
 8733           «mˇ»no«pˇ»"#
 8734    ));
 8735}
 8736
 8737#[gpui::test]
 8738async fn test_select_next(cx: &mut TestAppContext) {
 8739    init_test(cx, |_| {});
 8740    let mut cx = EditorTestContext::new(cx).await;
 8741
 8742    // Enable case sensitive search.
 8743    update_test_editor_settings(&mut cx, |settings| {
 8744        let mut search_settings = SearchSettingsContent::default();
 8745        search_settings.case_sensitive = Some(true);
 8746        settings.search = Some(search_settings);
 8747    });
 8748
 8749    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8750
 8751    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8752        .unwrap();
 8753    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8754
 8755    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8756        .unwrap();
 8757    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8758
 8759    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8760    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8761
 8762    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8763    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8764
 8765    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8766        .unwrap();
 8767    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8768
 8769    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8770        .unwrap();
 8771    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8772
 8773    // Test selection direction should be preserved
 8774    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8775
 8776    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8777        .unwrap();
 8778    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8779
 8780    // Test case sensitivity
 8781    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8782    cx.update_editor(|e, window, cx| {
 8783        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8784    });
 8785    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8786
 8787    // Disable case sensitive search.
 8788    update_test_editor_settings(&mut cx, |settings| {
 8789        let mut search_settings = SearchSettingsContent::default();
 8790        search_settings.case_sensitive = Some(false);
 8791        settings.search = Some(search_settings);
 8792    });
 8793
 8794    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8795    cx.update_editor(|e, window, cx| {
 8796        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8797        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8798    });
 8799    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8800}
 8801
 8802#[gpui::test]
 8803async fn test_select_all_matches(cx: &mut TestAppContext) {
 8804    init_test(cx, |_| {});
 8805    let mut cx = EditorTestContext::new(cx).await;
 8806
 8807    // Enable case sensitive search.
 8808    update_test_editor_settings(&mut cx, |settings| {
 8809        let mut search_settings = SearchSettingsContent::default();
 8810        search_settings.case_sensitive = Some(true);
 8811        settings.search = Some(search_settings);
 8812    });
 8813
 8814    // Test caret-only selections
 8815    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8816    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8817        .unwrap();
 8818    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8819
 8820    // Test left-to-right selections
 8821    cx.set_state("abc\n«abcˇ»\nabc");
 8822    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8823        .unwrap();
 8824    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8825
 8826    // Test right-to-left selections
 8827    cx.set_state("abc\n«ˇabc»\nabc");
 8828    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8829        .unwrap();
 8830    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8831
 8832    // Test selecting whitespace with caret selection
 8833    cx.set_state("abc\nˇ   abc\nabc");
 8834    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8835        .unwrap();
 8836    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8837
 8838    // Test selecting whitespace with left-to-right selection
 8839    cx.set_state("abc\n«ˇ  »abc\nabc");
 8840    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8841        .unwrap();
 8842    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8843
 8844    // Test no matches with right-to-left selection
 8845    cx.set_state("abc\n«  ˇ»abc\nabc");
 8846    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8847        .unwrap();
 8848    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8849
 8850    // Test with a single word and clip_at_line_ends=true (#29823)
 8851    cx.set_state("aˇbc");
 8852    cx.update_editor(|e, window, cx| {
 8853        e.set_clip_at_line_ends(true, cx);
 8854        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8855        e.set_clip_at_line_ends(false, cx);
 8856    });
 8857    cx.assert_editor_state("«abcˇ»");
 8858
 8859    // Test case sensitivity
 8860    cx.set_state("fˇoo\nFOO\nFoo");
 8861    cx.update_editor(|e, window, cx| {
 8862        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8863    });
 8864    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8865
 8866    // Disable case sensitive search.
 8867    update_test_editor_settings(&mut cx, |settings| {
 8868        let mut search_settings = SearchSettingsContent::default();
 8869        search_settings.case_sensitive = Some(false);
 8870        settings.search = Some(search_settings);
 8871    });
 8872
 8873    cx.set_state("fˇoo\nFOO\nFoo");
 8874    cx.update_editor(|e, window, cx| {
 8875        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8876    });
 8877    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8878}
 8879
 8880#[gpui::test]
 8881async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8882    init_test(cx, |_| {});
 8883
 8884    let mut cx = EditorTestContext::new(cx).await;
 8885
 8886    let large_body_1 = "\nd".repeat(200);
 8887    let large_body_2 = "\ne".repeat(200);
 8888
 8889    cx.set_state(&format!(
 8890        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8891    ));
 8892    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8893        let scroll_position = editor.scroll_position(cx);
 8894        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8895        scroll_position
 8896    });
 8897
 8898    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8899        .unwrap();
 8900    cx.assert_editor_state(&format!(
 8901        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8902    ));
 8903    let scroll_position_after_selection =
 8904        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8905    assert_eq!(
 8906        initial_scroll_position, scroll_position_after_selection,
 8907        "Scroll position should not change after selecting all matches"
 8908    );
 8909}
 8910
 8911#[gpui::test]
 8912async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8913    init_test(cx, |_| {});
 8914
 8915    let mut cx = EditorLspTestContext::new_rust(
 8916        lsp::ServerCapabilities {
 8917            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8918            ..Default::default()
 8919        },
 8920        cx,
 8921    )
 8922    .await;
 8923
 8924    cx.set_state(indoc! {"
 8925        line 1
 8926        line 2
 8927        linˇe 3
 8928        line 4
 8929        line 5
 8930    "});
 8931
 8932    // Make an edit
 8933    cx.update_editor(|editor, window, cx| {
 8934        editor.handle_input("X", window, cx);
 8935    });
 8936
 8937    // Move cursor to a different position
 8938    cx.update_editor(|editor, window, cx| {
 8939        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8940            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8941        });
 8942    });
 8943
 8944    cx.assert_editor_state(indoc! {"
 8945        line 1
 8946        line 2
 8947        linXe 3
 8948        line 4
 8949        liˇne 5
 8950    "});
 8951
 8952    cx.lsp
 8953        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8954            Ok(Some(vec![lsp::TextEdit::new(
 8955                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8956                "PREFIX ".to_string(),
 8957            )]))
 8958        });
 8959
 8960    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8961        .unwrap()
 8962        .await
 8963        .unwrap();
 8964
 8965    cx.assert_editor_state(indoc! {"
 8966        PREFIX line 1
 8967        line 2
 8968        linXe 3
 8969        line 4
 8970        liˇne 5
 8971    "});
 8972
 8973    // Undo formatting
 8974    cx.update_editor(|editor, window, cx| {
 8975        editor.undo(&Default::default(), window, cx);
 8976    });
 8977
 8978    // Verify cursor moved back to position after edit
 8979    cx.assert_editor_state(indoc! {"
 8980        line 1
 8981        line 2
 8982        linXˇe 3
 8983        line 4
 8984        line 5
 8985    "});
 8986}
 8987
 8988#[gpui::test]
 8989async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8990    init_test(cx, |_| {});
 8991
 8992    let mut cx = EditorTestContext::new(cx).await;
 8993
 8994    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8995    cx.update_editor(|editor, window, cx| {
 8996        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8997    });
 8998
 8999    cx.set_state(indoc! {"
 9000        line 1
 9001        line 2
 9002        linˇe 3
 9003        line 4
 9004        line 5
 9005        line 6
 9006        line 7
 9007        line 8
 9008        line 9
 9009        line 10
 9010    "});
 9011
 9012    let snapshot = cx.buffer_snapshot();
 9013    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9014
 9015    cx.update(|_, cx| {
 9016        provider.update(cx, |provider, _| {
 9017            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9018                id: None,
 9019                edits: vec![(edit_position..edit_position, "X".into())],
 9020                edit_preview: None,
 9021            }))
 9022        })
 9023    });
 9024
 9025    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9026    cx.update_editor(|editor, window, cx| {
 9027        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9028    });
 9029
 9030    cx.assert_editor_state(indoc! {"
 9031        line 1
 9032        line 2
 9033        lineXˇ 3
 9034        line 4
 9035        line 5
 9036        line 6
 9037        line 7
 9038        line 8
 9039        line 9
 9040        line 10
 9041    "});
 9042
 9043    cx.update_editor(|editor, window, cx| {
 9044        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9045            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9046        });
 9047    });
 9048
 9049    cx.assert_editor_state(indoc! {"
 9050        line 1
 9051        line 2
 9052        lineX 3
 9053        line 4
 9054        line 5
 9055        line 6
 9056        line 7
 9057        line 8
 9058        line 9
 9059        liˇne 10
 9060    "});
 9061
 9062    cx.update_editor(|editor, window, cx| {
 9063        editor.undo(&Default::default(), window, cx);
 9064    });
 9065
 9066    cx.assert_editor_state(indoc! {"
 9067        line 1
 9068        line 2
 9069        lineˇ 3
 9070        line 4
 9071        line 5
 9072        line 6
 9073        line 7
 9074        line 8
 9075        line 9
 9076        line 10
 9077    "});
 9078}
 9079
 9080#[gpui::test]
 9081async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9082    init_test(cx, |_| {});
 9083
 9084    let mut cx = EditorTestContext::new(cx).await;
 9085    cx.set_state(
 9086        r#"let foo = 2;
 9087lˇet foo = 2;
 9088let fooˇ = 2;
 9089let foo = 2;
 9090let foo = ˇ2;"#,
 9091    );
 9092
 9093    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9094        .unwrap();
 9095    cx.assert_editor_state(
 9096        r#"let foo = 2;
 9097«letˇ» foo = 2;
 9098let «fooˇ» = 2;
 9099let foo = 2;
 9100let foo = «2ˇ»;"#,
 9101    );
 9102
 9103    // noop for multiple selections with different contents
 9104    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9105        .unwrap();
 9106    cx.assert_editor_state(
 9107        r#"let foo = 2;
 9108«letˇ» foo = 2;
 9109let «fooˇ» = 2;
 9110let foo = 2;
 9111let foo = «2ˇ»;"#,
 9112    );
 9113
 9114    // Test last selection direction should be preserved
 9115    cx.set_state(
 9116        r#"let foo = 2;
 9117let foo = 2;
 9118let «fooˇ» = 2;
 9119let «ˇfoo» = 2;
 9120let foo = 2;"#,
 9121    );
 9122
 9123    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9124        .unwrap();
 9125    cx.assert_editor_state(
 9126        r#"let foo = 2;
 9127let foo = 2;
 9128let «fooˇ» = 2;
 9129let «ˇfoo» = 2;
 9130let «ˇfoo» = 2;"#,
 9131    );
 9132}
 9133
 9134#[gpui::test]
 9135async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9136    init_test(cx, |_| {});
 9137
 9138    let mut cx =
 9139        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9140
 9141    cx.assert_editor_state(indoc! {"
 9142        ˇbbb
 9143        ccc
 9144
 9145        bbb
 9146        ccc
 9147        "});
 9148    cx.dispatch_action(SelectPrevious::default());
 9149    cx.assert_editor_state(indoc! {"
 9150                «bbbˇ»
 9151                ccc
 9152
 9153                bbb
 9154                ccc
 9155                "});
 9156    cx.dispatch_action(SelectPrevious::default());
 9157    cx.assert_editor_state(indoc! {"
 9158                «bbbˇ»
 9159                ccc
 9160
 9161                «bbbˇ»
 9162                ccc
 9163                "});
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9172
 9173    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9174        .unwrap();
 9175    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9176
 9177    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9178        .unwrap();
 9179    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9180
 9181    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9182    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9183
 9184    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9185    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9186
 9187    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9188        .unwrap();
 9189    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9190
 9191    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9192        .unwrap();
 9193    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9194}
 9195
 9196#[gpui::test]
 9197async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9198    init_test(cx, |_| {});
 9199
 9200    let mut cx = EditorTestContext::new(cx).await;
 9201    cx.set_state("");
 9202
 9203    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9204        .unwrap();
 9205    cx.assert_editor_state("«aˇ»");
 9206    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9207        .unwrap();
 9208    cx.assert_editor_state("«aˇ»");
 9209}
 9210
 9211#[gpui::test]
 9212async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9213    init_test(cx, |_| {});
 9214
 9215    let mut cx = EditorTestContext::new(cx).await;
 9216    cx.set_state(
 9217        r#"let foo = 2;
 9218lˇet foo = 2;
 9219let fooˇ = 2;
 9220let foo = 2;
 9221let foo = ˇ2;"#,
 9222    );
 9223
 9224    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9225        .unwrap();
 9226    cx.assert_editor_state(
 9227        r#"let foo = 2;
 9228«letˇ» foo = 2;
 9229let «fooˇ» = 2;
 9230let foo = 2;
 9231let foo = «2ˇ»;"#,
 9232    );
 9233
 9234    // noop for multiple selections with different contents
 9235    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9236        .unwrap();
 9237    cx.assert_editor_state(
 9238        r#"let foo = 2;
 9239«letˇ» foo = 2;
 9240let «fooˇ» = 2;
 9241let foo = 2;
 9242let foo = «2ˇ»;"#,
 9243    );
 9244}
 9245
 9246#[gpui::test]
 9247async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9248    init_test(cx, |_| {});
 9249    let mut cx = EditorTestContext::new(cx).await;
 9250
 9251    // Enable case sensitive search.
 9252    update_test_editor_settings(&mut cx, |settings| {
 9253        let mut search_settings = SearchSettingsContent::default();
 9254        search_settings.case_sensitive = Some(true);
 9255        settings.search = Some(search_settings);
 9256    });
 9257
 9258    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9259
 9260    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9261        .unwrap();
 9262    // selection direction is preserved
 9263    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9264
 9265    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9266        .unwrap();
 9267    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9268
 9269    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9270    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9271
 9272    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9273    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9274
 9275    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9276        .unwrap();
 9277    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9278
 9279    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9280        .unwrap();
 9281    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9282
 9283    // Test case sensitivity
 9284    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9285    cx.update_editor(|e, window, cx| {
 9286        e.select_previous(&SelectPrevious::default(), window, cx)
 9287            .unwrap();
 9288        e.select_previous(&SelectPrevious::default(), window, cx)
 9289            .unwrap();
 9290    });
 9291    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9292
 9293    // Disable case sensitive search.
 9294    update_test_editor_settings(&mut cx, |settings| {
 9295        let mut search_settings = SearchSettingsContent::default();
 9296        search_settings.case_sensitive = Some(false);
 9297        settings.search = Some(search_settings);
 9298    });
 9299
 9300    cx.set_state("foo\nFOO\n«ˇFoo»");
 9301    cx.update_editor(|e, window, cx| {
 9302        e.select_previous(&SelectPrevious::default(), window, cx)
 9303            .unwrap();
 9304        e.select_previous(&SelectPrevious::default(), window, cx)
 9305            .unwrap();
 9306    });
 9307    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9308}
 9309
 9310#[gpui::test]
 9311async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9312    init_test(cx, |_| {});
 9313
 9314    let language = Arc::new(Language::new(
 9315        LanguageConfig::default(),
 9316        Some(tree_sitter_rust::LANGUAGE.into()),
 9317    ));
 9318
 9319    let text = r#"
 9320        use mod1::mod2::{mod3, mod4};
 9321
 9322        fn fn_1(param1: bool, param2: &str) {
 9323            let var1 = "text";
 9324        }
 9325    "#
 9326    .unindent();
 9327
 9328    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9329    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9330    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9331
 9332    editor
 9333        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9334        .await;
 9335
 9336    editor.update_in(cx, |editor, window, cx| {
 9337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9338            s.select_display_ranges([
 9339                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9340                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9341                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9342            ]);
 9343        });
 9344        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9345    });
 9346    editor.update(cx, |editor, cx| {
 9347        assert_text_with_selections(
 9348            editor,
 9349            indoc! {r#"
 9350                use mod1::mod2::{mod3, «mod4ˇ»};
 9351
 9352                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9353                    let var1 = "«ˇtext»";
 9354                }
 9355            "#},
 9356            cx,
 9357        );
 9358    });
 9359
 9360    editor.update_in(cx, |editor, window, cx| {
 9361        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9362    });
 9363    editor.update(cx, |editor, cx| {
 9364        assert_text_with_selections(
 9365            editor,
 9366            indoc! {r#"
 9367                use mod1::mod2::«{mod3, mod4}ˇ»;
 9368
 9369                «ˇfn fn_1(param1: bool, param2: &str) {
 9370                    let var1 = "text";
 9371 9372            "#},
 9373            cx,
 9374        );
 9375    });
 9376
 9377    editor.update_in(cx, |editor, window, cx| {
 9378        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9379    });
 9380    assert_eq!(
 9381        editor.update(cx, |editor, cx| editor
 9382            .selections
 9383            .display_ranges(&editor.display_snapshot(cx))),
 9384        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9385    );
 9386
 9387    // Trying to expand the selected syntax node one more time has no effect.
 9388    editor.update_in(cx, |editor, window, cx| {
 9389        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9390    });
 9391    assert_eq!(
 9392        editor.update(cx, |editor, cx| editor
 9393            .selections
 9394            .display_ranges(&editor.display_snapshot(cx))),
 9395        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9396    );
 9397
 9398    editor.update_in(cx, |editor, window, cx| {
 9399        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9400    });
 9401    editor.update(cx, |editor, cx| {
 9402        assert_text_with_selections(
 9403            editor,
 9404            indoc! {r#"
 9405                use mod1::mod2::«{mod3, mod4}ˇ»;
 9406
 9407                «ˇfn fn_1(param1: bool, param2: &str) {
 9408                    let var1 = "text";
 9409 9410            "#},
 9411            cx,
 9412        );
 9413    });
 9414
 9415    editor.update_in(cx, |editor, window, cx| {
 9416        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9417    });
 9418    editor.update(cx, |editor, cx| {
 9419        assert_text_with_selections(
 9420            editor,
 9421            indoc! {r#"
 9422                use mod1::mod2::{mod3, «mod4ˇ»};
 9423
 9424                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9425                    let var1 = "«ˇtext»";
 9426                }
 9427            "#},
 9428            cx,
 9429        );
 9430    });
 9431
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9434    });
 9435    editor.update(cx, |editor, cx| {
 9436        assert_text_with_selections(
 9437            editor,
 9438            indoc! {r#"
 9439                use mod1::mod2::{mod3, moˇd4};
 9440
 9441                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9442                    let var1 = "teˇxt";
 9443                }
 9444            "#},
 9445            cx,
 9446        );
 9447    });
 9448
 9449    // Trying to shrink the selected syntax node one more time has no effect.
 9450    editor.update_in(cx, |editor, window, cx| {
 9451        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9452    });
 9453    editor.update_in(cx, |editor, _, cx| {
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, moˇd4};
 9458
 9459                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9460                    let var1 = "teˇxt";
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465    });
 9466
 9467    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9468    // a fold.
 9469    editor.update_in(cx, |editor, window, cx| {
 9470        editor.fold_creases(
 9471            vec![
 9472                Crease::simple(
 9473                    Point::new(0, 21)..Point::new(0, 24),
 9474                    FoldPlaceholder::test(),
 9475                ),
 9476                Crease::simple(
 9477                    Point::new(3, 20)..Point::new(3, 22),
 9478                    FoldPlaceholder::test(),
 9479                ),
 9480            ],
 9481            true,
 9482            window,
 9483            cx,
 9484        );
 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                use mod1::mod2::«{mod3, mod4}ˇ»;
 9492
 9493                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9494                    let var1 = "«ˇtext»";
 9495                }
 9496            "#},
 9497            cx,
 9498        );
 9499    });
 9500}
 9501
 9502#[gpui::test]
 9503async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9504    init_test(cx, |_| {});
 9505
 9506    let language = Arc::new(Language::new(
 9507        LanguageConfig::default(),
 9508        Some(tree_sitter_rust::LANGUAGE.into()),
 9509    ));
 9510
 9511    let text = "let a = 2;";
 9512
 9513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9516
 9517    editor
 9518        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9519        .await;
 9520
 9521    // Test case 1: Cursor at end of word
 9522    editor.update_in(cx, |editor, window, cx| {
 9523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9524            s.select_display_ranges([
 9525                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9526            ]);
 9527        });
 9528    });
 9529    editor.update(cx, |editor, cx| {
 9530        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9531    });
 9532    editor.update_in(cx, |editor, window, cx| {
 9533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9534    });
 9535    editor.update(cx, |editor, cx| {
 9536        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9537    });
 9538    editor.update_in(cx, |editor, window, cx| {
 9539        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9540    });
 9541    editor.update(cx, |editor, cx| {
 9542        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9543    });
 9544
 9545    // Test case 2: Cursor at end of statement
 9546    editor.update_in(cx, |editor, window, cx| {
 9547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9548            s.select_display_ranges([
 9549                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9550            ]);
 9551        });
 9552    });
 9553    editor.update(cx, |editor, cx| {
 9554        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9555    });
 9556    editor.update_in(cx, |editor, window, cx| {
 9557        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9558    });
 9559    editor.update(cx, |editor, cx| {
 9560        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9561    });
 9562}
 9563
 9564#[gpui::test]
 9565async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9566    init_test(cx, |_| {});
 9567
 9568    let language = Arc::new(Language::new(
 9569        LanguageConfig {
 9570            name: "JavaScript".into(),
 9571            ..Default::default()
 9572        },
 9573        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9574    ));
 9575
 9576    let text = r#"
 9577        let a = {
 9578            key: "value",
 9579        };
 9580    "#
 9581    .unindent();
 9582
 9583    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9584    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9585    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9586
 9587    editor
 9588        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9589        .await;
 9590
 9591    // Test case 1: Cursor after '{'
 9592    editor.update_in(cx, |editor, window, cx| {
 9593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9594            s.select_display_ranges([
 9595                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9596            ]);
 9597        });
 9598    });
 9599    editor.update(cx, |editor, cx| {
 9600        assert_text_with_selections(
 9601            editor,
 9602            indoc! {r#"
 9603                let a = {ˇ
 9604                    key: "value",
 9605                };
 9606            "#},
 9607            cx,
 9608        );
 9609    });
 9610    editor.update_in(cx, |editor, window, cx| {
 9611        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9612    });
 9613    editor.update(cx, |editor, cx| {
 9614        assert_text_with_selections(
 9615            editor,
 9616            indoc! {r#"
 9617                let a = «ˇ{
 9618                    key: "value",
 9619                }»;
 9620            "#},
 9621            cx,
 9622        );
 9623    });
 9624
 9625    // Test case 2: Cursor after ':'
 9626    editor.update_in(cx, |editor, window, cx| {
 9627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9628            s.select_display_ranges([
 9629                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9630            ]);
 9631        });
 9632    });
 9633    editor.update(cx, |editor, cx| {
 9634        assert_text_with_selections(
 9635            editor,
 9636            indoc! {r#"
 9637                let a = {
 9638                    key:ˇ "value",
 9639                };
 9640            "#},
 9641            cx,
 9642        );
 9643    });
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9646    });
 9647    editor.update(cx, |editor, cx| {
 9648        assert_text_with_selections(
 9649            editor,
 9650            indoc! {r#"
 9651                let a = {
 9652                    «ˇkey: "value"»,
 9653                };
 9654            "#},
 9655            cx,
 9656        );
 9657    });
 9658    editor.update_in(cx, |editor, window, cx| {
 9659        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9660    });
 9661    editor.update(cx, |editor, cx| {
 9662        assert_text_with_selections(
 9663            editor,
 9664            indoc! {r#"
 9665                let a = «ˇ{
 9666                    key: "value",
 9667                }»;
 9668            "#},
 9669            cx,
 9670        );
 9671    });
 9672
 9673    // Test case 3: Cursor after ','
 9674    editor.update_in(cx, |editor, window, cx| {
 9675        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9676            s.select_display_ranges([
 9677                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9678            ]);
 9679        });
 9680    });
 9681    editor.update(cx, |editor, cx| {
 9682        assert_text_with_selections(
 9683            editor,
 9684            indoc! {r#"
 9685                let a = {
 9686                    key: "value",ˇ
 9687                };
 9688            "#},
 9689            cx,
 9690        );
 9691    });
 9692    editor.update_in(cx, |editor, window, cx| {
 9693        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9694    });
 9695    editor.update(cx, |editor, cx| {
 9696        assert_text_with_selections(
 9697            editor,
 9698            indoc! {r#"
 9699                let a = «ˇ{
 9700                    key: "value",
 9701                }»;
 9702            "#},
 9703            cx,
 9704        );
 9705    });
 9706
 9707    // Test case 4: Cursor after ';'
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9710            s.select_display_ranges([
 9711                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9712            ]);
 9713        });
 9714    });
 9715    editor.update(cx, |editor, cx| {
 9716        assert_text_with_selections(
 9717            editor,
 9718            indoc! {r#"
 9719                let a = {
 9720                    key: "value",
 9721                };ˇ
 9722            "#},
 9723            cx,
 9724        );
 9725    });
 9726    editor.update_in(cx, |editor, window, cx| {
 9727        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9728    });
 9729    editor.update(cx, |editor, cx| {
 9730        assert_text_with_selections(
 9731            editor,
 9732            indoc! {r#"
 9733                «ˇlet a = {
 9734                    key: "value",
 9735                };
 9736                »"#},
 9737            cx,
 9738        );
 9739    });
 9740}
 9741
 9742#[gpui::test]
 9743async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9744    init_test(cx, |_| {});
 9745
 9746    let language = Arc::new(Language::new(
 9747        LanguageConfig::default(),
 9748        Some(tree_sitter_rust::LANGUAGE.into()),
 9749    ));
 9750
 9751    let text = r#"
 9752        use mod1::mod2::{mod3, mod4};
 9753
 9754        fn fn_1(param1: bool, param2: &str) {
 9755            let var1 = "hello world";
 9756        }
 9757    "#
 9758    .unindent();
 9759
 9760    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9761    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9762    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9763
 9764    editor
 9765        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9766        .await;
 9767
 9768    // Test 1: Cursor on a letter of a string word
 9769    editor.update_in(cx, |editor, window, cx| {
 9770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9771            s.select_display_ranges([
 9772                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9773            ]);
 9774        });
 9775    });
 9776    editor.update_in(cx, |editor, window, cx| {
 9777        assert_text_with_selections(
 9778            editor,
 9779            indoc! {r#"
 9780                use mod1::mod2::{mod3, mod4};
 9781
 9782                fn fn_1(param1: bool, param2: &str) {
 9783                    let var1 = "hˇello world";
 9784                }
 9785            "#},
 9786            cx,
 9787        );
 9788        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9789        assert_text_with_selections(
 9790            editor,
 9791            indoc! {r#"
 9792                use mod1::mod2::{mod3, mod4};
 9793
 9794                fn fn_1(param1: bool, param2: &str) {
 9795                    let var1 = "«ˇhello» world";
 9796                }
 9797            "#},
 9798            cx,
 9799        );
 9800    });
 9801
 9802    // Test 2: Partial selection within a word
 9803    editor.update_in(cx, |editor, window, cx| {
 9804        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9805            s.select_display_ranges([
 9806                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9807            ]);
 9808        });
 9809    });
 9810    editor.update_in(cx, |editor, window, cx| {
 9811        assert_text_with_selections(
 9812            editor,
 9813            indoc! {r#"
 9814                use mod1::mod2::{mod3, mod4};
 9815
 9816                fn fn_1(param1: bool, param2: &str) {
 9817                    let var1 = "h«elˇ»lo world";
 9818                }
 9819            "#},
 9820            cx,
 9821        );
 9822        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9823        assert_text_with_selections(
 9824            editor,
 9825            indoc! {r#"
 9826                use mod1::mod2::{mod3, mod4};
 9827
 9828                fn fn_1(param1: bool, param2: &str) {
 9829                    let var1 = "«ˇhello» world";
 9830                }
 9831            "#},
 9832            cx,
 9833        );
 9834    });
 9835
 9836    // Test 3: Complete word already selected
 9837    editor.update_in(cx, |editor, window, cx| {
 9838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9839            s.select_display_ranges([
 9840                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9841            ]);
 9842        });
 9843    });
 9844    editor.update_in(cx, |editor, window, cx| {
 9845        assert_text_with_selections(
 9846            editor,
 9847            indoc! {r#"
 9848                use mod1::mod2::{mod3, mod4};
 9849
 9850                fn fn_1(param1: bool, param2: &str) {
 9851                    let var1 = "«helloˇ» world";
 9852                }
 9853            "#},
 9854            cx,
 9855        );
 9856        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9857        assert_text_with_selections(
 9858            editor,
 9859            indoc! {r#"
 9860                use mod1::mod2::{mod3, mod4};
 9861
 9862                fn fn_1(param1: bool, param2: &str) {
 9863                    let var1 = "«hello worldˇ»";
 9864                }
 9865            "#},
 9866            cx,
 9867        );
 9868    });
 9869
 9870    // Test 4: Selection spanning across words
 9871    editor.update_in(cx, |editor, window, cx| {
 9872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9873            s.select_display_ranges([
 9874                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9875            ]);
 9876        });
 9877    });
 9878    editor.update_in(cx, |editor, window, cx| {
 9879        assert_text_with_selections(
 9880            editor,
 9881            indoc! {r#"
 9882                use mod1::mod2::{mod3, mod4};
 9883
 9884                fn fn_1(param1: bool, param2: &str) {
 9885                    let var1 = "hel«lo woˇ»rld";
 9886                }
 9887            "#},
 9888            cx,
 9889        );
 9890        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9891        assert_text_with_selections(
 9892            editor,
 9893            indoc! {r#"
 9894                use mod1::mod2::{mod3, mod4};
 9895
 9896                fn fn_1(param1: bool, param2: &str) {
 9897                    let var1 = "«ˇhello world»";
 9898                }
 9899            "#},
 9900            cx,
 9901        );
 9902    });
 9903
 9904    // Test 5: Expansion beyond string
 9905    editor.update_in(cx, |editor, window, cx| {
 9906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9907        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9908        assert_text_with_selections(
 9909            editor,
 9910            indoc! {r#"
 9911                use mod1::mod2::{mod3, mod4};
 9912
 9913                fn fn_1(param1: bool, param2: &str) {
 9914                    «ˇlet var1 = "hello world";»
 9915                }
 9916            "#},
 9917            cx,
 9918        );
 9919    });
 9920}
 9921
 9922#[gpui::test]
 9923async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9924    init_test(cx, |_| {});
 9925
 9926    let mut cx = EditorTestContext::new(cx).await;
 9927
 9928    let language = Arc::new(Language::new(
 9929        LanguageConfig::default(),
 9930        Some(tree_sitter_rust::LANGUAGE.into()),
 9931    ));
 9932
 9933    cx.update_buffer(|buffer, cx| {
 9934        buffer.set_language(Some(language), cx);
 9935    });
 9936
 9937    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9938    cx.update_editor(|editor, window, cx| {
 9939        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9940    });
 9941
 9942    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9943
 9944    cx.set_state(indoc! { r#"fn a() {
 9945          // what
 9946          // a
 9947          // ˇlong
 9948          // method
 9949          // I
 9950          // sure
 9951          // hope
 9952          // it
 9953          // works
 9954    }"# });
 9955
 9956    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9957    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9958    cx.update(|_, cx| {
 9959        multi_buffer.update(cx, |multi_buffer, cx| {
 9960            multi_buffer.set_excerpts_for_path(
 9961                PathKey::for_buffer(&buffer, cx),
 9962                buffer,
 9963                [Point::new(1, 0)..Point::new(1, 0)],
 9964                3,
 9965                cx,
 9966            );
 9967        });
 9968    });
 9969
 9970    let editor2 = cx.new_window_entity(|window, cx| {
 9971        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9972    });
 9973
 9974    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9975    cx.update_editor(|editor, window, cx| {
 9976        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9977            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9978        })
 9979    });
 9980
 9981    cx.assert_editor_state(indoc! { "
 9982        fn a() {
 9983              // what
 9984              // a
 9985        ˇ      // long
 9986              // method"});
 9987
 9988    cx.update_editor(|editor, window, cx| {
 9989        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9990    });
 9991
 9992    // Although we could potentially make the action work when the syntax node
 9993    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9994    // did. Maybe we could also expand the excerpt to contain the range?
 9995    cx.assert_editor_state(indoc! { "
 9996        fn a() {
 9997              // what
 9998              // a
 9999        ˇ      // long
10000              // method"});
10001}
10002
10003#[gpui::test]
10004async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10005    init_test(cx, |_| {});
10006
10007    let base_text = r#"
10008        impl A {
10009            // this is an uncommitted comment
10010
10011            fn b() {
10012                c();
10013            }
10014
10015            // this is another uncommitted comment
10016
10017            fn d() {
10018                // e
10019                // f
10020            }
10021        }
10022
10023        fn g() {
10024            // h
10025        }
10026    "#
10027    .unindent();
10028
10029    let text = r#"
10030        ˇimpl A {
10031
10032            fn b() {
10033                c();
10034            }
10035
10036            fn d() {
10037                // e
10038                // f
10039            }
10040        }
10041
10042        fn g() {
10043            // h
10044        }
10045    "#
10046    .unindent();
10047
10048    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10049    cx.set_state(&text);
10050    cx.set_head_text(&base_text);
10051    cx.update_editor(|editor, window, cx| {
10052        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10053    });
10054
10055    cx.assert_state_with_diff(
10056        "
10057        ˇimpl A {
10058      -     // this is an uncommitted comment
10059
10060            fn b() {
10061                c();
10062            }
10063
10064      -     // this is another uncommitted comment
10065      -
10066            fn d() {
10067                // e
10068                // f
10069            }
10070        }
10071
10072        fn g() {
10073            // h
10074        }
10075    "
10076        .unindent(),
10077    );
10078
10079    let expected_display_text = "
10080        impl A {
10081            // this is an uncommitted comment
10082
10083            fn b() {
1008410085            }
10086
10087            // this is another uncommitted comment
10088
10089            fn d() {
1009010091            }
10092        }
10093
10094        fn g() {
1009510096        }
10097        "
10098    .unindent();
10099
10100    cx.update_editor(|editor, window, cx| {
10101        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10102        assert_eq!(editor.display_text(cx), expected_display_text);
10103    });
10104}
10105
10106#[gpui::test]
10107async fn test_autoindent(cx: &mut TestAppContext) {
10108    init_test(cx, |_| {});
10109
10110    let language = Arc::new(
10111        Language::new(
10112            LanguageConfig {
10113                brackets: BracketPairConfig {
10114                    pairs: vec![
10115                        BracketPair {
10116                            start: "{".to_string(),
10117                            end: "}".to_string(),
10118                            close: false,
10119                            surround: false,
10120                            newline: true,
10121                        },
10122                        BracketPair {
10123                            start: "(".to_string(),
10124                            end: ")".to_string(),
10125                            close: false,
10126                            surround: false,
10127                            newline: true,
10128                        },
10129                    ],
10130                    ..Default::default()
10131                },
10132                ..Default::default()
10133            },
10134            Some(tree_sitter_rust::LANGUAGE.into()),
10135        )
10136        .with_indents_query(
10137            r#"
10138                (_ "(" ")" @end) @indent
10139                (_ "{" "}" @end) @indent
10140            "#,
10141        )
10142        .unwrap(),
10143    );
10144
10145    let text = "fn a() {}";
10146
10147    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10148    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10149    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10150    editor
10151        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10152        .await;
10153
10154    editor.update_in(cx, |editor, window, cx| {
10155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10156            s.select_ranges([
10157                MultiBufferOffset(5)..MultiBufferOffset(5),
10158                MultiBufferOffset(8)..MultiBufferOffset(8),
10159                MultiBufferOffset(9)..MultiBufferOffset(9),
10160            ])
10161        });
10162        editor.newline(&Newline, window, cx);
10163        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10164        assert_eq!(
10165            editor.selections.ranges(&editor.display_snapshot(cx)),
10166            &[
10167                Point::new(1, 4)..Point::new(1, 4),
10168                Point::new(3, 4)..Point::new(3, 4),
10169                Point::new(5, 0)..Point::new(5, 0)
10170            ]
10171        );
10172    });
10173}
10174
10175#[gpui::test]
10176async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10177    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10178
10179    let language = Arc::new(
10180        Language::new(
10181            LanguageConfig {
10182                brackets: BracketPairConfig {
10183                    pairs: vec![
10184                        BracketPair {
10185                            start: "{".to_string(),
10186                            end: "}".to_string(),
10187                            close: false,
10188                            surround: false,
10189                            newline: true,
10190                        },
10191                        BracketPair {
10192                            start: "(".to_string(),
10193                            end: ")".to_string(),
10194                            close: false,
10195                            surround: false,
10196                            newline: true,
10197                        },
10198                    ],
10199                    ..Default::default()
10200                },
10201                ..Default::default()
10202            },
10203            Some(tree_sitter_rust::LANGUAGE.into()),
10204        )
10205        .with_indents_query(
10206            r#"
10207                (_ "(" ")" @end) @indent
10208                (_ "{" "}" @end) @indent
10209            "#,
10210        )
10211        .unwrap(),
10212    );
10213
10214    let text = "fn a() {}";
10215
10216    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10217    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10218    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10219    editor
10220        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10221        .await;
10222
10223    editor.update_in(cx, |editor, window, cx| {
10224        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10225            s.select_ranges([
10226                MultiBufferOffset(5)..MultiBufferOffset(5),
10227                MultiBufferOffset(8)..MultiBufferOffset(8),
10228                MultiBufferOffset(9)..MultiBufferOffset(9),
10229            ])
10230        });
10231        editor.newline(&Newline, window, cx);
10232        assert_eq!(
10233            editor.text(cx),
10234            indoc!(
10235                "
10236                fn a(
10237
10238                ) {
10239
10240                }
10241                "
10242            )
10243        );
10244        assert_eq!(
10245            editor.selections.ranges(&editor.display_snapshot(cx)),
10246            &[
10247                Point::new(1, 0)..Point::new(1, 0),
10248                Point::new(3, 0)..Point::new(3, 0),
10249                Point::new(5, 0)..Point::new(5, 0)
10250            ]
10251        );
10252    });
10253}
10254
10255#[gpui::test]
10256async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10257    init_test(cx, |settings| {
10258        settings.defaults.auto_indent = Some(true);
10259        settings.languages.0.insert(
10260            "python".into(),
10261            LanguageSettingsContent {
10262                auto_indent: Some(false),
10263                ..Default::default()
10264            },
10265        );
10266    });
10267
10268    let mut cx = EditorTestContext::new(cx).await;
10269
10270    let injected_language = Arc::new(
10271        Language::new(
10272            LanguageConfig {
10273                brackets: BracketPairConfig {
10274                    pairs: vec![
10275                        BracketPair {
10276                            start: "{".to_string(),
10277                            end: "}".to_string(),
10278                            close: false,
10279                            surround: false,
10280                            newline: true,
10281                        },
10282                        BracketPair {
10283                            start: "(".to_string(),
10284                            end: ")".to_string(),
10285                            close: true,
10286                            surround: false,
10287                            newline: true,
10288                        },
10289                    ],
10290                    ..Default::default()
10291                },
10292                name: "python".into(),
10293                ..Default::default()
10294            },
10295            Some(tree_sitter_python::LANGUAGE.into()),
10296        )
10297        .with_indents_query(
10298            r#"
10299                (_ "(" ")" @end) @indent
10300                (_ "{" "}" @end) @indent
10301            "#,
10302        )
10303        .unwrap(),
10304    );
10305
10306    let language = Arc::new(
10307        Language::new(
10308            LanguageConfig {
10309                brackets: BracketPairConfig {
10310                    pairs: vec![
10311                        BracketPair {
10312                            start: "{".to_string(),
10313                            end: "}".to_string(),
10314                            close: false,
10315                            surround: false,
10316                            newline: true,
10317                        },
10318                        BracketPair {
10319                            start: "(".to_string(),
10320                            end: ")".to_string(),
10321                            close: true,
10322                            surround: false,
10323                            newline: true,
10324                        },
10325                    ],
10326                    ..Default::default()
10327                },
10328                name: LanguageName::new_static("rust"),
10329                ..Default::default()
10330            },
10331            Some(tree_sitter_rust::LANGUAGE.into()),
10332        )
10333        .with_indents_query(
10334            r#"
10335                (_ "(" ")" @end) @indent
10336                (_ "{" "}" @end) @indent
10337            "#,
10338        )
10339        .unwrap()
10340        .with_injection_query(
10341            r#"
10342            (macro_invocation
10343                macro: (identifier) @_macro_name
10344                (token_tree) @injection.content
10345                (#set! injection.language "python"))
10346           "#,
10347        )
10348        .unwrap(),
10349    );
10350
10351    cx.language_registry().add(injected_language);
10352    cx.language_registry().add(language.clone());
10353
10354    cx.update_buffer(|buffer, cx| {
10355        buffer.set_language(Some(language), cx);
10356    });
10357
10358    cx.set_state(r#"struct A {ˇ}"#);
10359
10360    cx.update_editor(|editor, window, cx| {
10361        editor.newline(&Default::default(), window, cx);
10362    });
10363
10364    cx.assert_editor_state(indoc!(
10365        "struct A {
10366            ˇ
10367        }"
10368    ));
10369
10370    cx.set_state(r#"select_biased!(ˇ)"#);
10371
10372    cx.update_editor(|editor, window, cx| {
10373        editor.newline(&Default::default(), window, cx);
10374        editor.handle_input("def ", window, cx);
10375        editor.handle_input("(", window, cx);
10376        editor.newline(&Default::default(), window, cx);
10377        editor.handle_input("a", window, cx);
10378    });
10379
10380    cx.assert_editor_state(indoc!(
10381        "select_biased!(
10382        def (
1038310384        )
10385        )"
10386    ));
10387}
10388
10389#[gpui::test]
10390async fn test_autoindent_selections(cx: &mut TestAppContext) {
10391    init_test(cx, |_| {});
10392
10393    {
10394        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10395        cx.set_state(indoc! {"
10396            impl A {
10397
10398                fn b() {}
10399
10400            «fn c() {
10401
10402            }ˇ»
10403            }
10404        "});
10405
10406        cx.update_editor(|editor, window, cx| {
10407            editor.autoindent(&Default::default(), window, cx);
10408        });
10409        cx.wait_for_autoindent_applied().await;
10410
10411        cx.assert_editor_state(indoc! {"
10412            impl A {
10413
10414                fn b() {}
10415
10416                «fn c() {
10417
10418                }ˇ»
10419            }
10420        "});
10421    }
10422
10423    {
10424        let mut cx = EditorTestContext::new_multibuffer(
10425            cx,
10426            [indoc! { "
10427                impl A {
10428                «
10429                // a
10430                fn b(){}
10431                »
10432                «
10433                    }
10434                    fn c(){}
10435                »
10436            "}],
10437        );
10438
10439        let buffer = cx.update_editor(|editor, _, cx| {
10440            let buffer = editor.buffer().update(cx, |buffer, _| {
10441                buffer.all_buffers().iter().next().unwrap().clone()
10442            });
10443            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10444            buffer
10445        });
10446
10447        cx.run_until_parked();
10448        cx.update_editor(|editor, window, cx| {
10449            editor.select_all(&Default::default(), window, cx);
10450            editor.autoindent(&Default::default(), window, cx)
10451        });
10452        cx.run_until_parked();
10453
10454        cx.update(|_, cx| {
10455            assert_eq!(
10456                buffer.read(cx).text(),
10457                indoc! { "
10458                    impl A {
10459
10460                        // a
10461                        fn b(){}
10462
10463
10464                    }
10465                    fn c(){}
10466
10467                " }
10468            )
10469        });
10470    }
10471}
10472
10473#[gpui::test]
10474async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10475    init_test(cx, |_| {});
10476
10477    let mut cx = EditorTestContext::new(cx).await;
10478
10479    let language = Arc::new(Language::new(
10480        LanguageConfig {
10481            brackets: BracketPairConfig {
10482                pairs: vec![
10483                    BracketPair {
10484                        start: "{".to_string(),
10485                        end: "}".to_string(),
10486                        close: true,
10487                        surround: true,
10488                        newline: true,
10489                    },
10490                    BracketPair {
10491                        start: "(".to_string(),
10492                        end: ")".to_string(),
10493                        close: true,
10494                        surround: true,
10495                        newline: true,
10496                    },
10497                    BracketPair {
10498                        start: "/*".to_string(),
10499                        end: " */".to_string(),
10500                        close: true,
10501                        surround: true,
10502                        newline: true,
10503                    },
10504                    BracketPair {
10505                        start: "[".to_string(),
10506                        end: "]".to_string(),
10507                        close: false,
10508                        surround: false,
10509                        newline: true,
10510                    },
10511                    BracketPair {
10512                        start: "\"".to_string(),
10513                        end: "\"".to_string(),
10514                        close: true,
10515                        surround: true,
10516                        newline: false,
10517                    },
10518                    BracketPair {
10519                        start: "<".to_string(),
10520                        end: ">".to_string(),
10521                        close: false,
10522                        surround: true,
10523                        newline: true,
10524                    },
10525                ],
10526                ..Default::default()
10527            },
10528            autoclose_before: "})]".to_string(),
10529            ..Default::default()
10530        },
10531        Some(tree_sitter_rust::LANGUAGE.into()),
10532    ));
10533
10534    cx.language_registry().add(language.clone());
10535    cx.update_buffer(|buffer, cx| {
10536        buffer.set_language(Some(language), cx);
10537    });
10538
10539    cx.set_state(
10540        &r#"
10541            🏀ˇ
10542            εˇ
10543            ❤️ˇ
10544        "#
10545        .unindent(),
10546    );
10547
10548    // autoclose multiple nested brackets at multiple cursors
10549    cx.update_editor(|editor, window, cx| {
10550        editor.handle_input("{", window, cx);
10551        editor.handle_input("{", window, cx);
10552        editor.handle_input("{", window, cx);
10553    });
10554    cx.assert_editor_state(
10555        &"
10556            🏀{{{ˇ}}}
10557            ε{{{ˇ}}}
10558            ❤️{{{ˇ}}}
10559        "
10560        .unindent(),
10561    );
10562
10563    // insert a different closing bracket
10564    cx.update_editor(|editor, window, cx| {
10565        editor.handle_input(")", window, cx);
10566    });
10567    cx.assert_editor_state(
10568        &"
10569            🏀{{{)ˇ}}}
10570            ε{{{)ˇ}}}
10571            ❤️{{{)ˇ}}}
10572        "
10573        .unindent(),
10574    );
10575
10576    // skip over the auto-closed brackets when typing a closing bracket
10577    cx.update_editor(|editor, window, cx| {
10578        editor.move_right(&MoveRight, window, cx);
10579        editor.handle_input("}", window, cx);
10580        editor.handle_input("}", window, cx);
10581        editor.handle_input("}", window, cx);
10582    });
10583    cx.assert_editor_state(
10584        &"
10585            🏀{{{)}}}}ˇ
10586            ε{{{)}}}}ˇ
10587            ❤️{{{)}}}}ˇ
10588        "
10589        .unindent(),
10590    );
10591
10592    // autoclose multi-character pairs
10593    cx.set_state(
10594        &"
10595            ˇ
10596            ˇ
10597        "
10598        .unindent(),
10599    );
10600    cx.update_editor(|editor, window, cx| {
10601        editor.handle_input("/", window, cx);
10602        editor.handle_input("*", window, cx);
10603    });
10604    cx.assert_editor_state(
10605        &"
10606            /*ˇ */
10607            /*ˇ */
10608        "
10609        .unindent(),
10610    );
10611
10612    // one cursor autocloses a multi-character pair, one cursor
10613    // does not autoclose.
10614    cx.set_state(
10615        &"
1061610617            ˇ
10618        "
10619        .unindent(),
10620    );
10621    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10622    cx.assert_editor_state(
10623        &"
10624            /*ˇ */
1062510626        "
10627        .unindent(),
10628    );
10629
10630    // Don't autoclose if the next character isn't whitespace and isn't
10631    // listed in the language's "autoclose_before" section.
10632    cx.set_state("ˇa b");
10633    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10634    cx.assert_editor_state("{ˇa b");
10635
10636    // Don't autoclose if `close` is false for the bracket pair
10637    cx.set_state("ˇ");
10638    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10639    cx.assert_editor_state("");
10640
10641    // Surround with brackets if text is selected
10642    cx.set_state("«aˇ» b");
10643    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10644    cx.assert_editor_state("{«aˇ»} b");
10645
10646    // Autoclose when not immediately after a word character
10647    cx.set_state("a ˇ");
10648    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10649    cx.assert_editor_state("a \"ˇ\"");
10650
10651    // Autoclose pair where the start and end characters are the same
10652    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10653    cx.assert_editor_state("a \"\"ˇ");
10654
10655    // Don't autoclose when immediately after a word character
10656    cx.set_state("");
10657    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10658    cx.assert_editor_state("a\"ˇ");
10659
10660    // Do autoclose when after a non-word character
10661    cx.set_state("");
10662    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10663    cx.assert_editor_state("{\"ˇ\"");
10664
10665    // Non identical pairs autoclose regardless of preceding character
10666    cx.set_state("");
10667    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10668    cx.assert_editor_state("a{ˇ}");
10669
10670    // Don't autoclose pair if autoclose is disabled
10671    cx.set_state("ˇ");
10672    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10673    cx.assert_editor_state("");
10674
10675    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10676    cx.set_state("«aˇ» b");
10677    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10678    cx.assert_editor_state("<«aˇ»> b");
10679}
10680
10681#[gpui::test]
10682async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10683    init_test(cx, |settings| {
10684        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10685    });
10686
10687    let mut cx = EditorTestContext::new(cx).await;
10688
10689    let language = Arc::new(Language::new(
10690        LanguageConfig {
10691            brackets: BracketPairConfig {
10692                pairs: vec![
10693                    BracketPair {
10694                        start: "{".to_string(),
10695                        end: "}".to_string(),
10696                        close: true,
10697                        surround: true,
10698                        newline: true,
10699                    },
10700                    BracketPair {
10701                        start: "(".to_string(),
10702                        end: ")".to_string(),
10703                        close: true,
10704                        surround: true,
10705                        newline: true,
10706                    },
10707                    BracketPair {
10708                        start: "[".to_string(),
10709                        end: "]".to_string(),
10710                        close: false,
10711                        surround: false,
10712                        newline: true,
10713                    },
10714                ],
10715                ..Default::default()
10716            },
10717            autoclose_before: "})]".to_string(),
10718            ..Default::default()
10719        },
10720        Some(tree_sitter_rust::LANGUAGE.into()),
10721    ));
10722
10723    cx.language_registry().add(language.clone());
10724    cx.update_buffer(|buffer, cx| {
10725        buffer.set_language(Some(language), cx);
10726    });
10727
10728    cx.set_state(
10729        &"
10730            ˇ
10731            ˇ
10732            ˇ
10733        "
10734        .unindent(),
10735    );
10736
10737    // ensure only matching closing brackets are skipped over
10738    cx.update_editor(|editor, window, cx| {
10739        editor.handle_input("}", window, cx);
10740        editor.move_left(&MoveLeft, window, cx);
10741        editor.handle_input(")", window, cx);
10742        editor.move_left(&MoveLeft, window, cx);
10743    });
10744    cx.assert_editor_state(
10745        &"
10746            ˇ)}
10747            ˇ)}
10748            ˇ)}
10749        "
10750        .unindent(),
10751    );
10752
10753    // skip-over closing brackets at multiple cursors
10754    cx.update_editor(|editor, window, cx| {
10755        editor.handle_input(")", window, cx);
10756        editor.handle_input("}", window, cx);
10757    });
10758    cx.assert_editor_state(
10759        &"
10760            )}ˇ
10761            )}ˇ
10762            )}ˇ
10763        "
10764        .unindent(),
10765    );
10766
10767    // ignore non-close brackets
10768    cx.update_editor(|editor, window, cx| {
10769        editor.handle_input("]", window, cx);
10770        editor.move_left(&MoveLeft, window, cx);
10771        editor.handle_input("]", window, cx);
10772    });
10773    cx.assert_editor_state(
10774        &"
10775            )}]ˇ]
10776            )}]ˇ]
10777            )}]ˇ]
10778        "
10779        .unindent(),
10780    );
10781}
10782
10783#[gpui::test]
10784async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10785    init_test(cx, |_| {});
10786
10787    let mut cx = EditorTestContext::new(cx).await;
10788
10789    let html_language = Arc::new(
10790        Language::new(
10791            LanguageConfig {
10792                name: "HTML".into(),
10793                brackets: BracketPairConfig {
10794                    pairs: vec![
10795                        BracketPair {
10796                            start: "<".into(),
10797                            end: ">".into(),
10798                            close: true,
10799                            ..Default::default()
10800                        },
10801                        BracketPair {
10802                            start: "{".into(),
10803                            end: "}".into(),
10804                            close: true,
10805                            ..Default::default()
10806                        },
10807                        BracketPair {
10808                            start: "(".into(),
10809                            end: ")".into(),
10810                            close: true,
10811                            ..Default::default()
10812                        },
10813                    ],
10814                    ..Default::default()
10815                },
10816                autoclose_before: "})]>".into(),
10817                ..Default::default()
10818            },
10819            Some(tree_sitter_html::LANGUAGE.into()),
10820        )
10821        .with_injection_query(
10822            r#"
10823            (script_element
10824                (raw_text) @injection.content
10825                (#set! injection.language "javascript"))
10826            "#,
10827        )
10828        .unwrap(),
10829    );
10830
10831    let javascript_language = Arc::new(Language::new(
10832        LanguageConfig {
10833            name: "JavaScript".into(),
10834            brackets: BracketPairConfig {
10835                pairs: vec![
10836                    BracketPair {
10837                        start: "/*".into(),
10838                        end: " */".into(),
10839                        close: true,
10840                        ..Default::default()
10841                    },
10842                    BracketPair {
10843                        start: "{".into(),
10844                        end: "}".into(),
10845                        close: true,
10846                        ..Default::default()
10847                    },
10848                    BracketPair {
10849                        start: "(".into(),
10850                        end: ")".into(),
10851                        close: true,
10852                        ..Default::default()
10853                    },
10854                ],
10855                ..Default::default()
10856            },
10857            autoclose_before: "})]>".into(),
10858            ..Default::default()
10859        },
10860        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10861    ));
10862
10863    cx.language_registry().add(html_language.clone());
10864    cx.language_registry().add(javascript_language);
10865    cx.executor().run_until_parked();
10866
10867    cx.update_buffer(|buffer, cx| {
10868        buffer.set_language(Some(html_language), cx);
10869    });
10870
10871    cx.set_state(
10872        &r#"
10873            <body>ˇ
10874                <script>
10875                    var x = 1;ˇ
10876                </script>
10877            </body>ˇ
10878        "#
10879        .unindent(),
10880    );
10881
10882    // Precondition: different languages are active at different locations.
10883    cx.update_editor(|editor, window, cx| {
10884        let snapshot = editor.snapshot(window, cx);
10885        let cursors = editor
10886            .selections
10887            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10888        let languages = cursors
10889            .iter()
10890            .map(|c| snapshot.language_at(c.start).unwrap().name())
10891            .collect::<Vec<_>>();
10892        assert_eq!(
10893            languages,
10894            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10895        );
10896    });
10897
10898    // Angle brackets autoclose in HTML, but not JavaScript.
10899    cx.update_editor(|editor, window, cx| {
10900        editor.handle_input("<", window, cx);
10901        editor.handle_input("a", window, cx);
10902    });
10903    cx.assert_editor_state(
10904        &r#"
10905            <body><aˇ>
10906                <script>
10907                    var x = 1;<aˇ
10908                </script>
10909            </body><aˇ>
10910        "#
10911        .unindent(),
10912    );
10913
10914    // Curly braces and parens autoclose in both HTML and JavaScript.
10915    cx.update_editor(|editor, window, cx| {
10916        editor.handle_input(" b=", window, cx);
10917        editor.handle_input("{", window, cx);
10918        editor.handle_input("c", window, cx);
10919        editor.handle_input("(", window, cx);
10920    });
10921    cx.assert_editor_state(
10922        &r#"
10923            <body><a b={c(ˇ)}>
10924                <script>
10925                    var x = 1;<a b={c(ˇ)}
10926                </script>
10927            </body><a b={c(ˇ)}>
10928        "#
10929        .unindent(),
10930    );
10931
10932    // Brackets that were already autoclosed are skipped.
10933    cx.update_editor(|editor, window, cx| {
10934        editor.handle_input(")", window, cx);
10935        editor.handle_input("d", window, cx);
10936        editor.handle_input("}", window, cx);
10937    });
10938    cx.assert_editor_state(
10939        &r#"
10940            <body><a b={c()d}ˇ>
10941                <script>
10942                    var x = 1;<a b={c()d}ˇ
10943                </script>
10944            </body><a b={c()d}ˇ>
10945        "#
10946        .unindent(),
10947    );
10948    cx.update_editor(|editor, window, cx| {
10949        editor.handle_input(">", window, cx);
10950    });
10951    cx.assert_editor_state(
10952        &r#"
10953            <body><a b={c()d}>ˇ
10954                <script>
10955                    var x = 1;<a b={c()d}>ˇ
10956                </script>
10957            </body><a b={c()d}>ˇ
10958        "#
10959        .unindent(),
10960    );
10961
10962    // Reset
10963    cx.set_state(
10964        &r#"
10965            <body>ˇ
10966                <script>
10967                    var x = 1;ˇ
10968                </script>
10969            </body>ˇ
10970        "#
10971        .unindent(),
10972    );
10973
10974    cx.update_editor(|editor, window, cx| {
10975        editor.handle_input("<", window, cx);
10976    });
10977    cx.assert_editor_state(
10978        &r#"
10979            <body><ˇ>
10980                <script>
10981                    var x = 1;<ˇ
10982                </script>
10983            </body><ˇ>
10984        "#
10985        .unindent(),
10986    );
10987
10988    // When backspacing, the closing angle brackets are removed.
10989    cx.update_editor(|editor, window, cx| {
10990        editor.backspace(&Backspace, window, cx);
10991    });
10992    cx.assert_editor_state(
10993        &r#"
10994            <body>ˇ
10995                <script>
10996                    var x = 1;ˇ
10997                </script>
10998            </body>ˇ
10999        "#
11000        .unindent(),
11001    );
11002
11003    // Block comments autoclose in JavaScript, but not HTML.
11004    cx.update_editor(|editor, window, cx| {
11005        editor.handle_input("/", window, cx);
11006        editor.handle_input("*", window, cx);
11007    });
11008    cx.assert_editor_state(
11009        &r#"
11010            <body>/*ˇ
11011                <script>
11012                    var x = 1;/*ˇ */
11013                </script>
11014            </body>/*ˇ
11015        "#
11016        .unindent(),
11017    );
11018}
11019
11020#[gpui::test]
11021async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11022    init_test(cx, |_| {});
11023
11024    let mut cx = EditorTestContext::new(cx).await;
11025
11026    let rust_language = Arc::new(
11027        Language::new(
11028            LanguageConfig {
11029                name: "Rust".into(),
11030                brackets: serde_json::from_value(json!([
11031                    { "start": "{", "end": "}", "close": true, "newline": true },
11032                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11033                ]))
11034                .unwrap(),
11035                autoclose_before: "})]>".into(),
11036                ..Default::default()
11037            },
11038            Some(tree_sitter_rust::LANGUAGE.into()),
11039        )
11040        .with_override_query("(string_literal) @string")
11041        .unwrap(),
11042    );
11043
11044    cx.language_registry().add(rust_language.clone());
11045    cx.update_buffer(|buffer, cx| {
11046        buffer.set_language(Some(rust_language), cx);
11047    });
11048
11049    cx.set_state(
11050        &r#"
11051            let x = ˇ
11052        "#
11053        .unindent(),
11054    );
11055
11056    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11057    cx.update_editor(|editor, window, cx| {
11058        editor.handle_input("\"", window, cx);
11059    });
11060    cx.assert_editor_state(
11061        &r#"
11062            let x = "ˇ"
11063        "#
11064        .unindent(),
11065    );
11066
11067    // Inserting another quotation mark. The cursor moves across the existing
11068    // automatically-inserted quotation mark.
11069    cx.update_editor(|editor, window, cx| {
11070        editor.handle_input("\"", window, cx);
11071    });
11072    cx.assert_editor_state(
11073        &r#"
11074            let x = ""ˇ
11075        "#
11076        .unindent(),
11077    );
11078
11079    // Reset
11080    cx.set_state(
11081        &r#"
11082            let x = ˇ
11083        "#
11084        .unindent(),
11085    );
11086
11087    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11088    cx.update_editor(|editor, window, cx| {
11089        editor.handle_input("\"", window, cx);
11090        editor.handle_input(" ", window, cx);
11091        editor.move_left(&Default::default(), window, cx);
11092        editor.handle_input("\\", window, cx);
11093        editor.handle_input("\"", window, cx);
11094    });
11095    cx.assert_editor_state(
11096        &r#"
11097            let x = "\"ˇ "
11098        "#
11099        .unindent(),
11100    );
11101
11102    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11103    // mark. Nothing is inserted.
11104    cx.update_editor(|editor, window, cx| {
11105        editor.move_right(&Default::default(), window, cx);
11106        editor.handle_input("\"", window, cx);
11107    });
11108    cx.assert_editor_state(
11109        &r#"
11110            let x = "\" "ˇ
11111        "#
11112        .unindent(),
11113    );
11114}
11115
11116#[gpui::test]
11117async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11118    init_test(cx, |_| {});
11119
11120    let mut cx = EditorTestContext::new(cx).await;
11121    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11122
11123    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11124
11125    // Double quote inside single-quoted string
11126    cx.set_state(indoc! {r#"
11127        def main():
11128            items = ['"', ˇ]
11129    "#});
11130    cx.update_editor(|editor, window, cx| {
11131        editor.handle_input("\"", window, cx);
11132    });
11133    cx.assert_editor_state(indoc! {r#"
11134        def main():
11135            items = ['"', "ˇ"]
11136    "#});
11137
11138    // Two double quotes inside single-quoted string
11139    cx.set_state(indoc! {r#"
11140        def main():
11141            items = ['""', ˇ]
11142    "#});
11143    cx.update_editor(|editor, window, cx| {
11144        editor.handle_input("\"", window, cx);
11145    });
11146    cx.assert_editor_state(indoc! {r#"
11147        def main():
11148            items = ['""', "ˇ"]
11149    "#});
11150
11151    // Single quote inside double-quoted string
11152    cx.set_state(indoc! {r#"
11153        def main():
11154            items = ["'", ˇ]
11155    "#});
11156    cx.update_editor(|editor, window, cx| {
11157        editor.handle_input("'", window, cx);
11158    });
11159    cx.assert_editor_state(indoc! {r#"
11160        def main():
11161            items = ["'", 'ˇ']
11162    "#});
11163
11164    // Two single quotes inside double-quoted string
11165    cx.set_state(indoc! {r#"
11166        def main():
11167            items = ["''", ˇ]
11168    "#});
11169    cx.update_editor(|editor, window, cx| {
11170        editor.handle_input("'", window, cx);
11171    });
11172    cx.assert_editor_state(indoc! {r#"
11173        def main():
11174            items = ["''", 'ˇ']
11175    "#});
11176
11177    // Mixed quotes on same line
11178    cx.set_state(indoc! {r#"
11179        def main():
11180            items = ['"""', "'''''", ˇ]
11181    "#});
11182    cx.update_editor(|editor, window, cx| {
11183        editor.handle_input("\"", window, cx);
11184    });
11185    cx.assert_editor_state(indoc! {r#"
11186        def main():
11187            items = ['"""', "'''''", "ˇ"]
11188    "#});
11189    cx.update_editor(|editor, window, cx| {
11190        editor.move_right(&MoveRight, window, cx);
11191    });
11192    cx.update_editor(|editor, window, cx| {
11193        editor.handle_input(", ", window, cx);
11194    });
11195    cx.update_editor(|editor, window, cx| {
11196        editor.handle_input("'", window, cx);
11197    });
11198    cx.assert_editor_state(indoc! {r#"
11199        def main():
11200            items = ['"""', "'''''", "", 'ˇ']
11201    "#});
11202}
11203
11204#[gpui::test]
11205async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11206    init_test(cx, |_| {});
11207
11208    let mut cx = EditorTestContext::new(cx).await;
11209    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11210    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11211
11212    cx.set_state(indoc! {r#"
11213        def main():
11214            items = ["🎉", ˇ]
11215    "#});
11216    cx.update_editor(|editor, window, cx| {
11217        editor.handle_input("\"", window, cx);
11218    });
11219    cx.assert_editor_state(indoc! {r#"
11220        def main():
11221            items = ["🎉", "ˇ"]
11222    "#});
11223}
11224
11225#[gpui::test]
11226async fn test_surround_with_pair(cx: &mut TestAppContext) {
11227    init_test(cx, |_| {});
11228
11229    let language = Arc::new(Language::new(
11230        LanguageConfig {
11231            brackets: BracketPairConfig {
11232                pairs: vec![
11233                    BracketPair {
11234                        start: "{".to_string(),
11235                        end: "}".to_string(),
11236                        close: true,
11237                        surround: true,
11238                        newline: true,
11239                    },
11240                    BracketPair {
11241                        start: "/* ".to_string(),
11242                        end: "*/".to_string(),
11243                        close: true,
11244                        surround: true,
11245                        ..Default::default()
11246                    },
11247                ],
11248                ..Default::default()
11249            },
11250            ..Default::default()
11251        },
11252        Some(tree_sitter_rust::LANGUAGE.into()),
11253    ));
11254
11255    let text = r#"
11256        a
11257        b
11258        c
11259    "#
11260    .unindent();
11261
11262    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11263    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11264    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11265    editor
11266        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11267        .await;
11268
11269    editor.update_in(cx, |editor, window, cx| {
11270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11271            s.select_display_ranges([
11272                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11273                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11274                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11275            ])
11276        });
11277
11278        editor.handle_input("{", window, cx);
11279        editor.handle_input("{", window, cx);
11280        editor.handle_input("{", window, cx);
11281        assert_eq!(
11282            editor.text(cx),
11283            "
11284                {{{a}}}
11285                {{{b}}}
11286                {{{c}}}
11287            "
11288            .unindent()
11289        );
11290        assert_eq!(
11291            display_ranges(editor, cx),
11292            [
11293                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11294                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11295                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11296            ]
11297        );
11298
11299        editor.undo(&Undo, window, cx);
11300        editor.undo(&Undo, window, cx);
11301        editor.undo(&Undo, window, cx);
11302        assert_eq!(
11303            editor.text(cx),
11304            "
11305                a
11306                b
11307                c
11308            "
11309            .unindent()
11310        );
11311        assert_eq!(
11312            display_ranges(editor, cx),
11313            [
11314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11315                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11316                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11317            ]
11318        );
11319
11320        // Ensure inserting the first character of a multi-byte bracket pair
11321        // doesn't surround the selections with the bracket.
11322        editor.handle_input("/", window, cx);
11323        assert_eq!(
11324            editor.text(cx),
11325            "
11326                /
11327                /
11328                /
11329            "
11330            .unindent()
11331        );
11332        assert_eq!(
11333            display_ranges(editor, cx),
11334            [
11335                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11336                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11337                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11338            ]
11339        );
11340
11341        editor.undo(&Undo, window, cx);
11342        assert_eq!(
11343            editor.text(cx),
11344            "
11345                a
11346                b
11347                c
11348            "
11349            .unindent()
11350        );
11351        assert_eq!(
11352            display_ranges(editor, cx),
11353            [
11354                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11355                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11356                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11357            ]
11358        );
11359
11360        // Ensure inserting the last character of a multi-byte bracket pair
11361        // doesn't surround the selections with the bracket.
11362        editor.handle_input("*", window, cx);
11363        assert_eq!(
11364            editor.text(cx),
11365            "
11366                *
11367                *
11368                *
11369            "
11370            .unindent()
11371        );
11372        assert_eq!(
11373            display_ranges(editor, cx),
11374            [
11375                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11376                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11377                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11378            ]
11379        );
11380    });
11381}
11382
11383#[gpui::test]
11384async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11385    init_test(cx, |_| {});
11386
11387    let language = Arc::new(Language::new(
11388        LanguageConfig {
11389            brackets: BracketPairConfig {
11390                pairs: vec![BracketPair {
11391                    start: "{".to_string(),
11392                    end: "}".to_string(),
11393                    close: true,
11394                    surround: true,
11395                    newline: true,
11396                }],
11397                ..Default::default()
11398            },
11399            autoclose_before: "}".to_string(),
11400            ..Default::default()
11401        },
11402        Some(tree_sitter_rust::LANGUAGE.into()),
11403    ));
11404
11405    let text = r#"
11406        a
11407        b
11408        c
11409    "#
11410    .unindent();
11411
11412    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11413    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11414    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11415    editor
11416        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11417        .await;
11418
11419    editor.update_in(cx, |editor, window, cx| {
11420        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11421            s.select_ranges([
11422                Point::new(0, 1)..Point::new(0, 1),
11423                Point::new(1, 1)..Point::new(1, 1),
11424                Point::new(2, 1)..Point::new(2, 1),
11425            ])
11426        });
11427
11428        editor.handle_input("{", window, cx);
11429        editor.handle_input("{", window, cx);
11430        editor.handle_input("_", window, cx);
11431        assert_eq!(
11432            editor.text(cx),
11433            "
11434                a{{_}}
11435                b{{_}}
11436                c{{_}}
11437            "
11438            .unindent()
11439        );
11440        assert_eq!(
11441            editor
11442                .selections
11443                .ranges::<Point>(&editor.display_snapshot(cx)),
11444            [
11445                Point::new(0, 4)..Point::new(0, 4),
11446                Point::new(1, 4)..Point::new(1, 4),
11447                Point::new(2, 4)..Point::new(2, 4)
11448            ]
11449        );
11450
11451        editor.backspace(&Default::default(), window, cx);
11452        editor.backspace(&Default::default(), window, cx);
11453        assert_eq!(
11454            editor.text(cx),
11455            "
11456                a{}
11457                b{}
11458                c{}
11459            "
11460            .unindent()
11461        );
11462        assert_eq!(
11463            editor
11464                .selections
11465                .ranges::<Point>(&editor.display_snapshot(cx)),
11466            [
11467                Point::new(0, 2)..Point::new(0, 2),
11468                Point::new(1, 2)..Point::new(1, 2),
11469                Point::new(2, 2)..Point::new(2, 2)
11470            ]
11471        );
11472
11473        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11474        assert_eq!(
11475            editor.text(cx),
11476            "
11477                a
11478                b
11479                c
11480            "
11481            .unindent()
11482        );
11483        assert_eq!(
11484            editor
11485                .selections
11486                .ranges::<Point>(&editor.display_snapshot(cx)),
11487            [
11488                Point::new(0, 1)..Point::new(0, 1),
11489                Point::new(1, 1)..Point::new(1, 1),
11490                Point::new(2, 1)..Point::new(2, 1)
11491            ]
11492        );
11493    });
11494}
11495
11496#[gpui::test]
11497async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11498    init_test(cx, |settings| {
11499        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11500    });
11501
11502    let mut cx = EditorTestContext::new(cx).await;
11503
11504    let language = Arc::new(Language::new(
11505        LanguageConfig {
11506            brackets: BracketPairConfig {
11507                pairs: vec![
11508                    BracketPair {
11509                        start: "{".to_string(),
11510                        end: "}".to_string(),
11511                        close: true,
11512                        surround: true,
11513                        newline: true,
11514                    },
11515                    BracketPair {
11516                        start: "(".to_string(),
11517                        end: ")".to_string(),
11518                        close: true,
11519                        surround: true,
11520                        newline: true,
11521                    },
11522                    BracketPair {
11523                        start: "[".to_string(),
11524                        end: "]".to_string(),
11525                        close: false,
11526                        surround: true,
11527                        newline: true,
11528                    },
11529                ],
11530                ..Default::default()
11531            },
11532            autoclose_before: "})]".to_string(),
11533            ..Default::default()
11534        },
11535        Some(tree_sitter_rust::LANGUAGE.into()),
11536    ));
11537
11538    cx.language_registry().add(language.clone());
11539    cx.update_buffer(|buffer, cx| {
11540        buffer.set_language(Some(language), cx);
11541    });
11542
11543    cx.set_state(
11544        &"
11545            {(ˇ)}
11546            [[ˇ]]
11547            {(ˇ)}
11548        "
11549        .unindent(),
11550    );
11551
11552    cx.update_editor(|editor, window, cx| {
11553        editor.backspace(&Default::default(), window, cx);
11554        editor.backspace(&Default::default(), window, cx);
11555    });
11556
11557    cx.assert_editor_state(
11558        &"
11559            ˇ
11560            ˇ]]
11561            ˇ
11562        "
11563        .unindent(),
11564    );
11565
11566    cx.update_editor(|editor, window, cx| {
11567        editor.handle_input("{", window, cx);
11568        editor.handle_input("{", window, cx);
11569        editor.move_right(&MoveRight, window, cx);
11570        editor.move_right(&MoveRight, window, cx);
11571        editor.move_left(&MoveLeft, window, cx);
11572        editor.move_left(&MoveLeft, window, cx);
11573        editor.backspace(&Default::default(), window, cx);
11574    });
11575
11576    cx.assert_editor_state(
11577        &"
11578            {ˇ}
11579            {ˇ}]]
11580            {ˇ}
11581        "
11582        .unindent(),
11583    );
11584
11585    cx.update_editor(|editor, window, cx| {
11586        editor.backspace(&Default::default(), window, cx);
11587    });
11588
11589    cx.assert_editor_state(
11590        &"
11591            ˇ
11592            ˇ]]
11593            ˇ
11594        "
11595        .unindent(),
11596    );
11597}
11598
11599#[gpui::test]
11600async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11601    init_test(cx, |_| {});
11602
11603    let language = Arc::new(Language::new(
11604        LanguageConfig::default(),
11605        Some(tree_sitter_rust::LANGUAGE.into()),
11606    ));
11607
11608    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11609    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11610    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11611    editor
11612        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11613        .await;
11614
11615    editor.update_in(cx, |editor, window, cx| {
11616        editor.set_auto_replace_emoji_shortcode(true);
11617
11618        editor.handle_input("Hello ", window, cx);
11619        editor.handle_input(":wave", window, cx);
11620        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11621
11622        editor.handle_input(":", window, cx);
11623        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11624
11625        editor.handle_input(" :smile", window, cx);
11626        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11627
11628        editor.handle_input(":", window, cx);
11629        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11630
11631        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11632        editor.handle_input(":wave", window, cx);
11633        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11634
11635        editor.handle_input(":", window, cx);
11636        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11637
11638        editor.handle_input(":1", window, cx);
11639        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11640
11641        editor.handle_input(":", window, cx);
11642        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11643
11644        // Ensure shortcode does not get replaced when it is part of a word
11645        editor.handle_input(" Test:wave", window, cx);
11646        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11647
11648        editor.handle_input(":", window, cx);
11649        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11650
11651        editor.set_auto_replace_emoji_shortcode(false);
11652
11653        // Ensure shortcode does not get replaced when auto replace is off
11654        editor.handle_input(" :wave", window, cx);
11655        assert_eq!(
11656            editor.text(cx),
11657            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11658        );
11659
11660        editor.handle_input(":", window, cx);
11661        assert_eq!(
11662            editor.text(cx),
11663            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11664        );
11665    });
11666}
11667
11668#[gpui::test]
11669async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11670    init_test(cx, |_| {});
11671
11672    let (text, insertion_ranges) = marked_text_ranges(
11673        indoc! {"
11674            ˇ
11675        "},
11676        false,
11677    );
11678
11679    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11680    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11681
11682    _ = editor.update_in(cx, |editor, window, cx| {
11683        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11684
11685        editor
11686            .insert_snippet(
11687                &insertion_ranges
11688                    .iter()
11689                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11690                    .collect::<Vec<_>>(),
11691                snippet,
11692                window,
11693                cx,
11694            )
11695            .unwrap();
11696
11697        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11698            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11699            assert_eq!(editor.text(cx), expected_text);
11700            assert_eq!(
11701                editor.selections.ranges(&editor.display_snapshot(cx)),
11702                selection_ranges
11703                    .iter()
11704                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11705                    .collect::<Vec<_>>()
11706            );
11707        }
11708
11709        assert(
11710            editor,
11711            cx,
11712            indoc! {"
11713            type «» =•
11714            "},
11715        );
11716
11717        assert!(editor.context_menu_visible(), "There should be a matches");
11718    });
11719}
11720
11721#[gpui::test]
11722async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11723    init_test(cx, |_| {});
11724
11725    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11726        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11727        assert_eq!(editor.text(cx), expected_text);
11728        assert_eq!(
11729            editor.selections.ranges(&editor.display_snapshot(cx)),
11730            selection_ranges
11731                .iter()
11732                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11733                .collect::<Vec<_>>()
11734        );
11735    }
11736
11737    let (text, insertion_ranges) = marked_text_ranges(
11738        indoc! {"
11739            ˇ
11740        "},
11741        false,
11742    );
11743
11744    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11745    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11746
11747    _ = editor.update_in(cx, |editor, window, cx| {
11748        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11749
11750        editor
11751            .insert_snippet(
11752                &insertion_ranges
11753                    .iter()
11754                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11755                    .collect::<Vec<_>>(),
11756                snippet,
11757                window,
11758                cx,
11759            )
11760            .unwrap();
11761
11762        assert_state(
11763            editor,
11764            cx,
11765            indoc! {"
11766            type «» = ;•
11767            "},
11768        );
11769
11770        assert!(
11771            editor.context_menu_visible(),
11772            "Context menu should be visible for placeholder choices"
11773        );
11774
11775        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11776
11777        assert_state(
11778            editor,
11779            cx,
11780            indoc! {"
11781            type  = «»;•
11782            "},
11783        );
11784
11785        assert!(
11786            !editor.context_menu_visible(),
11787            "Context menu should be hidden after moving to next tabstop"
11788        );
11789
11790        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11791
11792        assert_state(
11793            editor,
11794            cx,
11795            indoc! {"
11796            type  = ; ˇ
11797            "},
11798        );
11799
11800        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11801
11802        assert_state(
11803            editor,
11804            cx,
11805            indoc! {"
11806            type  = ; ˇ
11807            "},
11808        );
11809    });
11810
11811    _ = editor.update_in(cx, |editor, window, cx| {
11812        editor.select_all(&SelectAll, window, cx);
11813        editor.backspace(&Backspace, window, cx);
11814
11815        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11816        let insertion_ranges = editor
11817            .selections
11818            .all(&editor.display_snapshot(cx))
11819            .iter()
11820            .map(|s| s.range())
11821            .collect::<Vec<_>>();
11822
11823        editor
11824            .insert_snippet(&insertion_ranges, snippet, window, cx)
11825            .unwrap();
11826
11827        assert_state(editor, cx, "fn «» = value;•");
11828
11829        assert!(
11830            editor.context_menu_visible(),
11831            "Context menu should be visible for placeholder choices"
11832        );
11833
11834        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11835
11836        assert_state(editor, cx, "fn  = «valueˇ»;•");
11837
11838        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11839
11840        assert_state(editor, cx, "fn «» = value;•");
11841
11842        assert!(
11843            editor.context_menu_visible(),
11844            "Context menu should be visible again after returning to first tabstop"
11845        );
11846
11847        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11848
11849        assert_state(editor, cx, "fn «» = value;•");
11850    });
11851}
11852
11853#[gpui::test]
11854async fn test_snippets(cx: &mut TestAppContext) {
11855    init_test(cx, |_| {});
11856
11857    let mut cx = EditorTestContext::new(cx).await;
11858
11859    cx.set_state(indoc! {"
11860        a.ˇ b
11861        a.ˇ b
11862        a.ˇ b
11863    "});
11864
11865    cx.update_editor(|editor, window, cx| {
11866        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11867        let insertion_ranges = editor
11868            .selections
11869            .all(&editor.display_snapshot(cx))
11870            .iter()
11871            .map(|s| s.range())
11872            .collect::<Vec<_>>();
11873        editor
11874            .insert_snippet(&insertion_ranges, snippet, window, cx)
11875            .unwrap();
11876    });
11877
11878    cx.assert_editor_state(indoc! {"
11879        a.f(«oneˇ», two, «threeˇ») b
11880        a.f(«oneˇ», two, «threeˇ») b
11881        a.f(«oneˇ», two, «threeˇ») b
11882    "});
11883
11884    // Can't move earlier than the first tab stop
11885    cx.update_editor(|editor, window, cx| {
11886        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11887    });
11888    cx.assert_editor_state(indoc! {"
11889        a.f(«oneˇ», two, «threeˇ») b
11890        a.f(«oneˇ», two, «threeˇ») b
11891        a.f(«oneˇ», two, «threeˇ») b
11892    "});
11893
11894    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11895    cx.assert_editor_state(indoc! {"
11896        a.f(one, «twoˇ», three) b
11897        a.f(one, «twoˇ», three) b
11898        a.f(one, «twoˇ», three) b
11899    "});
11900
11901    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11902    cx.assert_editor_state(indoc! {"
11903        a.f(«oneˇ», two, «threeˇ») b
11904        a.f(«oneˇ», two, «threeˇ») b
11905        a.f(«oneˇ», two, «threeˇ») b
11906    "});
11907
11908    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11909    cx.assert_editor_state(indoc! {"
11910        a.f(one, «twoˇ», three) b
11911        a.f(one, «twoˇ», three) b
11912        a.f(one, «twoˇ», three) b
11913    "});
11914    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11915    cx.assert_editor_state(indoc! {"
11916        a.f(one, two, three)ˇ b
11917        a.f(one, two, three)ˇ b
11918        a.f(one, two, three)ˇ b
11919    "});
11920
11921    // As soon as the last tab stop is reached, snippet state is gone
11922    cx.update_editor(|editor, window, cx| {
11923        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11924    });
11925    cx.assert_editor_state(indoc! {"
11926        a.f(one, two, three)ˇ b
11927        a.f(one, two, three)ˇ b
11928        a.f(one, two, three)ˇ b
11929    "});
11930}
11931
11932#[gpui::test]
11933async fn test_snippet_indentation(cx: &mut TestAppContext) {
11934    init_test(cx, |_| {});
11935
11936    let mut cx = EditorTestContext::new(cx).await;
11937
11938    cx.update_editor(|editor, window, cx| {
11939        let snippet = Snippet::parse(indoc! {"
11940            /*
11941             * Multiline comment with leading indentation
11942             *
11943             * $1
11944             */
11945            $0"})
11946        .unwrap();
11947        let insertion_ranges = editor
11948            .selections
11949            .all(&editor.display_snapshot(cx))
11950            .iter()
11951            .map(|s| s.range())
11952            .collect::<Vec<_>>();
11953        editor
11954            .insert_snippet(&insertion_ranges, snippet, window, cx)
11955            .unwrap();
11956    });
11957
11958    cx.assert_editor_state(indoc! {"
11959        /*
11960         * Multiline comment with leading indentation
11961         *
11962         * ˇ
11963         */
11964    "});
11965
11966    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11967    cx.assert_editor_state(indoc! {"
11968        /*
11969         * Multiline comment with leading indentation
11970         *
11971         *•
11972         */
11973        ˇ"});
11974}
11975
11976#[gpui::test]
11977async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11978    init_test(cx, |_| {});
11979
11980    let mut cx = EditorTestContext::new(cx).await;
11981    cx.update_editor(|editor, _, cx| {
11982        editor.project().unwrap().update(cx, |project, cx| {
11983            project.snippets().update(cx, |snippets, _cx| {
11984                let snippet = project::snippet_provider::Snippet {
11985                    prefix: vec!["multi word".to_string()],
11986                    body: "this is many words".to_string(),
11987                    description: Some("description".to_string()),
11988                    name: "multi-word snippet test".to_string(),
11989                };
11990                snippets.add_snippet_for_test(
11991                    None,
11992                    PathBuf::from("test_snippets.json"),
11993                    vec![Arc::new(snippet)],
11994                );
11995            });
11996        })
11997    });
11998
11999    for (input_to_simulate, should_match_snippet) in [
12000        ("m", true),
12001        ("m ", true),
12002        ("m w", true),
12003        ("aa m w", true),
12004        ("aa m g", false),
12005    ] {
12006        cx.set_state("ˇ");
12007        cx.simulate_input(input_to_simulate); // fails correctly
12008
12009        cx.update_editor(|editor, _, _| {
12010            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12011            else {
12012                assert!(!should_match_snippet); // no completions! don't even show the menu
12013                return;
12014            };
12015            assert!(context_menu.visible());
12016            let completions = context_menu.completions.borrow();
12017
12018            assert_eq!(!completions.is_empty(), should_match_snippet);
12019        });
12020    }
12021}
12022
12023#[gpui::test]
12024async fn test_document_format_during_save(cx: &mut TestAppContext) {
12025    init_test(cx, |_| {});
12026
12027    let fs = FakeFs::new(cx.executor());
12028    fs.insert_file(path!("/file.rs"), Default::default()).await;
12029
12030    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12031
12032    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12033    language_registry.add(rust_lang());
12034    let mut fake_servers = language_registry.register_fake_lsp(
12035        "Rust",
12036        FakeLspAdapter {
12037            capabilities: lsp::ServerCapabilities {
12038                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12039                ..Default::default()
12040            },
12041            ..Default::default()
12042        },
12043    );
12044
12045    let buffer = project
12046        .update(cx, |project, cx| {
12047            project.open_local_buffer(path!("/file.rs"), cx)
12048        })
12049        .await
12050        .unwrap();
12051
12052    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12053    let (editor, cx) = cx.add_window_view(|window, cx| {
12054        build_editor_with_project(project.clone(), buffer, window, cx)
12055    });
12056    editor.update_in(cx, |editor, window, cx| {
12057        editor.set_text("one\ntwo\nthree\n", window, cx)
12058    });
12059    assert!(cx.read(|cx| editor.is_dirty(cx)));
12060
12061    let fake_server = fake_servers.next().await.unwrap();
12062
12063    {
12064        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12065            move |params, _| async move {
12066                assert_eq!(
12067                    params.text_document.uri,
12068                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12069                );
12070                assert_eq!(params.options.tab_size, 4);
12071                Ok(Some(vec![lsp::TextEdit::new(
12072                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12073                    ", ".to_string(),
12074                )]))
12075            },
12076        );
12077        let save = editor
12078            .update_in(cx, |editor, window, cx| {
12079                editor.save(
12080                    SaveOptions {
12081                        format: true,
12082                        autosave: false,
12083                    },
12084                    project.clone(),
12085                    window,
12086                    cx,
12087                )
12088            })
12089            .unwrap();
12090        save.await;
12091
12092        assert_eq!(
12093            editor.update(cx, |editor, cx| editor.text(cx)),
12094            "one, two\nthree\n"
12095        );
12096        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12097    }
12098
12099    {
12100        editor.update_in(cx, |editor, window, cx| {
12101            editor.set_text("one\ntwo\nthree\n", window, cx)
12102        });
12103        assert!(cx.read(|cx| editor.is_dirty(cx)));
12104
12105        // Ensure we can still save even if formatting hangs.
12106        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12107            move |params, _| async move {
12108                assert_eq!(
12109                    params.text_document.uri,
12110                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12111                );
12112                futures::future::pending::<()>().await;
12113                unreachable!()
12114            },
12115        );
12116        let save = editor
12117            .update_in(cx, |editor, window, cx| {
12118                editor.save(
12119                    SaveOptions {
12120                        format: true,
12121                        autosave: false,
12122                    },
12123                    project.clone(),
12124                    window,
12125                    cx,
12126                )
12127            })
12128            .unwrap();
12129        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12130        save.await;
12131        assert_eq!(
12132            editor.update(cx, |editor, cx| editor.text(cx)),
12133            "one\ntwo\nthree\n"
12134        );
12135    }
12136
12137    // Set rust language override and assert overridden tabsize is sent to language server
12138    update_test_language_settings(cx, |settings| {
12139        settings.languages.0.insert(
12140            "Rust".into(),
12141            LanguageSettingsContent {
12142                tab_size: NonZeroU32::new(8),
12143                ..Default::default()
12144            },
12145        );
12146    });
12147
12148    {
12149        editor.update_in(cx, |editor, window, cx| {
12150            editor.set_text("somehting_new\n", window, cx)
12151        });
12152        assert!(cx.read(|cx| editor.is_dirty(cx)));
12153        let _formatting_request_signal = fake_server
12154            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12155                assert_eq!(
12156                    params.text_document.uri,
12157                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12158                );
12159                assert_eq!(params.options.tab_size, 8);
12160                Ok(Some(vec![]))
12161            });
12162        let save = editor
12163            .update_in(cx, |editor, window, cx| {
12164                editor.save(
12165                    SaveOptions {
12166                        format: true,
12167                        autosave: false,
12168                    },
12169                    project.clone(),
12170                    window,
12171                    cx,
12172                )
12173            })
12174            .unwrap();
12175        save.await;
12176    }
12177}
12178
12179#[gpui::test]
12180async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12181    init_test(cx, |settings| {
12182        settings.defaults.ensure_final_newline_on_save = Some(false);
12183    });
12184
12185    let fs = FakeFs::new(cx.executor());
12186    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12187
12188    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12189
12190    let buffer = project
12191        .update(cx, |project, cx| {
12192            project.open_local_buffer(path!("/file.txt"), cx)
12193        })
12194        .await
12195        .unwrap();
12196
12197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12198    let (editor, cx) = cx.add_window_view(|window, cx| {
12199        build_editor_with_project(project.clone(), buffer, window, cx)
12200    });
12201    editor.update_in(cx, |editor, window, cx| {
12202        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12204        });
12205    });
12206    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12207
12208    editor.update_in(cx, |editor, window, cx| {
12209        editor.handle_input("\n", window, cx)
12210    });
12211    cx.run_until_parked();
12212    save(&editor, &project, cx).await;
12213    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12214
12215    editor.update_in(cx, |editor, window, cx| {
12216        editor.undo(&Default::default(), window, cx);
12217    });
12218    save(&editor, &project, cx).await;
12219    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12220
12221    editor.update_in(cx, |editor, window, cx| {
12222        editor.redo(&Default::default(), window, cx);
12223    });
12224    cx.run_until_parked();
12225    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12226
12227    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12228        let save = editor
12229            .update_in(cx, |editor, window, cx| {
12230                editor.save(
12231                    SaveOptions {
12232                        format: true,
12233                        autosave: false,
12234                    },
12235                    project.clone(),
12236                    window,
12237                    cx,
12238                )
12239            })
12240            .unwrap();
12241        save.await;
12242        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12243    }
12244}
12245
12246#[gpui::test]
12247async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12248    init_test(cx, |_| {});
12249
12250    let cols = 4;
12251    let rows = 10;
12252    let sample_text_1 = sample_text(rows, cols, 'a');
12253    assert_eq!(
12254        sample_text_1,
12255        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12256    );
12257    let sample_text_2 = sample_text(rows, cols, 'l');
12258    assert_eq!(
12259        sample_text_2,
12260        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12261    );
12262    let sample_text_3 = sample_text(rows, cols, 'v');
12263    assert_eq!(
12264        sample_text_3,
12265        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12266    );
12267
12268    let fs = FakeFs::new(cx.executor());
12269    fs.insert_tree(
12270        path!("/a"),
12271        json!({
12272            "main.rs": sample_text_1,
12273            "other.rs": sample_text_2,
12274            "lib.rs": sample_text_3,
12275        }),
12276    )
12277    .await;
12278
12279    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12280    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12281    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12282
12283    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12284    language_registry.add(rust_lang());
12285    let mut fake_servers = language_registry.register_fake_lsp(
12286        "Rust",
12287        FakeLspAdapter {
12288            capabilities: lsp::ServerCapabilities {
12289                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12290                ..Default::default()
12291            },
12292            ..Default::default()
12293        },
12294    );
12295
12296    let worktree = project.update(cx, |project, cx| {
12297        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12298        assert_eq!(worktrees.len(), 1);
12299        worktrees.pop().unwrap()
12300    });
12301    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12302
12303    let buffer_1 = project
12304        .update(cx, |project, cx| {
12305            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12306        })
12307        .await
12308        .unwrap();
12309    let buffer_2 = project
12310        .update(cx, |project, cx| {
12311            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12312        })
12313        .await
12314        .unwrap();
12315    let buffer_3 = project
12316        .update(cx, |project, cx| {
12317            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12318        })
12319        .await
12320        .unwrap();
12321
12322    let multi_buffer = cx.new(|cx| {
12323        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12324        multi_buffer.push_excerpts(
12325            buffer_1.clone(),
12326            [
12327                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12328                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12329                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12330            ],
12331            cx,
12332        );
12333        multi_buffer.push_excerpts(
12334            buffer_2.clone(),
12335            [
12336                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12337                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12338                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12339            ],
12340            cx,
12341        );
12342        multi_buffer.push_excerpts(
12343            buffer_3.clone(),
12344            [
12345                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12346                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12347                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12348            ],
12349            cx,
12350        );
12351        multi_buffer
12352    });
12353    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12354        Editor::new(
12355            EditorMode::full(),
12356            multi_buffer,
12357            Some(project.clone()),
12358            window,
12359            cx,
12360        )
12361    });
12362
12363    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12364        editor.change_selections(
12365            SelectionEffects::scroll(Autoscroll::Next),
12366            window,
12367            cx,
12368            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12369        );
12370        editor.insert("|one|two|three|", window, cx);
12371    });
12372    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12373    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12374        editor.change_selections(
12375            SelectionEffects::scroll(Autoscroll::Next),
12376            window,
12377            cx,
12378            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12379        );
12380        editor.insert("|four|five|six|", window, cx);
12381    });
12382    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12383
12384    // First two buffers should be edited, but not the third one.
12385    assert_eq!(
12386        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12387        "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}",
12388    );
12389    buffer_1.update(cx, |buffer, _| {
12390        assert!(buffer.is_dirty());
12391        assert_eq!(
12392            buffer.text(),
12393            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12394        )
12395    });
12396    buffer_2.update(cx, |buffer, _| {
12397        assert!(buffer.is_dirty());
12398        assert_eq!(
12399            buffer.text(),
12400            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12401        )
12402    });
12403    buffer_3.update(cx, |buffer, _| {
12404        assert!(!buffer.is_dirty());
12405        assert_eq!(buffer.text(), sample_text_3,)
12406    });
12407    cx.executor().run_until_parked();
12408
12409    let save = multi_buffer_editor
12410        .update_in(cx, |editor, window, cx| {
12411            editor.save(
12412                SaveOptions {
12413                    format: true,
12414                    autosave: false,
12415                },
12416                project.clone(),
12417                window,
12418                cx,
12419            )
12420        })
12421        .unwrap();
12422
12423    let fake_server = fake_servers.next().await.unwrap();
12424    fake_server
12425        .server
12426        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12427            Ok(Some(vec![lsp::TextEdit::new(
12428                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12429                format!("[{} formatted]", params.text_document.uri),
12430            )]))
12431        })
12432        .detach();
12433    save.await;
12434
12435    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12436    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12437    assert_eq!(
12438        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12439        uri!(
12440            "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}"
12441        ),
12442    );
12443    buffer_1.update(cx, |buffer, _| {
12444        assert!(!buffer.is_dirty());
12445        assert_eq!(
12446            buffer.text(),
12447            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12448        )
12449    });
12450    buffer_2.update(cx, |buffer, _| {
12451        assert!(!buffer.is_dirty());
12452        assert_eq!(
12453            buffer.text(),
12454            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12455        )
12456    });
12457    buffer_3.update(cx, |buffer, _| {
12458        assert!(!buffer.is_dirty());
12459        assert_eq!(buffer.text(), sample_text_3,)
12460    });
12461}
12462
12463#[gpui::test]
12464async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12465    init_test(cx, |_| {});
12466
12467    let fs = FakeFs::new(cx.executor());
12468    fs.insert_tree(
12469        path!("/dir"),
12470        json!({
12471            "file1.rs": "fn main() { println!(\"hello\"); }",
12472            "file2.rs": "fn test() { println!(\"test\"); }",
12473            "file3.rs": "fn other() { println!(\"other\"); }\n",
12474        }),
12475    )
12476    .await;
12477
12478    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12479    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12480    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12481
12482    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12483    language_registry.add(rust_lang());
12484
12485    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12486    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12487
12488    // Open three buffers
12489    let buffer_1 = project
12490        .update(cx, |project, cx| {
12491            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12492        })
12493        .await
12494        .unwrap();
12495    let buffer_2 = project
12496        .update(cx, |project, cx| {
12497            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12498        })
12499        .await
12500        .unwrap();
12501    let buffer_3 = project
12502        .update(cx, |project, cx| {
12503            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12504        })
12505        .await
12506        .unwrap();
12507
12508    // Create a multi-buffer with all three buffers
12509    let multi_buffer = cx.new(|cx| {
12510        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12511        multi_buffer.push_excerpts(
12512            buffer_1.clone(),
12513            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12514            cx,
12515        );
12516        multi_buffer.push_excerpts(
12517            buffer_2.clone(),
12518            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12519            cx,
12520        );
12521        multi_buffer.push_excerpts(
12522            buffer_3.clone(),
12523            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12524            cx,
12525        );
12526        multi_buffer
12527    });
12528
12529    let editor = cx.new_window_entity(|window, cx| {
12530        Editor::new(
12531            EditorMode::full(),
12532            multi_buffer,
12533            Some(project.clone()),
12534            window,
12535            cx,
12536        )
12537    });
12538
12539    // Edit only the first buffer
12540    editor.update_in(cx, |editor, window, cx| {
12541        editor.change_selections(
12542            SelectionEffects::scroll(Autoscroll::Next),
12543            window,
12544            cx,
12545            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12546        );
12547        editor.insert("// edited", window, cx);
12548    });
12549
12550    // Verify that only buffer 1 is dirty
12551    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12552    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12553    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12554
12555    // Get write counts after file creation (files were created with initial content)
12556    // We expect each file to have been written once during creation
12557    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12558    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12559    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12560
12561    // Perform autosave
12562    let save_task = editor.update_in(cx, |editor, window, cx| {
12563        editor.save(
12564            SaveOptions {
12565                format: true,
12566                autosave: true,
12567            },
12568            project.clone(),
12569            window,
12570            cx,
12571        )
12572    });
12573    save_task.await.unwrap();
12574
12575    // Only the dirty buffer should have been saved
12576    assert_eq!(
12577        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12578        1,
12579        "Buffer 1 was dirty, so it should have been written once during autosave"
12580    );
12581    assert_eq!(
12582        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12583        0,
12584        "Buffer 2 was clean, so it should not have been written during autosave"
12585    );
12586    assert_eq!(
12587        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12588        0,
12589        "Buffer 3 was clean, so it should not have been written during autosave"
12590    );
12591
12592    // Verify buffer states after autosave
12593    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12594    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12595    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12596
12597    // Now perform a manual save (format = true)
12598    let save_task = editor.update_in(cx, |editor, window, cx| {
12599        editor.save(
12600            SaveOptions {
12601                format: true,
12602                autosave: false,
12603            },
12604            project.clone(),
12605            window,
12606            cx,
12607        )
12608    });
12609    save_task.await.unwrap();
12610
12611    // During manual save, clean buffers don't get written to disk
12612    // They just get did_save called for language server notifications
12613    assert_eq!(
12614        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12615        1,
12616        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12617    );
12618    assert_eq!(
12619        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12620        0,
12621        "Buffer 2 should not have been written at all"
12622    );
12623    assert_eq!(
12624        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12625        0,
12626        "Buffer 3 should not have been written at all"
12627    );
12628}
12629
12630async fn setup_range_format_test(
12631    cx: &mut TestAppContext,
12632) -> (
12633    Entity<Project>,
12634    Entity<Editor>,
12635    &mut gpui::VisualTestContext,
12636    lsp::FakeLanguageServer,
12637) {
12638    init_test(cx, |_| {});
12639
12640    let fs = FakeFs::new(cx.executor());
12641    fs.insert_file(path!("/file.rs"), Default::default()).await;
12642
12643    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12644
12645    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12646    language_registry.add(rust_lang());
12647    let mut fake_servers = language_registry.register_fake_lsp(
12648        "Rust",
12649        FakeLspAdapter {
12650            capabilities: lsp::ServerCapabilities {
12651                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12652                ..lsp::ServerCapabilities::default()
12653            },
12654            ..FakeLspAdapter::default()
12655        },
12656    );
12657
12658    let buffer = project
12659        .update(cx, |project, cx| {
12660            project.open_local_buffer(path!("/file.rs"), cx)
12661        })
12662        .await
12663        .unwrap();
12664
12665    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12666    let (editor, cx) = cx.add_window_view(|window, cx| {
12667        build_editor_with_project(project.clone(), buffer, window, cx)
12668    });
12669
12670    let fake_server = fake_servers.next().await.unwrap();
12671
12672    (project, editor, cx, fake_server)
12673}
12674
12675#[gpui::test]
12676async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12677    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12678
12679    editor.update_in(cx, |editor, window, cx| {
12680        editor.set_text("one\ntwo\nthree\n", window, cx)
12681    });
12682    assert!(cx.read(|cx| editor.is_dirty(cx)));
12683
12684    let save = editor
12685        .update_in(cx, |editor, window, cx| {
12686            editor.save(
12687                SaveOptions {
12688                    format: true,
12689                    autosave: false,
12690                },
12691                project.clone(),
12692                window,
12693                cx,
12694            )
12695        })
12696        .unwrap();
12697    fake_server
12698        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12699            assert_eq!(
12700                params.text_document.uri,
12701                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12702            );
12703            assert_eq!(params.options.tab_size, 4);
12704            Ok(Some(vec![lsp::TextEdit::new(
12705                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12706                ", ".to_string(),
12707            )]))
12708        })
12709        .next()
12710        .await;
12711    save.await;
12712    assert_eq!(
12713        editor.update(cx, |editor, cx| editor.text(cx)),
12714        "one, two\nthree\n"
12715    );
12716    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12717}
12718
12719#[gpui::test]
12720async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12721    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12722
12723    editor.update_in(cx, |editor, window, cx| {
12724        editor.set_text("one\ntwo\nthree\n", window, cx)
12725    });
12726    assert!(cx.read(|cx| editor.is_dirty(cx)));
12727
12728    // Test that save still works when formatting hangs
12729    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12730        move |params, _| async move {
12731            assert_eq!(
12732                params.text_document.uri,
12733                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12734            );
12735            futures::future::pending::<()>().await;
12736            unreachable!()
12737        },
12738    );
12739    let save = editor
12740        .update_in(cx, |editor, window, cx| {
12741            editor.save(
12742                SaveOptions {
12743                    format: true,
12744                    autosave: false,
12745                },
12746                project.clone(),
12747                window,
12748                cx,
12749            )
12750        })
12751        .unwrap();
12752    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12753    save.await;
12754    assert_eq!(
12755        editor.update(cx, |editor, cx| editor.text(cx)),
12756        "one\ntwo\nthree\n"
12757    );
12758    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12759}
12760
12761#[gpui::test]
12762async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12763    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12764
12765    // Buffer starts clean, no formatting should be requested
12766    let save = editor
12767        .update_in(cx, |editor, window, cx| {
12768            editor.save(
12769                SaveOptions {
12770                    format: false,
12771                    autosave: false,
12772                },
12773                project.clone(),
12774                window,
12775                cx,
12776            )
12777        })
12778        .unwrap();
12779    let _pending_format_request = fake_server
12780        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12781            panic!("Should not be invoked");
12782        })
12783        .next();
12784    save.await;
12785    cx.run_until_parked();
12786}
12787
12788#[gpui::test]
12789async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12790    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12791
12792    // Set Rust language override and assert overridden tabsize is sent to language server
12793    update_test_language_settings(cx, |settings| {
12794        settings.languages.0.insert(
12795            "Rust".into(),
12796            LanguageSettingsContent {
12797                tab_size: NonZeroU32::new(8),
12798                ..Default::default()
12799            },
12800        );
12801    });
12802
12803    editor.update_in(cx, |editor, window, cx| {
12804        editor.set_text("something_new\n", window, cx)
12805    });
12806    assert!(cx.read(|cx| editor.is_dirty(cx)));
12807    let save = editor
12808        .update_in(cx, |editor, window, cx| {
12809            editor.save(
12810                SaveOptions {
12811                    format: true,
12812                    autosave: false,
12813                },
12814                project.clone(),
12815                window,
12816                cx,
12817            )
12818        })
12819        .unwrap();
12820    fake_server
12821        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12822            assert_eq!(
12823                params.text_document.uri,
12824                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12825            );
12826            assert_eq!(params.options.tab_size, 8);
12827            Ok(Some(Vec::new()))
12828        })
12829        .next()
12830        .await;
12831    save.await;
12832}
12833
12834#[gpui::test]
12835async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12836    init_test(cx, |settings| {
12837        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12838            settings::LanguageServerFormatterSpecifier::Current,
12839        )))
12840    });
12841
12842    let fs = FakeFs::new(cx.executor());
12843    fs.insert_file(path!("/file.rs"), Default::default()).await;
12844
12845    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12846
12847    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12848    language_registry.add(Arc::new(Language::new(
12849        LanguageConfig {
12850            name: "Rust".into(),
12851            matcher: LanguageMatcher {
12852                path_suffixes: vec!["rs".to_string()],
12853                ..Default::default()
12854            },
12855            ..LanguageConfig::default()
12856        },
12857        Some(tree_sitter_rust::LANGUAGE.into()),
12858    )));
12859    update_test_language_settings(cx, |settings| {
12860        // Enable Prettier formatting for the same buffer, and ensure
12861        // LSP is called instead of Prettier.
12862        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12863    });
12864    let mut fake_servers = language_registry.register_fake_lsp(
12865        "Rust",
12866        FakeLspAdapter {
12867            capabilities: lsp::ServerCapabilities {
12868                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12869                ..Default::default()
12870            },
12871            ..Default::default()
12872        },
12873    );
12874
12875    let buffer = project
12876        .update(cx, |project, cx| {
12877            project.open_local_buffer(path!("/file.rs"), cx)
12878        })
12879        .await
12880        .unwrap();
12881
12882    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12883    let (editor, cx) = cx.add_window_view(|window, cx| {
12884        build_editor_with_project(project.clone(), buffer, window, cx)
12885    });
12886    editor.update_in(cx, |editor, window, cx| {
12887        editor.set_text("one\ntwo\nthree\n", window, cx)
12888    });
12889
12890    let fake_server = fake_servers.next().await.unwrap();
12891
12892    let format = editor
12893        .update_in(cx, |editor, window, cx| {
12894            editor.perform_format(
12895                project.clone(),
12896                FormatTrigger::Manual,
12897                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12898                window,
12899                cx,
12900            )
12901        })
12902        .unwrap();
12903    fake_server
12904        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12905            assert_eq!(
12906                params.text_document.uri,
12907                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12908            );
12909            assert_eq!(params.options.tab_size, 4);
12910            Ok(Some(vec![lsp::TextEdit::new(
12911                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12912                ", ".to_string(),
12913            )]))
12914        })
12915        .next()
12916        .await;
12917    format.await;
12918    assert_eq!(
12919        editor.update(cx, |editor, cx| editor.text(cx)),
12920        "one, two\nthree\n"
12921    );
12922
12923    editor.update_in(cx, |editor, window, cx| {
12924        editor.set_text("one\ntwo\nthree\n", window, cx)
12925    });
12926    // Ensure we don't lock if formatting hangs.
12927    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12928        move |params, _| async move {
12929            assert_eq!(
12930                params.text_document.uri,
12931                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12932            );
12933            futures::future::pending::<()>().await;
12934            unreachable!()
12935        },
12936    );
12937    let format = editor
12938        .update_in(cx, |editor, window, cx| {
12939            editor.perform_format(
12940                project,
12941                FormatTrigger::Manual,
12942                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12943                window,
12944                cx,
12945            )
12946        })
12947        .unwrap();
12948    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12949    format.await;
12950    assert_eq!(
12951        editor.update(cx, |editor, cx| editor.text(cx)),
12952        "one\ntwo\nthree\n"
12953    );
12954}
12955
12956#[gpui::test]
12957async fn test_multiple_formatters(cx: &mut TestAppContext) {
12958    init_test(cx, |settings| {
12959        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12960        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12961            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12962            Formatter::CodeAction("code-action-1".into()),
12963            Formatter::CodeAction("code-action-2".into()),
12964        ]))
12965    });
12966
12967    let fs = FakeFs::new(cx.executor());
12968    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12969        .await;
12970
12971    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12972    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12973    language_registry.add(rust_lang());
12974
12975    let mut fake_servers = language_registry.register_fake_lsp(
12976        "Rust",
12977        FakeLspAdapter {
12978            capabilities: lsp::ServerCapabilities {
12979                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12980                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12981                    commands: vec!["the-command-for-code-action-1".into()],
12982                    ..Default::default()
12983                }),
12984                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12985                ..Default::default()
12986            },
12987            ..Default::default()
12988        },
12989    );
12990
12991    let buffer = project
12992        .update(cx, |project, cx| {
12993            project.open_local_buffer(path!("/file.rs"), cx)
12994        })
12995        .await
12996        .unwrap();
12997
12998    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12999    let (editor, cx) = cx.add_window_view(|window, cx| {
13000        build_editor_with_project(project.clone(), buffer, window, cx)
13001    });
13002
13003    let fake_server = fake_servers.next().await.unwrap();
13004    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13005        move |_params, _| async move {
13006            Ok(Some(vec![lsp::TextEdit::new(
13007                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13008                "applied-formatting\n".to_string(),
13009            )]))
13010        },
13011    );
13012    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13013        move |params, _| async move {
13014            let requested_code_actions = params.context.only.expect("Expected code action request");
13015            assert_eq!(requested_code_actions.len(), 1);
13016
13017            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
13018            let code_action = match requested_code_actions[0].as_str() {
13019                "code-action-1" => lsp::CodeAction {
13020                    kind: Some("code-action-1".into()),
13021                    edit: Some(lsp::WorkspaceEdit::new(
13022                        [(
13023                            uri,
13024                            vec![lsp::TextEdit::new(
13025                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13026                                "applied-code-action-1-edit\n".to_string(),
13027                            )],
13028                        )]
13029                        .into_iter()
13030                        .collect(),
13031                    )),
13032                    command: Some(lsp::Command {
13033                        command: "the-command-for-code-action-1".into(),
13034                        ..Default::default()
13035                    }),
13036                    ..Default::default()
13037                },
13038                "code-action-2" => lsp::CodeAction {
13039                    kind: Some("code-action-2".into()),
13040                    edit: Some(lsp::WorkspaceEdit::new(
13041                        [(
13042                            uri,
13043                            vec![lsp::TextEdit::new(
13044                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13045                                "applied-code-action-2-edit\n".to_string(),
13046                            )],
13047                        )]
13048                        .into_iter()
13049                        .collect(),
13050                    )),
13051                    ..Default::default()
13052                },
13053                req => panic!("Unexpected code action request: {:?}", req),
13054            };
13055            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13056                code_action,
13057            )]))
13058        },
13059    );
13060
13061    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
13062        move |params, _| async move { Ok(params) }
13063    });
13064
13065    let command_lock = Arc::new(futures::lock::Mutex::new(()));
13066    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
13067        let fake = fake_server.clone();
13068        let lock = command_lock.clone();
13069        move |params, _| {
13070            assert_eq!(params.command, "the-command-for-code-action-1");
13071            let fake = fake.clone();
13072            let lock = lock.clone();
13073            async move {
13074                lock.lock().await;
13075                fake.server
13076                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
13077                        label: None,
13078                        edit: lsp::WorkspaceEdit {
13079                            changes: Some(
13080                                [(
13081                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13082                                    vec![lsp::TextEdit {
13083                                        range: lsp::Range::new(
13084                                            lsp::Position::new(0, 0),
13085                                            lsp::Position::new(0, 0),
13086                                        ),
13087                                        new_text: "applied-code-action-1-command\n".into(),
13088                                    }],
13089                                )]
13090                                .into_iter()
13091                                .collect(),
13092                            ),
13093                            ..Default::default()
13094                        },
13095                    })
13096                    .await
13097                    .into_response()
13098                    .unwrap();
13099                Ok(Some(json!(null)))
13100            }
13101        }
13102    });
13103
13104    editor
13105        .update_in(cx, |editor, window, cx| {
13106            editor.perform_format(
13107                project.clone(),
13108                FormatTrigger::Manual,
13109                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13110                window,
13111                cx,
13112            )
13113        })
13114        .unwrap()
13115        .await;
13116    editor.update(cx, |editor, cx| {
13117        assert_eq!(
13118            editor.text(cx),
13119            r#"
13120                applied-code-action-2-edit
13121                applied-code-action-1-command
13122                applied-code-action-1-edit
13123                applied-formatting
13124                one
13125                two
13126                three
13127            "#
13128            .unindent()
13129        );
13130    });
13131
13132    editor.update_in(cx, |editor, window, cx| {
13133        editor.undo(&Default::default(), window, cx);
13134        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13135    });
13136
13137    // Perform a manual edit while waiting for an LSP command
13138    // that's being run as part of a formatting code action.
13139    let lock_guard = command_lock.lock().await;
13140    let format = editor
13141        .update_in(cx, |editor, window, cx| {
13142            editor.perform_format(
13143                project.clone(),
13144                FormatTrigger::Manual,
13145                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13146                window,
13147                cx,
13148            )
13149        })
13150        .unwrap();
13151    cx.run_until_parked();
13152    editor.update(cx, |editor, cx| {
13153        assert_eq!(
13154            editor.text(cx),
13155            r#"
13156                applied-code-action-1-edit
13157                applied-formatting
13158                one
13159                two
13160                three
13161            "#
13162            .unindent()
13163        );
13164
13165        editor.buffer.update(cx, |buffer, cx| {
13166            let ix = buffer.len(cx);
13167            buffer.edit([(ix..ix, "edited\n")], None, cx);
13168        });
13169    });
13170
13171    // Allow the LSP command to proceed. Because the buffer was edited,
13172    // the second code action will not be run.
13173    drop(lock_guard);
13174    format.await;
13175    editor.update_in(cx, |editor, window, cx| {
13176        assert_eq!(
13177            editor.text(cx),
13178            r#"
13179                applied-code-action-1-command
13180                applied-code-action-1-edit
13181                applied-formatting
13182                one
13183                two
13184                three
13185                edited
13186            "#
13187            .unindent()
13188        );
13189
13190        // The manual edit is undone first, because it is the last thing the user did
13191        // (even though the command completed afterwards).
13192        editor.undo(&Default::default(), window, cx);
13193        assert_eq!(
13194            editor.text(cx),
13195            r#"
13196                applied-code-action-1-command
13197                applied-code-action-1-edit
13198                applied-formatting
13199                one
13200                two
13201                three
13202            "#
13203            .unindent()
13204        );
13205
13206        // All the formatting (including the command, which completed after the manual edit)
13207        // is undone together.
13208        editor.undo(&Default::default(), window, cx);
13209        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13210    });
13211}
13212
13213#[gpui::test]
13214async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13215    init_test(cx, |settings| {
13216        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13217            settings::LanguageServerFormatterSpecifier::Current,
13218        )]))
13219    });
13220
13221    let fs = FakeFs::new(cx.executor());
13222    fs.insert_file(path!("/file.ts"), Default::default()).await;
13223
13224    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13225
13226    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13227    language_registry.add(Arc::new(Language::new(
13228        LanguageConfig {
13229            name: "TypeScript".into(),
13230            matcher: LanguageMatcher {
13231                path_suffixes: vec!["ts".to_string()],
13232                ..Default::default()
13233            },
13234            ..LanguageConfig::default()
13235        },
13236        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13237    )));
13238    update_test_language_settings(cx, |settings| {
13239        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13240    });
13241    let mut fake_servers = language_registry.register_fake_lsp(
13242        "TypeScript",
13243        FakeLspAdapter {
13244            capabilities: lsp::ServerCapabilities {
13245                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13246                ..Default::default()
13247            },
13248            ..Default::default()
13249        },
13250    );
13251
13252    let buffer = project
13253        .update(cx, |project, cx| {
13254            project.open_local_buffer(path!("/file.ts"), cx)
13255        })
13256        .await
13257        .unwrap();
13258
13259    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13260    let (editor, cx) = cx.add_window_view(|window, cx| {
13261        build_editor_with_project(project.clone(), buffer, window, cx)
13262    });
13263    editor.update_in(cx, |editor, window, cx| {
13264        editor.set_text(
13265            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13266            window,
13267            cx,
13268        )
13269    });
13270
13271    let fake_server = fake_servers.next().await.unwrap();
13272
13273    let format = editor
13274        .update_in(cx, |editor, window, cx| {
13275            editor.perform_code_action_kind(
13276                project.clone(),
13277                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13278                window,
13279                cx,
13280            )
13281        })
13282        .unwrap();
13283    fake_server
13284        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13285            assert_eq!(
13286                params.text_document.uri,
13287                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13288            );
13289            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13290                lsp::CodeAction {
13291                    title: "Organize Imports".to_string(),
13292                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13293                    edit: Some(lsp::WorkspaceEdit {
13294                        changes: Some(
13295                            [(
13296                                params.text_document.uri.clone(),
13297                                vec![lsp::TextEdit::new(
13298                                    lsp::Range::new(
13299                                        lsp::Position::new(1, 0),
13300                                        lsp::Position::new(2, 0),
13301                                    ),
13302                                    "".to_string(),
13303                                )],
13304                            )]
13305                            .into_iter()
13306                            .collect(),
13307                        ),
13308                        ..Default::default()
13309                    }),
13310                    ..Default::default()
13311                },
13312            )]))
13313        })
13314        .next()
13315        .await;
13316    format.await;
13317    assert_eq!(
13318        editor.update(cx, |editor, cx| editor.text(cx)),
13319        "import { a } from 'module';\n\nconst x = a;\n"
13320    );
13321
13322    editor.update_in(cx, |editor, window, cx| {
13323        editor.set_text(
13324            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13325            window,
13326            cx,
13327        )
13328    });
13329    // Ensure we don't lock if code action hangs.
13330    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13331        move |params, _| async move {
13332            assert_eq!(
13333                params.text_document.uri,
13334                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13335            );
13336            futures::future::pending::<()>().await;
13337            unreachable!()
13338        },
13339    );
13340    let format = editor
13341        .update_in(cx, |editor, window, cx| {
13342            editor.perform_code_action_kind(
13343                project,
13344                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13345                window,
13346                cx,
13347            )
13348        })
13349        .unwrap();
13350    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13351    format.await;
13352    assert_eq!(
13353        editor.update(cx, |editor, cx| editor.text(cx)),
13354        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13355    );
13356}
13357
13358#[gpui::test]
13359async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13360    init_test(cx, |_| {});
13361
13362    let mut cx = EditorLspTestContext::new_rust(
13363        lsp::ServerCapabilities {
13364            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13365            ..Default::default()
13366        },
13367        cx,
13368    )
13369    .await;
13370
13371    cx.set_state(indoc! {"
13372        one.twoˇ
13373    "});
13374
13375    // The format request takes a long time. When it completes, it inserts
13376    // a newline and an indent before the `.`
13377    cx.lsp
13378        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13379            let executor = cx.background_executor().clone();
13380            async move {
13381                executor.timer(Duration::from_millis(100)).await;
13382                Ok(Some(vec![lsp::TextEdit {
13383                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13384                    new_text: "\n    ".into(),
13385                }]))
13386            }
13387        });
13388
13389    // Submit a format request.
13390    let format_1 = cx
13391        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13392        .unwrap();
13393    cx.executor().run_until_parked();
13394
13395    // Submit a second format request.
13396    let format_2 = cx
13397        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13398        .unwrap();
13399    cx.executor().run_until_parked();
13400
13401    // Wait for both format requests to complete
13402    cx.executor().advance_clock(Duration::from_millis(200));
13403    format_1.await.unwrap();
13404    format_2.await.unwrap();
13405
13406    // The formatting edits only happens once.
13407    cx.assert_editor_state(indoc! {"
13408        one
13409            .twoˇ
13410    "});
13411}
13412
13413#[gpui::test]
13414async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13415    init_test(cx, |settings| {
13416        settings.defaults.formatter = Some(FormatterList::default())
13417    });
13418
13419    let mut cx = EditorLspTestContext::new_rust(
13420        lsp::ServerCapabilities {
13421            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13422            ..Default::default()
13423        },
13424        cx,
13425    )
13426    .await;
13427
13428    // Record which buffer changes have been sent to the language server
13429    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13430    cx.lsp
13431        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13432            let buffer_changes = buffer_changes.clone();
13433            move |params, _| {
13434                buffer_changes.lock().extend(
13435                    params
13436                        .content_changes
13437                        .into_iter()
13438                        .map(|e| (e.range.unwrap(), e.text)),
13439                );
13440            }
13441        });
13442    // Handle formatting requests to the language server.
13443    cx.lsp
13444        .set_request_handler::<lsp::request::Formatting, _, _>({
13445            move |_, _| {
13446                // Insert blank lines between each line of the buffer.
13447                async move {
13448                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13449                    // DidChangedTextDocument to the LSP before sending the formatting request.
13450                    // assert_eq!(
13451                    //     &buffer_changes.lock()[1..],
13452                    //     &[
13453                    //         (
13454                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13455                    //             "".into()
13456                    //         ),
13457                    //         (
13458                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13459                    //             "".into()
13460                    //         ),
13461                    //         (
13462                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13463                    //             "\n".into()
13464                    //         ),
13465                    //     ]
13466                    // );
13467
13468                    Ok(Some(vec![
13469                        lsp::TextEdit {
13470                            range: lsp::Range::new(
13471                                lsp::Position::new(1, 0),
13472                                lsp::Position::new(1, 0),
13473                            ),
13474                            new_text: "\n".into(),
13475                        },
13476                        lsp::TextEdit {
13477                            range: lsp::Range::new(
13478                                lsp::Position::new(2, 0),
13479                                lsp::Position::new(2, 0),
13480                            ),
13481                            new_text: "\n".into(),
13482                        },
13483                    ]))
13484                }
13485            }
13486        });
13487
13488    // Set up a buffer white some trailing whitespace and no trailing newline.
13489    cx.set_state(
13490        &[
13491            "one ",   //
13492            "twoˇ",   //
13493            "three ", //
13494            "four",   //
13495        ]
13496        .join("\n"),
13497    );
13498
13499    // Submit a format request.
13500    let format = cx
13501        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13502        .unwrap();
13503
13504    cx.run_until_parked();
13505    // After formatting the buffer, the trailing whitespace is stripped,
13506    // a newline is appended, and the edits provided by the language server
13507    // have been applied.
13508    format.await.unwrap();
13509
13510    cx.assert_editor_state(
13511        &[
13512            "one",   //
13513            "",      //
13514            "twoˇ",  //
13515            "",      //
13516            "three", //
13517            "four",  //
13518            "",      //
13519        ]
13520        .join("\n"),
13521    );
13522
13523    // Undoing the formatting undoes the trailing whitespace removal, the
13524    // trailing newline, and the LSP edits.
13525    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13526    cx.assert_editor_state(
13527        &[
13528            "one ",   //
13529            "twoˇ",   //
13530            "three ", //
13531            "four",   //
13532        ]
13533        .join("\n"),
13534    );
13535}
13536
13537#[gpui::test]
13538async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13539    cx: &mut TestAppContext,
13540) {
13541    init_test(cx, |_| {});
13542
13543    cx.update(|cx| {
13544        cx.update_global::<SettingsStore, _>(|settings, cx| {
13545            settings.update_user_settings(cx, |settings| {
13546                settings.editor.auto_signature_help = Some(true);
13547            });
13548        });
13549    });
13550
13551    let mut cx = EditorLspTestContext::new_rust(
13552        lsp::ServerCapabilities {
13553            signature_help_provider: Some(lsp::SignatureHelpOptions {
13554                ..Default::default()
13555            }),
13556            ..Default::default()
13557        },
13558        cx,
13559    )
13560    .await;
13561
13562    let language = Language::new(
13563        LanguageConfig {
13564            name: "Rust".into(),
13565            brackets: BracketPairConfig {
13566                pairs: vec![
13567                    BracketPair {
13568                        start: "{".to_string(),
13569                        end: "}".to_string(),
13570                        close: true,
13571                        surround: true,
13572                        newline: true,
13573                    },
13574                    BracketPair {
13575                        start: "(".to_string(),
13576                        end: ")".to_string(),
13577                        close: true,
13578                        surround: true,
13579                        newline: true,
13580                    },
13581                    BracketPair {
13582                        start: "/*".to_string(),
13583                        end: " */".to_string(),
13584                        close: true,
13585                        surround: true,
13586                        newline: true,
13587                    },
13588                    BracketPair {
13589                        start: "[".to_string(),
13590                        end: "]".to_string(),
13591                        close: false,
13592                        surround: false,
13593                        newline: true,
13594                    },
13595                    BracketPair {
13596                        start: "\"".to_string(),
13597                        end: "\"".to_string(),
13598                        close: true,
13599                        surround: true,
13600                        newline: false,
13601                    },
13602                    BracketPair {
13603                        start: "<".to_string(),
13604                        end: ">".to_string(),
13605                        close: false,
13606                        surround: true,
13607                        newline: true,
13608                    },
13609                ],
13610                ..Default::default()
13611            },
13612            autoclose_before: "})]".to_string(),
13613            ..Default::default()
13614        },
13615        Some(tree_sitter_rust::LANGUAGE.into()),
13616    );
13617    let language = Arc::new(language);
13618
13619    cx.language_registry().add(language.clone());
13620    cx.update_buffer(|buffer, cx| {
13621        buffer.set_language(Some(language), cx);
13622    });
13623
13624    cx.set_state(
13625        &r#"
13626            fn main() {
13627                sampleˇ
13628            }
13629        "#
13630        .unindent(),
13631    );
13632
13633    cx.update_editor(|editor, window, cx| {
13634        editor.handle_input("(", window, cx);
13635    });
13636    cx.assert_editor_state(
13637        &"
13638            fn main() {
13639                sample(ˇ)
13640            }
13641        "
13642        .unindent(),
13643    );
13644
13645    let mocked_response = lsp::SignatureHelp {
13646        signatures: vec![lsp::SignatureInformation {
13647            label: "fn sample(param1: u8, param2: u8)".to_string(),
13648            documentation: None,
13649            parameters: Some(vec![
13650                lsp::ParameterInformation {
13651                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13652                    documentation: None,
13653                },
13654                lsp::ParameterInformation {
13655                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13656                    documentation: None,
13657                },
13658            ]),
13659            active_parameter: None,
13660        }],
13661        active_signature: Some(0),
13662        active_parameter: Some(0),
13663    };
13664    handle_signature_help_request(&mut cx, mocked_response).await;
13665
13666    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13667        .await;
13668
13669    cx.editor(|editor, _, _| {
13670        let signature_help_state = editor.signature_help_state.popover().cloned();
13671        let signature = signature_help_state.unwrap();
13672        assert_eq!(
13673            signature.signatures[signature.current_signature].label,
13674            "fn sample(param1: u8, param2: u8)"
13675        );
13676    });
13677}
13678
13679#[gpui::test]
13680async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13681    init_test(cx, |_| {});
13682
13683    cx.update(|cx| {
13684        cx.update_global::<SettingsStore, _>(|settings, cx| {
13685            settings.update_user_settings(cx, |settings| {
13686                settings.editor.auto_signature_help = Some(false);
13687                settings.editor.show_signature_help_after_edits = Some(false);
13688            });
13689        });
13690    });
13691
13692    let mut cx = EditorLspTestContext::new_rust(
13693        lsp::ServerCapabilities {
13694            signature_help_provider: Some(lsp::SignatureHelpOptions {
13695                ..Default::default()
13696            }),
13697            ..Default::default()
13698        },
13699        cx,
13700    )
13701    .await;
13702
13703    let language = Language::new(
13704        LanguageConfig {
13705            name: "Rust".into(),
13706            brackets: BracketPairConfig {
13707                pairs: vec![
13708                    BracketPair {
13709                        start: "{".to_string(),
13710                        end: "}".to_string(),
13711                        close: true,
13712                        surround: true,
13713                        newline: true,
13714                    },
13715                    BracketPair {
13716                        start: "(".to_string(),
13717                        end: ")".to_string(),
13718                        close: true,
13719                        surround: true,
13720                        newline: true,
13721                    },
13722                    BracketPair {
13723                        start: "/*".to_string(),
13724                        end: " */".to_string(),
13725                        close: true,
13726                        surround: true,
13727                        newline: true,
13728                    },
13729                    BracketPair {
13730                        start: "[".to_string(),
13731                        end: "]".to_string(),
13732                        close: false,
13733                        surround: false,
13734                        newline: true,
13735                    },
13736                    BracketPair {
13737                        start: "\"".to_string(),
13738                        end: "\"".to_string(),
13739                        close: true,
13740                        surround: true,
13741                        newline: false,
13742                    },
13743                    BracketPair {
13744                        start: "<".to_string(),
13745                        end: ">".to_string(),
13746                        close: false,
13747                        surround: true,
13748                        newline: true,
13749                    },
13750                ],
13751                ..Default::default()
13752            },
13753            autoclose_before: "})]".to_string(),
13754            ..Default::default()
13755        },
13756        Some(tree_sitter_rust::LANGUAGE.into()),
13757    );
13758    let language = Arc::new(language);
13759
13760    cx.language_registry().add(language.clone());
13761    cx.update_buffer(|buffer, cx| {
13762        buffer.set_language(Some(language), cx);
13763    });
13764
13765    // Ensure that signature_help is not called when no signature help is enabled.
13766    cx.set_state(
13767        &r#"
13768            fn main() {
13769                sampleˇ
13770            }
13771        "#
13772        .unindent(),
13773    );
13774    cx.update_editor(|editor, window, cx| {
13775        editor.handle_input("(", window, cx);
13776    });
13777    cx.assert_editor_state(
13778        &"
13779            fn main() {
13780                sample(ˇ)
13781            }
13782        "
13783        .unindent(),
13784    );
13785    cx.editor(|editor, _, _| {
13786        assert!(editor.signature_help_state.task().is_none());
13787    });
13788
13789    let mocked_response = lsp::SignatureHelp {
13790        signatures: vec![lsp::SignatureInformation {
13791            label: "fn sample(param1: u8, param2: u8)".to_string(),
13792            documentation: None,
13793            parameters: Some(vec![
13794                lsp::ParameterInformation {
13795                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13796                    documentation: None,
13797                },
13798                lsp::ParameterInformation {
13799                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13800                    documentation: None,
13801                },
13802            ]),
13803            active_parameter: None,
13804        }],
13805        active_signature: Some(0),
13806        active_parameter: Some(0),
13807    };
13808
13809    // Ensure that signature_help is called when enabled afte edits
13810    cx.update(|_, cx| {
13811        cx.update_global::<SettingsStore, _>(|settings, cx| {
13812            settings.update_user_settings(cx, |settings| {
13813                settings.editor.auto_signature_help = Some(false);
13814                settings.editor.show_signature_help_after_edits = Some(true);
13815            });
13816        });
13817    });
13818    cx.set_state(
13819        &r#"
13820            fn main() {
13821                sampleˇ
13822            }
13823        "#
13824        .unindent(),
13825    );
13826    cx.update_editor(|editor, window, cx| {
13827        editor.handle_input("(", window, cx);
13828    });
13829    cx.assert_editor_state(
13830        &"
13831            fn main() {
13832                sample(ˇ)
13833            }
13834        "
13835        .unindent(),
13836    );
13837    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13838    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13839        .await;
13840    cx.update_editor(|editor, _, _| {
13841        let signature_help_state = editor.signature_help_state.popover().cloned();
13842        assert!(signature_help_state.is_some());
13843        let signature = signature_help_state.unwrap();
13844        assert_eq!(
13845            signature.signatures[signature.current_signature].label,
13846            "fn sample(param1: u8, param2: u8)"
13847        );
13848        editor.signature_help_state = SignatureHelpState::default();
13849    });
13850
13851    // Ensure that signature_help is called when auto signature help override is enabled
13852    cx.update(|_, cx| {
13853        cx.update_global::<SettingsStore, _>(|settings, cx| {
13854            settings.update_user_settings(cx, |settings| {
13855                settings.editor.auto_signature_help = Some(true);
13856                settings.editor.show_signature_help_after_edits = Some(false);
13857            });
13858        });
13859    });
13860    cx.set_state(
13861        &r#"
13862            fn main() {
13863                sampleˇ
13864            }
13865        "#
13866        .unindent(),
13867    );
13868    cx.update_editor(|editor, window, cx| {
13869        editor.handle_input("(", window, cx);
13870    });
13871    cx.assert_editor_state(
13872        &"
13873            fn main() {
13874                sample(ˇ)
13875            }
13876        "
13877        .unindent(),
13878    );
13879    handle_signature_help_request(&mut cx, mocked_response).await;
13880    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13881        .await;
13882    cx.editor(|editor, _, _| {
13883        let signature_help_state = editor.signature_help_state.popover().cloned();
13884        assert!(signature_help_state.is_some());
13885        let signature = signature_help_state.unwrap();
13886        assert_eq!(
13887            signature.signatures[signature.current_signature].label,
13888            "fn sample(param1: u8, param2: u8)"
13889        );
13890    });
13891}
13892
13893#[gpui::test]
13894async fn test_signature_help(cx: &mut TestAppContext) {
13895    init_test(cx, |_| {});
13896    cx.update(|cx| {
13897        cx.update_global::<SettingsStore, _>(|settings, cx| {
13898            settings.update_user_settings(cx, |settings| {
13899                settings.editor.auto_signature_help = Some(true);
13900            });
13901        });
13902    });
13903
13904    let mut cx = EditorLspTestContext::new_rust(
13905        lsp::ServerCapabilities {
13906            signature_help_provider: Some(lsp::SignatureHelpOptions {
13907                ..Default::default()
13908            }),
13909            ..Default::default()
13910        },
13911        cx,
13912    )
13913    .await;
13914
13915    // A test that directly calls `show_signature_help`
13916    cx.update_editor(|editor, window, cx| {
13917        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13918    });
13919
13920    let mocked_response = lsp::SignatureHelp {
13921        signatures: vec![lsp::SignatureInformation {
13922            label: "fn sample(param1: u8, param2: u8)".to_string(),
13923            documentation: None,
13924            parameters: Some(vec![
13925                lsp::ParameterInformation {
13926                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13927                    documentation: None,
13928                },
13929                lsp::ParameterInformation {
13930                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13931                    documentation: None,
13932                },
13933            ]),
13934            active_parameter: None,
13935        }],
13936        active_signature: Some(0),
13937        active_parameter: Some(0),
13938    };
13939    handle_signature_help_request(&mut cx, mocked_response).await;
13940
13941    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13942        .await;
13943
13944    cx.editor(|editor, _, _| {
13945        let signature_help_state = editor.signature_help_state.popover().cloned();
13946        assert!(signature_help_state.is_some());
13947        let signature = signature_help_state.unwrap();
13948        assert_eq!(
13949            signature.signatures[signature.current_signature].label,
13950            "fn sample(param1: u8, param2: u8)"
13951        );
13952    });
13953
13954    // When exiting outside from inside the brackets, `signature_help` is closed.
13955    cx.set_state(indoc! {"
13956        fn main() {
13957            sample(ˇ);
13958        }
13959
13960        fn sample(param1: u8, param2: u8) {}
13961    "});
13962
13963    cx.update_editor(|editor, window, cx| {
13964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13965            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13966        });
13967    });
13968
13969    let mocked_response = lsp::SignatureHelp {
13970        signatures: Vec::new(),
13971        active_signature: None,
13972        active_parameter: None,
13973    };
13974    handle_signature_help_request(&mut cx, mocked_response).await;
13975
13976    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13977        .await;
13978
13979    cx.editor(|editor, _, _| {
13980        assert!(!editor.signature_help_state.is_shown());
13981    });
13982
13983    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13984    cx.set_state(indoc! {"
13985        fn main() {
13986            sample(ˇ);
13987        }
13988
13989        fn sample(param1: u8, param2: u8) {}
13990    "});
13991
13992    let mocked_response = lsp::SignatureHelp {
13993        signatures: vec![lsp::SignatureInformation {
13994            label: "fn sample(param1: u8, param2: u8)".to_string(),
13995            documentation: None,
13996            parameters: Some(vec![
13997                lsp::ParameterInformation {
13998                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13999                    documentation: None,
14000                },
14001                lsp::ParameterInformation {
14002                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14003                    documentation: None,
14004                },
14005            ]),
14006            active_parameter: None,
14007        }],
14008        active_signature: Some(0),
14009        active_parameter: Some(0),
14010    };
14011    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14012    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14013        .await;
14014    cx.editor(|editor, _, _| {
14015        assert!(editor.signature_help_state.is_shown());
14016    });
14017
14018    // Restore the popover with more parameter input
14019    cx.set_state(indoc! {"
14020        fn main() {
14021            sample(param1, param2ˇ);
14022        }
14023
14024        fn sample(param1: u8, param2: u8) {}
14025    "});
14026
14027    let mocked_response = lsp::SignatureHelp {
14028        signatures: vec![lsp::SignatureInformation {
14029            label: "fn sample(param1: u8, param2: u8)".to_string(),
14030            documentation: None,
14031            parameters: Some(vec![
14032                lsp::ParameterInformation {
14033                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14034                    documentation: None,
14035                },
14036                lsp::ParameterInformation {
14037                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14038                    documentation: None,
14039                },
14040            ]),
14041            active_parameter: None,
14042        }],
14043        active_signature: Some(0),
14044        active_parameter: Some(1),
14045    };
14046    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14047    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14048        .await;
14049
14050    // When selecting a range, the popover is gone.
14051    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14052    cx.update_editor(|editor, window, cx| {
14053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14054            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14055        })
14056    });
14057    cx.assert_editor_state(indoc! {"
14058        fn main() {
14059            sample(param1, «ˇparam2»);
14060        }
14061
14062        fn sample(param1: u8, param2: u8) {}
14063    "});
14064    cx.editor(|editor, _, _| {
14065        assert!(!editor.signature_help_state.is_shown());
14066    });
14067
14068    // When unselecting again, the popover is back if within the brackets.
14069    cx.update_editor(|editor, window, cx| {
14070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14071            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14072        })
14073    });
14074    cx.assert_editor_state(indoc! {"
14075        fn main() {
14076            sample(param1, ˇparam2);
14077        }
14078
14079        fn sample(param1: u8, param2: u8) {}
14080    "});
14081    handle_signature_help_request(&mut cx, mocked_response).await;
14082    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14083        .await;
14084    cx.editor(|editor, _, _| {
14085        assert!(editor.signature_help_state.is_shown());
14086    });
14087
14088    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14089    cx.update_editor(|editor, window, cx| {
14090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14091            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14092            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14093        })
14094    });
14095    cx.assert_editor_state(indoc! {"
14096        fn main() {
14097            sample(param1, ˇparam2);
14098        }
14099
14100        fn sample(param1: u8, param2: u8) {}
14101    "});
14102
14103    let mocked_response = lsp::SignatureHelp {
14104        signatures: vec![lsp::SignatureInformation {
14105            label: "fn sample(param1: u8, param2: u8)".to_string(),
14106            documentation: None,
14107            parameters: Some(vec![
14108                lsp::ParameterInformation {
14109                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14110                    documentation: None,
14111                },
14112                lsp::ParameterInformation {
14113                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14114                    documentation: None,
14115                },
14116            ]),
14117            active_parameter: None,
14118        }],
14119        active_signature: Some(0),
14120        active_parameter: Some(1),
14121    };
14122    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14123    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14124        .await;
14125    cx.update_editor(|editor, _, cx| {
14126        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14127    });
14128    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14129        .await;
14130    cx.update_editor(|editor, window, cx| {
14131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14132            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14133        })
14134    });
14135    cx.assert_editor_state(indoc! {"
14136        fn main() {
14137            sample(param1, «ˇparam2»);
14138        }
14139
14140        fn sample(param1: u8, param2: u8) {}
14141    "});
14142    cx.update_editor(|editor, window, cx| {
14143        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14144            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14145        })
14146    });
14147    cx.assert_editor_state(indoc! {"
14148        fn main() {
14149            sample(param1, ˇparam2);
14150        }
14151
14152        fn sample(param1: u8, param2: u8) {}
14153    "});
14154    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14155        .await;
14156}
14157
14158#[gpui::test]
14159async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14160    init_test(cx, |_| {});
14161
14162    let mut cx = EditorLspTestContext::new_rust(
14163        lsp::ServerCapabilities {
14164            signature_help_provider: Some(lsp::SignatureHelpOptions {
14165                ..Default::default()
14166            }),
14167            ..Default::default()
14168        },
14169        cx,
14170    )
14171    .await;
14172
14173    cx.set_state(indoc! {"
14174        fn main() {
14175            overloadedˇ
14176        }
14177    "});
14178
14179    cx.update_editor(|editor, window, cx| {
14180        editor.handle_input("(", window, cx);
14181        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14182    });
14183
14184    // Mock response with 3 signatures
14185    let mocked_response = lsp::SignatureHelp {
14186        signatures: vec![
14187            lsp::SignatureInformation {
14188                label: "fn overloaded(x: i32)".to_string(),
14189                documentation: None,
14190                parameters: Some(vec![lsp::ParameterInformation {
14191                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14192                    documentation: None,
14193                }]),
14194                active_parameter: None,
14195            },
14196            lsp::SignatureInformation {
14197                label: "fn overloaded(x: i32, y: i32)".to_string(),
14198                documentation: None,
14199                parameters: Some(vec![
14200                    lsp::ParameterInformation {
14201                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14202                        documentation: None,
14203                    },
14204                    lsp::ParameterInformation {
14205                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14206                        documentation: None,
14207                    },
14208                ]),
14209                active_parameter: None,
14210            },
14211            lsp::SignatureInformation {
14212                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14213                documentation: None,
14214                parameters: Some(vec![
14215                    lsp::ParameterInformation {
14216                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14217                        documentation: None,
14218                    },
14219                    lsp::ParameterInformation {
14220                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14221                        documentation: None,
14222                    },
14223                    lsp::ParameterInformation {
14224                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14225                        documentation: None,
14226                    },
14227                ]),
14228                active_parameter: None,
14229            },
14230        ],
14231        active_signature: Some(1),
14232        active_parameter: Some(0),
14233    };
14234    handle_signature_help_request(&mut cx, mocked_response).await;
14235
14236    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14237        .await;
14238
14239    // Verify we have multiple signatures and the right one is selected
14240    cx.editor(|editor, _, _| {
14241        let popover = editor.signature_help_state.popover().cloned().unwrap();
14242        assert_eq!(popover.signatures.len(), 3);
14243        // active_signature was 1, so that should be the current
14244        assert_eq!(popover.current_signature, 1);
14245        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14246        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14247        assert_eq!(
14248            popover.signatures[2].label,
14249            "fn overloaded(x: i32, y: i32, z: i32)"
14250        );
14251    });
14252
14253    // Test navigation functionality
14254    cx.update_editor(|editor, window, cx| {
14255        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14256    });
14257
14258    cx.editor(|editor, _, _| {
14259        let popover = editor.signature_help_state.popover().cloned().unwrap();
14260        assert_eq!(popover.current_signature, 2);
14261    });
14262
14263    // Test wrap around
14264    cx.update_editor(|editor, window, cx| {
14265        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14266    });
14267
14268    cx.editor(|editor, _, _| {
14269        let popover = editor.signature_help_state.popover().cloned().unwrap();
14270        assert_eq!(popover.current_signature, 0);
14271    });
14272
14273    // Test previous navigation
14274    cx.update_editor(|editor, window, cx| {
14275        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14276    });
14277
14278    cx.editor(|editor, _, _| {
14279        let popover = editor.signature_help_state.popover().cloned().unwrap();
14280        assert_eq!(popover.current_signature, 2);
14281    });
14282}
14283
14284#[gpui::test]
14285async fn test_completion_mode(cx: &mut TestAppContext) {
14286    init_test(cx, |_| {});
14287    let mut cx = EditorLspTestContext::new_rust(
14288        lsp::ServerCapabilities {
14289            completion_provider: Some(lsp::CompletionOptions {
14290                resolve_provider: Some(true),
14291                ..Default::default()
14292            }),
14293            ..Default::default()
14294        },
14295        cx,
14296    )
14297    .await;
14298
14299    struct Run {
14300        run_description: &'static str,
14301        initial_state: String,
14302        buffer_marked_text: String,
14303        completion_label: &'static str,
14304        completion_text: &'static str,
14305        expected_with_insert_mode: String,
14306        expected_with_replace_mode: String,
14307        expected_with_replace_subsequence_mode: String,
14308        expected_with_replace_suffix_mode: String,
14309    }
14310
14311    let runs = [
14312        Run {
14313            run_description: "Start of word matches completion text",
14314            initial_state: "before ediˇ after".into(),
14315            buffer_marked_text: "before <edi|> after".into(),
14316            completion_label: "editor",
14317            completion_text: "editor",
14318            expected_with_insert_mode: "before editorˇ after".into(),
14319            expected_with_replace_mode: "before editorˇ after".into(),
14320            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14321            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14322        },
14323        Run {
14324            run_description: "Accept same text at the middle of the word",
14325            initial_state: "before ediˇtor after".into(),
14326            buffer_marked_text: "before <edi|tor> after".into(),
14327            completion_label: "editor",
14328            completion_text: "editor",
14329            expected_with_insert_mode: "before editorˇtor after".into(),
14330            expected_with_replace_mode: "before editorˇ after".into(),
14331            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14332            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14333        },
14334        Run {
14335            run_description: "End of word matches completion text -- cursor at end",
14336            initial_state: "before torˇ after".into(),
14337            buffer_marked_text: "before <tor|> after".into(),
14338            completion_label: "editor",
14339            completion_text: "editor",
14340            expected_with_insert_mode: "before editorˇ after".into(),
14341            expected_with_replace_mode: "before editorˇ after".into(),
14342            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14343            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14344        },
14345        Run {
14346            run_description: "End of word matches completion text -- cursor at start",
14347            initial_state: "before ˇtor after".into(),
14348            buffer_marked_text: "before <|tor> after".into(),
14349            completion_label: "editor",
14350            completion_text: "editor",
14351            expected_with_insert_mode: "before editorˇtor after".into(),
14352            expected_with_replace_mode: "before editorˇ after".into(),
14353            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14354            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14355        },
14356        Run {
14357            run_description: "Prepend text containing whitespace",
14358            initial_state: "pˇfield: bool".into(),
14359            buffer_marked_text: "<p|field>: bool".into(),
14360            completion_label: "pub ",
14361            completion_text: "pub ",
14362            expected_with_insert_mode: "pub ˇfield: bool".into(),
14363            expected_with_replace_mode: "pub ˇ: bool".into(),
14364            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14365            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14366        },
14367        Run {
14368            run_description: "Add element to start of list",
14369            initial_state: "[element_ˇelement_2]".into(),
14370            buffer_marked_text: "[<element_|element_2>]".into(),
14371            completion_label: "element_1",
14372            completion_text: "element_1",
14373            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14374            expected_with_replace_mode: "[element_1ˇ]".into(),
14375            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14376            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14377        },
14378        Run {
14379            run_description: "Add element to start of list -- first and second elements are equal",
14380            initial_state: "[elˇelement]".into(),
14381            buffer_marked_text: "[<el|element>]".into(),
14382            completion_label: "element",
14383            completion_text: "element",
14384            expected_with_insert_mode: "[elementˇelement]".into(),
14385            expected_with_replace_mode: "[elementˇ]".into(),
14386            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14387            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14388        },
14389        Run {
14390            run_description: "Ends with matching suffix",
14391            initial_state: "SubˇError".into(),
14392            buffer_marked_text: "<Sub|Error>".into(),
14393            completion_label: "SubscriptionError",
14394            completion_text: "SubscriptionError",
14395            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14396            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14397            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14398            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14399        },
14400        Run {
14401            run_description: "Suffix is a subsequence -- contiguous",
14402            initial_state: "SubˇErr".into(),
14403            buffer_marked_text: "<Sub|Err>".into(),
14404            completion_label: "SubscriptionError",
14405            completion_text: "SubscriptionError",
14406            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14407            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14408            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14409            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14410        },
14411        Run {
14412            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14413            initial_state: "Suˇscrirr".into(),
14414            buffer_marked_text: "<Su|scrirr>".into(),
14415            completion_label: "SubscriptionError",
14416            completion_text: "SubscriptionError",
14417            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14418            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14419            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14420            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14421        },
14422        Run {
14423            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14424            initial_state: "foo(indˇix)".into(),
14425            buffer_marked_text: "foo(<ind|ix>)".into(),
14426            completion_label: "node_index",
14427            completion_text: "node_index",
14428            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14429            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14430            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14431            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14432        },
14433        Run {
14434            run_description: "Replace range ends before cursor - should extend to cursor",
14435            initial_state: "before editˇo after".into(),
14436            buffer_marked_text: "before <{ed}>it|o after".into(),
14437            completion_label: "editor",
14438            completion_text: "editor",
14439            expected_with_insert_mode: "before editorˇo after".into(),
14440            expected_with_replace_mode: "before editorˇo after".into(),
14441            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14442            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14443        },
14444        Run {
14445            run_description: "Uses label for suffix matching",
14446            initial_state: "before ediˇtor after".into(),
14447            buffer_marked_text: "before <edi|tor> after".into(),
14448            completion_label: "editor",
14449            completion_text: "editor()",
14450            expected_with_insert_mode: "before editor()ˇtor after".into(),
14451            expected_with_replace_mode: "before editor()ˇ after".into(),
14452            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14453            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14454        },
14455        Run {
14456            run_description: "Case insensitive subsequence and suffix matching",
14457            initial_state: "before EDiˇtoR after".into(),
14458            buffer_marked_text: "before <EDi|toR> after".into(),
14459            completion_label: "editor",
14460            completion_text: "editor",
14461            expected_with_insert_mode: "before editorˇtoR after".into(),
14462            expected_with_replace_mode: "before editorˇ after".into(),
14463            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14464            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14465        },
14466    ];
14467
14468    for run in runs {
14469        let run_variations = [
14470            (LspInsertMode::Insert, run.expected_with_insert_mode),
14471            (LspInsertMode::Replace, run.expected_with_replace_mode),
14472            (
14473                LspInsertMode::ReplaceSubsequence,
14474                run.expected_with_replace_subsequence_mode,
14475            ),
14476            (
14477                LspInsertMode::ReplaceSuffix,
14478                run.expected_with_replace_suffix_mode,
14479            ),
14480        ];
14481
14482        for (lsp_insert_mode, expected_text) in run_variations {
14483            eprintln!(
14484                "run = {:?}, mode = {lsp_insert_mode:.?}",
14485                run.run_description,
14486            );
14487
14488            update_test_language_settings(&mut cx, |settings| {
14489                settings.defaults.completions = Some(CompletionSettingsContent {
14490                    lsp_insert_mode: Some(lsp_insert_mode),
14491                    words: Some(WordsCompletionMode::Disabled),
14492                    words_min_length: Some(0),
14493                    ..Default::default()
14494                });
14495            });
14496
14497            cx.set_state(&run.initial_state);
14498
14499            // Set up resolve handler before showing completions, since resolve may be
14500            // triggered when menu becomes visible (for documentation), not just on confirm.
14501            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14502                move |_, _, _| async move {
14503                    Ok(lsp::CompletionItem {
14504                        additional_text_edits: None,
14505                        ..Default::default()
14506                    })
14507                },
14508            );
14509
14510            cx.update_editor(|editor, window, cx| {
14511                editor.show_completions(&ShowCompletions, window, cx);
14512            });
14513
14514            let counter = Arc::new(AtomicUsize::new(0));
14515            handle_completion_request_with_insert_and_replace(
14516                &mut cx,
14517                &run.buffer_marked_text,
14518                vec![(run.completion_label, run.completion_text)],
14519                counter.clone(),
14520            )
14521            .await;
14522            cx.condition(|editor, _| editor.context_menu_visible())
14523                .await;
14524            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14525
14526            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14527                editor
14528                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14529                    .unwrap()
14530            });
14531            cx.assert_editor_state(&expected_text);
14532            apply_additional_edits.await.unwrap();
14533        }
14534    }
14535}
14536
14537#[gpui::test]
14538async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14539    init_test(cx, |_| {});
14540    let mut cx = EditorLspTestContext::new_rust(
14541        lsp::ServerCapabilities {
14542            completion_provider: Some(lsp::CompletionOptions {
14543                resolve_provider: Some(true),
14544                ..Default::default()
14545            }),
14546            ..Default::default()
14547        },
14548        cx,
14549    )
14550    .await;
14551
14552    let initial_state = "SubˇError";
14553    let buffer_marked_text = "<Sub|Error>";
14554    let completion_text = "SubscriptionError";
14555    let expected_with_insert_mode = "SubscriptionErrorˇError";
14556    let expected_with_replace_mode = "SubscriptionErrorˇ";
14557
14558    update_test_language_settings(&mut cx, |settings| {
14559        settings.defaults.completions = Some(CompletionSettingsContent {
14560            words: Some(WordsCompletionMode::Disabled),
14561            words_min_length: Some(0),
14562            // set the opposite here to ensure that the action is overriding the default behavior
14563            lsp_insert_mode: Some(LspInsertMode::Insert),
14564            ..Default::default()
14565        });
14566    });
14567
14568    cx.set_state(initial_state);
14569    cx.update_editor(|editor, window, cx| {
14570        editor.show_completions(&ShowCompletions, window, cx);
14571    });
14572
14573    let counter = Arc::new(AtomicUsize::new(0));
14574    handle_completion_request_with_insert_and_replace(
14575        &mut cx,
14576        buffer_marked_text,
14577        vec![(completion_text, completion_text)],
14578        counter.clone(),
14579    )
14580    .await;
14581    cx.condition(|editor, _| editor.context_menu_visible())
14582        .await;
14583    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14584
14585    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14586        editor
14587            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14588            .unwrap()
14589    });
14590    cx.assert_editor_state(expected_with_replace_mode);
14591    handle_resolve_completion_request(&mut cx, None).await;
14592    apply_additional_edits.await.unwrap();
14593
14594    update_test_language_settings(&mut cx, |settings| {
14595        settings.defaults.completions = Some(CompletionSettingsContent {
14596            words: Some(WordsCompletionMode::Disabled),
14597            words_min_length: Some(0),
14598            // set the opposite here to ensure that the action is overriding the default behavior
14599            lsp_insert_mode: Some(LspInsertMode::Replace),
14600            ..Default::default()
14601        });
14602    });
14603
14604    cx.set_state(initial_state);
14605    cx.update_editor(|editor, window, cx| {
14606        editor.show_completions(&ShowCompletions, window, cx);
14607    });
14608    handle_completion_request_with_insert_and_replace(
14609        &mut cx,
14610        buffer_marked_text,
14611        vec![(completion_text, completion_text)],
14612        counter.clone(),
14613    )
14614    .await;
14615    cx.condition(|editor, _| editor.context_menu_visible())
14616        .await;
14617    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14618
14619    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14620        editor
14621            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14622            .unwrap()
14623    });
14624    cx.assert_editor_state(expected_with_insert_mode);
14625    handle_resolve_completion_request(&mut cx, None).await;
14626    apply_additional_edits.await.unwrap();
14627}
14628
14629#[gpui::test]
14630async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14631    init_test(cx, |_| {});
14632    let mut cx = EditorLspTestContext::new_rust(
14633        lsp::ServerCapabilities {
14634            completion_provider: Some(lsp::CompletionOptions {
14635                resolve_provider: Some(true),
14636                ..Default::default()
14637            }),
14638            ..Default::default()
14639        },
14640        cx,
14641    )
14642    .await;
14643
14644    // scenario: surrounding text matches completion text
14645    let completion_text = "to_offset";
14646    let initial_state = indoc! {"
14647        1. buf.to_offˇsuffix
14648        2. buf.to_offˇsuf
14649        3. buf.to_offˇfix
14650        4. buf.to_offˇ
14651        5. into_offˇensive
14652        6. ˇsuffix
14653        7. let ˇ //
14654        8. aaˇzz
14655        9. buf.to_off«zzzzzˇ»suffix
14656        10. buf.«ˇzzzzz»suffix
14657        11. to_off«ˇzzzzz»
14658
14659        buf.to_offˇsuffix  // newest cursor
14660    "};
14661    let completion_marked_buffer = indoc! {"
14662        1. buf.to_offsuffix
14663        2. buf.to_offsuf
14664        3. buf.to_offfix
14665        4. buf.to_off
14666        5. into_offensive
14667        6. suffix
14668        7. let  //
14669        8. aazz
14670        9. buf.to_offzzzzzsuffix
14671        10. buf.zzzzzsuffix
14672        11. to_offzzzzz
14673
14674        buf.<to_off|suffix>  // newest cursor
14675    "};
14676    let expected = indoc! {"
14677        1. buf.to_offsetˇ
14678        2. buf.to_offsetˇsuf
14679        3. buf.to_offsetˇfix
14680        4. buf.to_offsetˇ
14681        5. into_offsetˇensive
14682        6. to_offsetˇsuffix
14683        7. let to_offsetˇ //
14684        8. aato_offsetˇzz
14685        9. buf.to_offsetˇ
14686        10. buf.to_offsetˇsuffix
14687        11. to_offsetˇ
14688
14689        buf.to_offsetˇ  // newest cursor
14690    "};
14691    cx.set_state(initial_state);
14692    cx.update_editor(|editor, window, cx| {
14693        editor.show_completions(&ShowCompletions, window, cx);
14694    });
14695    handle_completion_request_with_insert_and_replace(
14696        &mut cx,
14697        completion_marked_buffer,
14698        vec![(completion_text, completion_text)],
14699        Arc::new(AtomicUsize::new(0)),
14700    )
14701    .await;
14702    cx.condition(|editor, _| editor.context_menu_visible())
14703        .await;
14704    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14705        editor
14706            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14707            .unwrap()
14708    });
14709    cx.assert_editor_state(expected);
14710    handle_resolve_completion_request(&mut cx, None).await;
14711    apply_additional_edits.await.unwrap();
14712
14713    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14714    let completion_text = "foo_and_bar";
14715    let initial_state = indoc! {"
14716        1. ooanbˇ
14717        2. zooanbˇ
14718        3. ooanbˇz
14719        4. zooanbˇz
14720        5. ooanˇ
14721        6. oanbˇ
14722
14723        ooanbˇ
14724    "};
14725    let completion_marked_buffer = indoc! {"
14726        1. ooanb
14727        2. zooanb
14728        3. ooanbz
14729        4. zooanbz
14730        5. ooan
14731        6. oanb
14732
14733        <ooanb|>
14734    "};
14735    let expected = indoc! {"
14736        1. foo_and_barˇ
14737        2. zfoo_and_barˇ
14738        3. foo_and_barˇz
14739        4. zfoo_and_barˇz
14740        5. ooanfoo_and_barˇ
14741        6. oanbfoo_and_barˇ
14742
14743        foo_and_barˇ
14744    "};
14745    cx.set_state(initial_state);
14746    cx.update_editor(|editor, window, cx| {
14747        editor.show_completions(&ShowCompletions, window, cx);
14748    });
14749    handle_completion_request_with_insert_and_replace(
14750        &mut cx,
14751        completion_marked_buffer,
14752        vec![(completion_text, completion_text)],
14753        Arc::new(AtomicUsize::new(0)),
14754    )
14755    .await;
14756    cx.condition(|editor, _| editor.context_menu_visible())
14757        .await;
14758    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14759        editor
14760            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14761            .unwrap()
14762    });
14763    cx.assert_editor_state(expected);
14764    handle_resolve_completion_request(&mut cx, None).await;
14765    apply_additional_edits.await.unwrap();
14766
14767    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14768    // (expects the same as if it was inserted at the end)
14769    let completion_text = "foo_and_bar";
14770    let initial_state = indoc! {"
14771        1. ooˇanb
14772        2. zooˇanb
14773        3. ooˇanbz
14774        4. zooˇanbz
14775
14776        ooˇanb
14777    "};
14778    let completion_marked_buffer = indoc! {"
14779        1. ooanb
14780        2. zooanb
14781        3. ooanbz
14782        4. zooanbz
14783
14784        <oo|anb>
14785    "};
14786    let expected = indoc! {"
14787        1. foo_and_barˇ
14788        2. zfoo_and_barˇ
14789        3. foo_and_barˇz
14790        4. zfoo_and_barˇz
14791
14792        foo_and_barˇ
14793    "};
14794    cx.set_state(initial_state);
14795    cx.update_editor(|editor, window, cx| {
14796        editor.show_completions(&ShowCompletions, window, cx);
14797    });
14798    handle_completion_request_with_insert_and_replace(
14799        &mut cx,
14800        completion_marked_buffer,
14801        vec![(completion_text, completion_text)],
14802        Arc::new(AtomicUsize::new(0)),
14803    )
14804    .await;
14805    cx.condition(|editor, _| editor.context_menu_visible())
14806        .await;
14807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14808        editor
14809            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14810            .unwrap()
14811    });
14812    cx.assert_editor_state(expected);
14813    handle_resolve_completion_request(&mut cx, None).await;
14814    apply_additional_edits.await.unwrap();
14815}
14816
14817// This used to crash
14818#[gpui::test]
14819async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14820    init_test(cx, |_| {});
14821
14822    let buffer_text = indoc! {"
14823        fn main() {
14824            10.satu;
14825
14826            //
14827            // separate cursors so they open in different excerpts (manually reproducible)
14828            //
14829
14830            10.satu20;
14831        }
14832    "};
14833    let multibuffer_text_with_selections = indoc! {"
14834        fn main() {
14835            10.satuˇ;
14836
14837            //
14838
14839            //
14840
14841            10.satuˇ20;
14842        }
14843    "};
14844    let expected_multibuffer = indoc! {"
14845        fn main() {
14846            10.saturating_sub()ˇ;
14847
14848            //
14849
14850            //
14851
14852            10.saturating_sub()ˇ;
14853        }
14854    "};
14855
14856    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14857    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14858
14859    let fs = FakeFs::new(cx.executor());
14860    fs.insert_tree(
14861        path!("/a"),
14862        json!({
14863            "main.rs": buffer_text,
14864        }),
14865    )
14866    .await;
14867
14868    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14869    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14870    language_registry.add(rust_lang());
14871    let mut fake_servers = language_registry.register_fake_lsp(
14872        "Rust",
14873        FakeLspAdapter {
14874            capabilities: lsp::ServerCapabilities {
14875                completion_provider: Some(lsp::CompletionOptions {
14876                    resolve_provider: None,
14877                    ..lsp::CompletionOptions::default()
14878                }),
14879                ..lsp::ServerCapabilities::default()
14880            },
14881            ..FakeLspAdapter::default()
14882        },
14883    );
14884    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14885    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14886    let buffer = project
14887        .update(cx, |project, cx| {
14888            project.open_local_buffer(path!("/a/main.rs"), cx)
14889        })
14890        .await
14891        .unwrap();
14892
14893    let multi_buffer = cx.new(|cx| {
14894        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14895        multi_buffer.push_excerpts(
14896            buffer.clone(),
14897            [ExcerptRange::new(0..first_excerpt_end)],
14898            cx,
14899        );
14900        multi_buffer.push_excerpts(
14901            buffer.clone(),
14902            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14903            cx,
14904        );
14905        multi_buffer
14906    });
14907
14908    let editor = workspace
14909        .update(cx, |_, window, cx| {
14910            cx.new(|cx| {
14911                Editor::new(
14912                    EditorMode::Full {
14913                        scale_ui_elements_with_buffer_font_size: false,
14914                        show_active_line_background: false,
14915                        sizing_behavior: SizingBehavior::Default,
14916                    },
14917                    multi_buffer.clone(),
14918                    Some(project.clone()),
14919                    window,
14920                    cx,
14921                )
14922            })
14923        })
14924        .unwrap();
14925
14926    let pane = workspace
14927        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14928        .unwrap();
14929    pane.update_in(cx, |pane, window, cx| {
14930        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14931    });
14932
14933    let fake_server = fake_servers.next().await.unwrap();
14934    cx.run_until_parked();
14935
14936    editor.update_in(cx, |editor, window, cx| {
14937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14938            s.select_ranges([
14939                Point::new(1, 11)..Point::new(1, 11),
14940                Point::new(7, 11)..Point::new(7, 11),
14941            ])
14942        });
14943
14944        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14945    });
14946
14947    editor.update_in(cx, |editor, window, cx| {
14948        editor.show_completions(&ShowCompletions, window, cx);
14949    });
14950
14951    fake_server
14952        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14953            let completion_item = lsp::CompletionItem {
14954                label: "saturating_sub()".into(),
14955                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14956                    lsp::InsertReplaceEdit {
14957                        new_text: "saturating_sub()".to_owned(),
14958                        insert: lsp::Range::new(
14959                            lsp::Position::new(7, 7),
14960                            lsp::Position::new(7, 11),
14961                        ),
14962                        replace: lsp::Range::new(
14963                            lsp::Position::new(7, 7),
14964                            lsp::Position::new(7, 13),
14965                        ),
14966                    },
14967                )),
14968                ..lsp::CompletionItem::default()
14969            };
14970
14971            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14972        })
14973        .next()
14974        .await
14975        .unwrap();
14976
14977    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14978        .await;
14979
14980    editor
14981        .update_in(cx, |editor, window, cx| {
14982            editor
14983                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14984                .unwrap()
14985        })
14986        .await
14987        .unwrap();
14988
14989    editor.update(cx, |editor, cx| {
14990        assert_text_with_selections(editor, expected_multibuffer, cx);
14991    })
14992}
14993
14994#[gpui::test]
14995async fn test_completion(cx: &mut TestAppContext) {
14996    init_test(cx, |_| {});
14997
14998    let mut cx = EditorLspTestContext::new_rust(
14999        lsp::ServerCapabilities {
15000            completion_provider: Some(lsp::CompletionOptions {
15001                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15002                resolve_provider: Some(true),
15003                ..Default::default()
15004            }),
15005            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15006            ..Default::default()
15007        },
15008        cx,
15009    )
15010    .await;
15011    let counter = Arc::new(AtomicUsize::new(0));
15012
15013    cx.set_state(indoc! {"
15014        oneˇ
15015        two
15016        three
15017    "});
15018    cx.simulate_keystroke(".");
15019    handle_completion_request(
15020        indoc! {"
15021            one.|<>
15022            two
15023            three
15024        "},
15025        vec!["first_completion", "second_completion"],
15026        true,
15027        counter.clone(),
15028        &mut cx,
15029    )
15030    .await;
15031    cx.condition(|editor, _| editor.context_menu_visible())
15032        .await;
15033    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15034
15035    let _handler = handle_signature_help_request(
15036        &mut cx,
15037        lsp::SignatureHelp {
15038            signatures: vec![lsp::SignatureInformation {
15039                label: "test signature".to_string(),
15040                documentation: None,
15041                parameters: Some(vec![lsp::ParameterInformation {
15042                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15043                    documentation: None,
15044                }]),
15045                active_parameter: None,
15046            }],
15047            active_signature: None,
15048            active_parameter: None,
15049        },
15050    );
15051    cx.update_editor(|editor, window, cx| {
15052        assert!(
15053            !editor.signature_help_state.is_shown(),
15054            "No signature help was called for"
15055        );
15056        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15057    });
15058    cx.run_until_parked();
15059    cx.update_editor(|editor, _, _| {
15060        assert!(
15061            !editor.signature_help_state.is_shown(),
15062            "No signature help should be shown when completions menu is open"
15063        );
15064    });
15065
15066    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15067        editor.context_menu_next(&Default::default(), window, cx);
15068        editor
15069            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15070            .unwrap()
15071    });
15072    cx.assert_editor_state(indoc! {"
15073        one.second_completionˇ
15074        two
15075        three
15076    "});
15077
15078    handle_resolve_completion_request(
15079        &mut cx,
15080        Some(vec![
15081            (
15082                //This overlaps with the primary completion edit which is
15083                //misbehavior from the LSP spec, test that we filter it out
15084                indoc! {"
15085                    one.second_ˇcompletion
15086                    two
15087                    threeˇ
15088                "},
15089                "overlapping additional edit",
15090            ),
15091            (
15092                indoc! {"
15093                    one.second_completion
15094                    two
15095                    threeˇ
15096                "},
15097                "\nadditional edit",
15098            ),
15099        ]),
15100    )
15101    .await;
15102    apply_additional_edits.await.unwrap();
15103    cx.assert_editor_state(indoc! {"
15104        one.second_completionˇ
15105        two
15106        three
15107        additional edit
15108    "});
15109
15110    cx.set_state(indoc! {"
15111        one.second_completion
15112        twoˇ
15113        threeˇ
15114        additional edit
15115    "});
15116    cx.simulate_keystroke(" ");
15117    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15118    cx.simulate_keystroke("s");
15119    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15120
15121    cx.assert_editor_state(indoc! {"
15122        one.second_completion
15123        two sˇ
15124        three sˇ
15125        additional edit
15126    "});
15127    handle_completion_request(
15128        indoc! {"
15129            one.second_completion
15130            two s
15131            three <s|>
15132            additional edit
15133        "},
15134        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15135        true,
15136        counter.clone(),
15137        &mut cx,
15138    )
15139    .await;
15140    cx.condition(|editor, _| editor.context_menu_visible())
15141        .await;
15142    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15143
15144    cx.simulate_keystroke("i");
15145
15146    handle_completion_request(
15147        indoc! {"
15148            one.second_completion
15149            two si
15150            three <si|>
15151            additional edit
15152        "},
15153        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15154        true,
15155        counter.clone(),
15156        &mut cx,
15157    )
15158    .await;
15159    cx.condition(|editor, _| editor.context_menu_visible())
15160        .await;
15161    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15162
15163    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15164        editor
15165            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15166            .unwrap()
15167    });
15168    cx.assert_editor_state(indoc! {"
15169        one.second_completion
15170        two sixth_completionˇ
15171        three sixth_completionˇ
15172        additional edit
15173    "});
15174
15175    apply_additional_edits.await.unwrap();
15176
15177    update_test_language_settings(&mut cx, |settings| {
15178        settings.defaults.show_completions_on_input = Some(false);
15179    });
15180    cx.set_state("editorˇ");
15181    cx.simulate_keystroke(".");
15182    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15183    cx.simulate_keystrokes("c l o");
15184    cx.assert_editor_state("editor.cloˇ");
15185    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15186    cx.update_editor(|editor, window, cx| {
15187        editor.show_completions(&ShowCompletions, window, cx);
15188    });
15189    handle_completion_request(
15190        "editor.<clo|>",
15191        vec!["close", "clobber"],
15192        true,
15193        counter.clone(),
15194        &mut cx,
15195    )
15196    .await;
15197    cx.condition(|editor, _| editor.context_menu_visible())
15198        .await;
15199    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15200
15201    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15202        editor
15203            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15204            .unwrap()
15205    });
15206    cx.assert_editor_state("editor.clobberˇ");
15207    handle_resolve_completion_request(&mut cx, None).await;
15208    apply_additional_edits.await.unwrap();
15209}
15210
15211#[gpui::test]
15212async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15213    init_test(cx, |_| {});
15214
15215    let fs = FakeFs::new(cx.executor());
15216    fs.insert_tree(
15217        path!("/a"),
15218        json!({
15219            "main.rs": "",
15220        }),
15221    )
15222    .await;
15223
15224    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15225    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15226    language_registry.add(rust_lang());
15227    let command_calls = Arc::new(AtomicUsize::new(0));
15228    let registered_command = "_the/command";
15229
15230    let closure_command_calls = command_calls.clone();
15231    let mut fake_servers = language_registry.register_fake_lsp(
15232        "Rust",
15233        FakeLspAdapter {
15234            capabilities: lsp::ServerCapabilities {
15235                completion_provider: Some(lsp::CompletionOptions {
15236                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15237                    ..lsp::CompletionOptions::default()
15238                }),
15239                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15240                    commands: vec![registered_command.to_owned()],
15241                    ..lsp::ExecuteCommandOptions::default()
15242                }),
15243                ..lsp::ServerCapabilities::default()
15244            },
15245            initializer: Some(Box::new(move |fake_server| {
15246                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15247                    move |params, _| async move {
15248                        Ok(Some(lsp::CompletionResponse::Array(vec![
15249                            lsp::CompletionItem {
15250                                label: "registered_command".to_owned(),
15251                                text_edit: gen_text_edit(&params, ""),
15252                                command: Some(lsp::Command {
15253                                    title: registered_command.to_owned(),
15254                                    command: "_the/command".to_owned(),
15255                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15256                                }),
15257                                ..lsp::CompletionItem::default()
15258                            },
15259                            lsp::CompletionItem {
15260                                label: "unregistered_command".to_owned(),
15261                                text_edit: gen_text_edit(&params, ""),
15262                                command: Some(lsp::Command {
15263                                    title: "????????????".to_owned(),
15264                                    command: "????????????".to_owned(),
15265                                    arguments: Some(vec![serde_json::Value::Null]),
15266                                }),
15267                                ..lsp::CompletionItem::default()
15268                            },
15269                        ])))
15270                    },
15271                );
15272                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15273                    let command_calls = closure_command_calls.clone();
15274                    move |params, _| {
15275                        assert_eq!(params.command, registered_command);
15276                        let command_calls = command_calls.clone();
15277                        async move {
15278                            command_calls.fetch_add(1, atomic::Ordering::Release);
15279                            Ok(Some(json!(null)))
15280                        }
15281                    }
15282                });
15283            })),
15284            ..FakeLspAdapter::default()
15285        },
15286    );
15287    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15288    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15289    let editor = workspace
15290        .update(cx, |workspace, window, cx| {
15291            workspace.open_abs_path(
15292                PathBuf::from(path!("/a/main.rs")),
15293                OpenOptions::default(),
15294                window,
15295                cx,
15296            )
15297        })
15298        .unwrap()
15299        .await
15300        .unwrap()
15301        .downcast::<Editor>()
15302        .unwrap();
15303    let _fake_server = fake_servers.next().await.unwrap();
15304    cx.run_until_parked();
15305
15306    editor.update_in(cx, |editor, window, cx| {
15307        cx.focus_self(window);
15308        editor.move_to_end(&MoveToEnd, window, cx);
15309        editor.handle_input(".", window, cx);
15310    });
15311    cx.run_until_parked();
15312    editor.update(cx, |editor, _| {
15313        assert!(editor.context_menu_visible());
15314        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15315        {
15316            let completion_labels = menu
15317                .completions
15318                .borrow()
15319                .iter()
15320                .map(|c| c.label.text.clone())
15321                .collect::<Vec<_>>();
15322            assert_eq!(
15323                completion_labels,
15324                &["registered_command", "unregistered_command",],
15325            );
15326        } else {
15327            panic!("expected completion menu to be open");
15328        }
15329    });
15330
15331    editor
15332        .update_in(cx, |editor, window, cx| {
15333            editor
15334                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15335                .unwrap()
15336        })
15337        .await
15338        .unwrap();
15339    cx.run_until_parked();
15340    assert_eq!(
15341        command_calls.load(atomic::Ordering::Acquire),
15342        1,
15343        "For completion with a registered command, Zed should send a command execution request",
15344    );
15345
15346    editor.update_in(cx, |editor, window, cx| {
15347        cx.focus_self(window);
15348        editor.handle_input(".", window, cx);
15349    });
15350    cx.run_until_parked();
15351    editor.update(cx, |editor, _| {
15352        assert!(editor.context_menu_visible());
15353        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15354        {
15355            let completion_labels = menu
15356                .completions
15357                .borrow()
15358                .iter()
15359                .map(|c| c.label.text.clone())
15360                .collect::<Vec<_>>();
15361            assert_eq!(
15362                completion_labels,
15363                &["registered_command", "unregistered_command",],
15364            );
15365        } else {
15366            panic!("expected completion menu to be open");
15367        }
15368    });
15369    editor
15370        .update_in(cx, |editor, window, cx| {
15371            editor.context_menu_next(&Default::default(), window, cx);
15372            editor
15373                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15374                .unwrap()
15375        })
15376        .await
15377        .unwrap();
15378    cx.run_until_parked();
15379    assert_eq!(
15380        command_calls.load(atomic::Ordering::Acquire),
15381        1,
15382        "For completion with an unregistered command, Zed should not send a command execution request",
15383    );
15384}
15385
15386#[gpui::test]
15387async fn test_completion_reuse(cx: &mut TestAppContext) {
15388    init_test(cx, |_| {});
15389
15390    let mut cx = EditorLspTestContext::new_rust(
15391        lsp::ServerCapabilities {
15392            completion_provider: Some(lsp::CompletionOptions {
15393                trigger_characters: Some(vec![".".to_string()]),
15394                ..Default::default()
15395            }),
15396            ..Default::default()
15397        },
15398        cx,
15399    )
15400    .await;
15401
15402    let counter = Arc::new(AtomicUsize::new(0));
15403    cx.set_state("objˇ");
15404    cx.simulate_keystroke(".");
15405
15406    // Initial completion request returns complete results
15407    let is_incomplete = false;
15408    handle_completion_request(
15409        "obj.|<>",
15410        vec!["a", "ab", "abc"],
15411        is_incomplete,
15412        counter.clone(),
15413        &mut cx,
15414    )
15415    .await;
15416    cx.run_until_parked();
15417    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15418    cx.assert_editor_state("obj.ˇ");
15419    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15420
15421    // Type "a" - filters existing completions
15422    cx.simulate_keystroke("a");
15423    cx.run_until_parked();
15424    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15425    cx.assert_editor_state("obj.aˇ");
15426    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15427
15428    // Type "b" - filters existing completions
15429    cx.simulate_keystroke("b");
15430    cx.run_until_parked();
15431    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15432    cx.assert_editor_state("obj.abˇ");
15433    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15434
15435    // Type "c" - filters existing completions
15436    cx.simulate_keystroke("c");
15437    cx.run_until_parked();
15438    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15439    cx.assert_editor_state("obj.abcˇ");
15440    check_displayed_completions(vec!["abc"], &mut cx);
15441
15442    // Backspace to delete "c" - filters existing completions
15443    cx.update_editor(|editor, window, cx| {
15444        editor.backspace(&Backspace, window, cx);
15445    });
15446    cx.run_until_parked();
15447    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15448    cx.assert_editor_state("obj.abˇ");
15449    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15450
15451    // Moving cursor to the left dismisses menu.
15452    cx.update_editor(|editor, window, cx| {
15453        editor.move_left(&MoveLeft, window, cx);
15454    });
15455    cx.run_until_parked();
15456    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15457    cx.assert_editor_state("obj.aˇb");
15458    cx.update_editor(|editor, _, _| {
15459        assert_eq!(editor.context_menu_visible(), false);
15460    });
15461
15462    // Type "b" - new request
15463    cx.simulate_keystroke("b");
15464    let is_incomplete = false;
15465    handle_completion_request(
15466        "obj.<ab|>a",
15467        vec!["ab", "abc"],
15468        is_incomplete,
15469        counter.clone(),
15470        &mut cx,
15471    )
15472    .await;
15473    cx.run_until_parked();
15474    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15475    cx.assert_editor_state("obj.abˇb");
15476    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15477
15478    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15479    cx.update_editor(|editor, window, cx| {
15480        editor.backspace(&Backspace, window, cx);
15481    });
15482    let is_incomplete = false;
15483    handle_completion_request(
15484        "obj.<a|>b",
15485        vec!["a", "ab", "abc"],
15486        is_incomplete,
15487        counter.clone(),
15488        &mut cx,
15489    )
15490    .await;
15491    cx.run_until_parked();
15492    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15493    cx.assert_editor_state("obj.aˇb");
15494    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15495
15496    // Backspace to delete "a" - dismisses menu.
15497    cx.update_editor(|editor, window, cx| {
15498        editor.backspace(&Backspace, window, cx);
15499    });
15500    cx.run_until_parked();
15501    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15502    cx.assert_editor_state("obj.ˇb");
15503    cx.update_editor(|editor, _, _| {
15504        assert_eq!(editor.context_menu_visible(), false);
15505    });
15506}
15507
15508#[gpui::test]
15509async fn test_word_completion(cx: &mut TestAppContext) {
15510    let lsp_fetch_timeout_ms = 10;
15511    init_test(cx, |language_settings| {
15512        language_settings.defaults.completions = Some(CompletionSettingsContent {
15513            words_min_length: Some(0),
15514            lsp_fetch_timeout_ms: Some(10),
15515            lsp_insert_mode: Some(LspInsertMode::Insert),
15516            ..Default::default()
15517        });
15518    });
15519
15520    let mut cx = EditorLspTestContext::new_rust(
15521        lsp::ServerCapabilities {
15522            completion_provider: Some(lsp::CompletionOptions {
15523                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15524                ..lsp::CompletionOptions::default()
15525            }),
15526            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15527            ..lsp::ServerCapabilities::default()
15528        },
15529        cx,
15530    )
15531    .await;
15532
15533    let throttle_completions = Arc::new(AtomicBool::new(false));
15534
15535    let lsp_throttle_completions = throttle_completions.clone();
15536    let _completion_requests_handler =
15537        cx.lsp
15538            .server
15539            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15540                let lsp_throttle_completions = lsp_throttle_completions.clone();
15541                let cx = cx.clone();
15542                async move {
15543                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15544                        cx.background_executor()
15545                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15546                            .await;
15547                    }
15548                    Ok(Some(lsp::CompletionResponse::Array(vec![
15549                        lsp::CompletionItem {
15550                            label: "first".into(),
15551                            ..lsp::CompletionItem::default()
15552                        },
15553                        lsp::CompletionItem {
15554                            label: "last".into(),
15555                            ..lsp::CompletionItem::default()
15556                        },
15557                    ])))
15558                }
15559            });
15560
15561    cx.set_state(indoc! {"
15562        oneˇ
15563        two
15564        three
15565    "});
15566    cx.simulate_keystroke(".");
15567    cx.executor().run_until_parked();
15568    cx.condition(|editor, _| editor.context_menu_visible())
15569        .await;
15570    cx.update_editor(|editor, window, cx| {
15571        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15572        {
15573            assert_eq!(
15574                completion_menu_entries(menu),
15575                &["first", "last"],
15576                "When LSP server is fast to reply, no fallback word completions are used"
15577            );
15578        } else {
15579            panic!("expected completion menu to be open");
15580        }
15581        editor.cancel(&Cancel, window, cx);
15582    });
15583    cx.executor().run_until_parked();
15584    cx.condition(|editor, _| !editor.context_menu_visible())
15585        .await;
15586
15587    throttle_completions.store(true, atomic::Ordering::Release);
15588    cx.simulate_keystroke(".");
15589    cx.executor()
15590        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15591    cx.executor().run_until_parked();
15592    cx.condition(|editor, _| editor.context_menu_visible())
15593        .await;
15594    cx.update_editor(|editor, _, _| {
15595        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15596        {
15597            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15598                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15599        } else {
15600            panic!("expected completion menu to be open");
15601        }
15602    });
15603}
15604
15605#[gpui::test]
15606async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15607    init_test(cx, |language_settings| {
15608        language_settings.defaults.completions = Some(CompletionSettingsContent {
15609            words: Some(WordsCompletionMode::Enabled),
15610            words_min_length: Some(0),
15611            lsp_insert_mode: Some(LspInsertMode::Insert),
15612            ..Default::default()
15613        });
15614    });
15615
15616    let mut cx = EditorLspTestContext::new_rust(
15617        lsp::ServerCapabilities {
15618            completion_provider: Some(lsp::CompletionOptions {
15619                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15620                ..lsp::CompletionOptions::default()
15621            }),
15622            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15623            ..lsp::ServerCapabilities::default()
15624        },
15625        cx,
15626    )
15627    .await;
15628
15629    let _completion_requests_handler =
15630        cx.lsp
15631            .server
15632            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15633                Ok(Some(lsp::CompletionResponse::Array(vec![
15634                    lsp::CompletionItem {
15635                        label: "first".into(),
15636                        ..lsp::CompletionItem::default()
15637                    },
15638                    lsp::CompletionItem {
15639                        label: "last".into(),
15640                        ..lsp::CompletionItem::default()
15641                    },
15642                ])))
15643            });
15644
15645    cx.set_state(indoc! {"ˇ
15646        first
15647        last
15648        second
15649    "});
15650    cx.simulate_keystroke(".");
15651    cx.executor().run_until_parked();
15652    cx.condition(|editor, _| editor.context_menu_visible())
15653        .await;
15654    cx.update_editor(|editor, _, _| {
15655        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15656        {
15657            assert_eq!(
15658                completion_menu_entries(menu),
15659                &["first", "last", "second"],
15660                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15661            );
15662        } else {
15663            panic!("expected completion menu to be open");
15664        }
15665    });
15666}
15667
15668#[gpui::test]
15669async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15670    init_test(cx, |language_settings| {
15671        language_settings.defaults.completions = Some(CompletionSettingsContent {
15672            words: Some(WordsCompletionMode::Disabled),
15673            words_min_length: Some(0),
15674            lsp_insert_mode: Some(LspInsertMode::Insert),
15675            ..Default::default()
15676        });
15677    });
15678
15679    let mut cx = EditorLspTestContext::new_rust(
15680        lsp::ServerCapabilities {
15681            completion_provider: Some(lsp::CompletionOptions {
15682                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15683                ..lsp::CompletionOptions::default()
15684            }),
15685            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15686            ..lsp::ServerCapabilities::default()
15687        },
15688        cx,
15689    )
15690    .await;
15691
15692    let _completion_requests_handler =
15693        cx.lsp
15694            .server
15695            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15696                panic!("LSP completions should not be queried when dealing with word completions")
15697            });
15698
15699    cx.set_state(indoc! {"ˇ
15700        first
15701        last
15702        second
15703    "});
15704    cx.update_editor(|editor, window, cx| {
15705        editor.show_word_completions(&ShowWordCompletions, window, cx);
15706    });
15707    cx.executor().run_until_parked();
15708    cx.condition(|editor, _| editor.context_menu_visible())
15709        .await;
15710    cx.update_editor(|editor, _, _| {
15711        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15712        {
15713            assert_eq!(
15714                completion_menu_entries(menu),
15715                &["first", "last", "second"],
15716                "`ShowWordCompletions` action should show word completions"
15717            );
15718        } else {
15719            panic!("expected completion menu to be open");
15720        }
15721    });
15722
15723    cx.simulate_keystroke("l");
15724    cx.executor().run_until_parked();
15725    cx.condition(|editor, _| editor.context_menu_visible())
15726        .await;
15727    cx.update_editor(|editor, _, _| {
15728        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15729        {
15730            assert_eq!(
15731                completion_menu_entries(menu),
15732                &["last"],
15733                "After showing word completions, further editing should filter them and not query the LSP"
15734            );
15735        } else {
15736            panic!("expected completion menu to be open");
15737        }
15738    });
15739}
15740
15741#[gpui::test]
15742async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15743    init_test(cx, |language_settings| {
15744        language_settings.defaults.completions = Some(CompletionSettingsContent {
15745            words_min_length: Some(0),
15746            lsp: Some(false),
15747            lsp_insert_mode: Some(LspInsertMode::Insert),
15748            ..Default::default()
15749        });
15750    });
15751
15752    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15753
15754    cx.set_state(indoc! {"ˇ
15755        0_usize
15756        let
15757        33
15758        4.5f32
15759    "});
15760    cx.update_editor(|editor, window, cx| {
15761        editor.show_completions(&ShowCompletions, window, cx);
15762    });
15763    cx.executor().run_until_parked();
15764    cx.condition(|editor, _| editor.context_menu_visible())
15765        .await;
15766    cx.update_editor(|editor, window, cx| {
15767        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15768        {
15769            assert_eq!(
15770                completion_menu_entries(menu),
15771                &["let"],
15772                "With no digits in the completion query, no digits should be in the word completions"
15773            );
15774        } else {
15775            panic!("expected completion menu to be open");
15776        }
15777        editor.cancel(&Cancel, window, cx);
15778    });
15779
15780    cx.set_state(indoc! {"15781        0_usize
15782        let
15783        3
15784        33.35f32
15785    "});
15786    cx.update_editor(|editor, window, cx| {
15787        editor.show_completions(&ShowCompletions, window, cx);
15788    });
15789    cx.executor().run_until_parked();
15790    cx.condition(|editor, _| editor.context_menu_visible())
15791        .await;
15792    cx.update_editor(|editor, _, _| {
15793        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15794        {
15795            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15796                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15797        } else {
15798            panic!("expected completion menu to be open");
15799        }
15800    });
15801}
15802
15803#[gpui::test]
15804async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15805    init_test(cx, |language_settings| {
15806        language_settings.defaults.completions = Some(CompletionSettingsContent {
15807            words: Some(WordsCompletionMode::Enabled),
15808            words_min_length: Some(3),
15809            lsp_insert_mode: Some(LspInsertMode::Insert),
15810            ..Default::default()
15811        });
15812    });
15813
15814    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15815    cx.set_state(indoc! {"ˇ
15816        wow
15817        wowen
15818        wowser
15819    "});
15820    cx.simulate_keystroke("w");
15821    cx.executor().run_until_parked();
15822    cx.update_editor(|editor, _, _| {
15823        if editor.context_menu.borrow_mut().is_some() {
15824            panic!(
15825                "expected completion menu to be hidden, as words completion threshold is not met"
15826            );
15827        }
15828    });
15829
15830    cx.update_editor(|editor, window, cx| {
15831        editor.show_word_completions(&ShowWordCompletions, window, cx);
15832    });
15833    cx.executor().run_until_parked();
15834    cx.update_editor(|editor, window, cx| {
15835        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15836        {
15837            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");
15838        } else {
15839            panic!("expected completion menu to be open after the word completions are called with an action");
15840        }
15841
15842        editor.cancel(&Cancel, window, cx);
15843    });
15844    cx.update_editor(|editor, _, _| {
15845        if editor.context_menu.borrow_mut().is_some() {
15846            panic!("expected completion menu to be hidden after canceling");
15847        }
15848    });
15849
15850    cx.simulate_keystroke("o");
15851    cx.executor().run_until_parked();
15852    cx.update_editor(|editor, _, _| {
15853        if editor.context_menu.borrow_mut().is_some() {
15854            panic!(
15855                "expected completion menu to be hidden, as words completion threshold is not met still"
15856            );
15857        }
15858    });
15859
15860    cx.simulate_keystroke("w");
15861    cx.executor().run_until_parked();
15862    cx.update_editor(|editor, _, _| {
15863        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15864        {
15865            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15866        } else {
15867            panic!("expected completion menu to be open after the word completions threshold is met");
15868        }
15869    });
15870}
15871
15872#[gpui::test]
15873async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15874    init_test(cx, |language_settings| {
15875        language_settings.defaults.completions = Some(CompletionSettingsContent {
15876            words: Some(WordsCompletionMode::Enabled),
15877            words_min_length: Some(0),
15878            lsp_insert_mode: Some(LspInsertMode::Insert),
15879            ..Default::default()
15880        });
15881    });
15882
15883    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15884    cx.update_editor(|editor, _, _| {
15885        editor.disable_word_completions();
15886    });
15887    cx.set_state(indoc! {"ˇ
15888        wow
15889        wowen
15890        wowser
15891    "});
15892    cx.simulate_keystroke("w");
15893    cx.executor().run_until_parked();
15894    cx.update_editor(|editor, _, _| {
15895        if editor.context_menu.borrow_mut().is_some() {
15896            panic!(
15897                "expected completion menu to be hidden, as words completion are disabled for this editor"
15898            );
15899        }
15900    });
15901
15902    cx.update_editor(|editor, window, cx| {
15903        editor.show_word_completions(&ShowWordCompletions, window, cx);
15904    });
15905    cx.executor().run_until_parked();
15906    cx.update_editor(|editor, _, _| {
15907        if editor.context_menu.borrow_mut().is_some() {
15908            panic!(
15909                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15910            );
15911        }
15912    });
15913}
15914
15915#[gpui::test]
15916async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15917    init_test(cx, |language_settings| {
15918        language_settings.defaults.completions = Some(CompletionSettingsContent {
15919            words: Some(WordsCompletionMode::Disabled),
15920            words_min_length: Some(0),
15921            lsp_insert_mode: Some(LspInsertMode::Insert),
15922            ..Default::default()
15923        });
15924    });
15925
15926    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15927    cx.update_editor(|editor, _, _| {
15928        editor.set_completion_provider(None);
15929    });
15930    cx.set_state(indoc! {"ˇ
15931        wow
15932        wowen
15933        wowser
15934    "});
15935    cx.simulate_keystroke("w");
15936    cx.executor().run_until_parked();
15937    cx.update_editor(|editor, _, _| {
15938        if editor.context_menu.borrow_mut().is_some() {
15939            panic!("expected completion menu to be hidden, as disabled in settings");
15940        }
15941    });
15942}
15943
15944fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15945    let position = || lsp::Position {
15946        line: params.text_document_position.position.line,
15947        character: params.text_document_position.position.character,
15948    };
15949    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15950        range: lsp::Range {
15951            start: position(),
15952            end: position(),
15953        },
15954        new_text: text.to_string(),
15955    }))
15956}
15957
15958#[gpui::test]
15959async fn test_multiline_completion(cx: &mut TestAppContext) {
15960    init_test(cx, |_| {});
15961
15962    let fs = FakeFs::new(cx.executor());
15963    fs.insert_tree(
15964        path!("/a"),
15965        json!({
15966            "main.ts": "a",
15967        }),
15968    )
15969    .await;
15970
15971    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15972    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15973    let typescript_language = Arc::new(Language::new(
15974        LanguageConfig {
15975            name: "TypeScript".into(),
15976            matcher: LanguageMatcher {
15977                path_suffixes: vec!["ts".to_string()],
15978                ..LanguageMatcher::default()
15979            },
15980            line_comments: vec!["// ".into()],
15981            ..LanguageConfig::default()
15982        },
15983        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15984    ));
15985    language_registry.add(typescript_language.clone());
15986    let mut fake_servers = language_registry.register_fake_lsp(
15987        "TypeScript",
15988        FakeLspAdapter {
15989            capabilities: lsp::ServerCapabilities {
15990                completion_provider: Some(lsp::CompletionOptions {
15991                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15992                    ..lsp::CompletionOptions::default()
15993                }),
15994                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15995                ..lsp::ServerCapabilities::default()
15996            },
15997            // Emulate vtsls label generation
15998            label_for_completion: Some(Box::new(|item, _| {
15999                let text = if let Some(description) = item
16000                    .label_details
16001                    .as_ref()
16002                    .and_then(|label_details| label_details.description.as_ref())
16003                {
16004                    format!("{} {}", item.label, description)
16005                } else if let Some(detail) = &item.detail {
16006                    format!("{} {}", item.label, detail)
16007                } else {
16008                    item.label.clone()
16009                };
16010                Some(language::CodeLabel::plain(text, None))
16011            })),
16012            ..FakeLspAdapter::default()
16013        },
16014    );
16015    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16016    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16017    let worktree_id = workspace
16018        .update(cx, |workspace, _window, cx| {
16019            workspace.project().update(cx, |project, cx| {
16020                project.worktrees(cx).next().unwrap().read(cx).id()
16021            })
16022        })
16023        .unwrap();
16024    let _buffer = project
16025        .update(cx, |project, cx| {
16026            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16027        })
16028        .await
16029        .unwrap();
16030    let editor = workspace
16031        .update(cx, |workspace, window, cx| {
16032            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16033        })
16034        .unwrap()
16035        .await
16036        .unwrap()
16037        .downcast::<Editor>()
16038        .unwrap();
16039    let fake_server = fake_servers.next().await.unwrap();
16040    cx.run_until_parked();
16041
16042    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16043    let multiline_label_2 = "a\nb\nc\n";
16044    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16045    let multiline_description = "d\ne\nf\n";
16046    let multiline_detail_2 = "g\nh\ni\n";
16047
16048    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16049        move |params, _| async move {
16050            Ok(Some(lsp::CompletionResponse::Array(vec![
16051                lsp::CompletionItem {
16052                    label: multiline_label.to_string(),
16053                    text_edit: gen_text_edit(&params, "new_text_1"),
16054                    ..lsp::CompletionItem::default()
16055                },
16056                lsp::CompletionItem {
16057                    label: "single line label 1".to_string(),
16058                    detail: Some(multiline_detail.to_string()),
16059                    text_edit: gen_text_edit(&params, "new_text_2"),
16060                    ..lsp::CompletionItem::default()
16061                },
16062                lsp::CompletionItem {
16063                    label: "single line label 2".to_string(),
16064                    label_details: Some(lsp::CompletionItemLabelDetails {
16065                        description: Some(multiline_description.to_string()),
16066                        detail: None,
16067                    }),
16068                    text_edit: gen_text_edit(&params, "new_text_2"),
16069                    ..lsp::CompletionItem::default()
16070                },
16071                lsp::CompletionItem {
16072                    label: multiline_label_2.to_string(),
16073                    detail: Some(multiline_detail_2.to_string()),
16074                    text_edit: gen_text_edit(&params, "new_text_3"),
16075                    ..lsp::CompletionItem::default()
16076                },
16077                lsp::CompletionItem {
16078                    label: "Label with many     spaces and \t but without newlines".to_string(),
16079                    detail: Some(
16080                        "Details with many     spaces and \t but without newlines".to_string(),
16081                    ),
16082                    text_edit: gen_text_edit(&params, "new_text_4"),
16083                    ..lsp::CompletionItem::default()
16084                },
16085            ])))
16086        },
16087    );
16088
16089    editor.update_in(cx, |editor, window, cx| {
16090        cx.focus_self(window);
16091        editor.move_to_end(&MoveToEnd, window, cx);
16092        editor.handle_input(".", window, cx);
16093    });
16094    cx.run_until_parked();
16095    completion_handle.next().await.unwrap();
16096
16097    editor.update(cx, |editor, _| {
16098        assert!(editor.context_menu_visible());
16099        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16100        {
16101            let completion_labels = menu
16102                .completions
16103                .borrow()
16104                .iter()
16105                .map(|c| c.label.text.clone())
16106                .collect::<Vec<_>>();
16107            assert_eq!(
16108                completion_labels,
16109                &[
16110                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16111                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16112                    "single line label 2 d e f ",
16113                    "a b c g h i ",
16114                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16115                ],
16116                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16117            );
16118
16119            for completion in menu
16120                .completions
16121                .borrow()
16122                .iter() {
16123                    assert_eq!(
16124                        completion.label.filter_range,
16125                        0..completion.label.text.len(),
16126                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16127                    );
16128                }
16129        } else {
16130            panic!("expected completion menu to be open");
16131        }
16132    });
16133}
16134
16135#[gpui::test]
16136async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16137    init_test(cx, |_| {});
16138    let mut cx = EditorLspTestContext::new_rust(
16139        lsp::ServerCapabilities {
16140            completion_provider: Some(lsp::CompletionOptions {
16141                trigger_characters: Some(vec![".".to_string()]),
16142                ..Default::default()
16143            }),
16144            ..Default::default()
16145        },
16146        cx,
16147    )
16148    .await;
16149    cx.lsp
16150        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16151            Ok(Some(lsp::CompletionResponse::Array(vec![
16152                lsp::CompletionItem {
16153                    label: "first".into(),
16154                    ..Default::default()
16155                },
16156                lsp::CompletionItem {
16157                    label: "last".into(),
16158                    ..Default::default()
16159                },
16160            ])))
16161        });
16162    cx.set_state("variableˇ");
16163    cx.simulate_keystroke(".");
16164    cx.executor().run_until_parked();
16165
16166    cx.update_editor(|editor, _, _| {
16167        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16168        {
16169            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16170        } else {
16171            panic!("expected completion menu to be open");
16172        }
16173    });
16174
16175    cx.update_editor(|editor, window, cx| {
16176        editor.move_page_down(&MovePageDown::default(), window, cx);
16177        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16178        {
16179            assert!(
16180                menu.selected_item == 1,
16181                "expected PageDown to select the last item from the context menu"
16182            );
16183        } else {
16184            panic!("expected completion menu to stay open after PageDown");
16185        }
16186    });
16187
16188    cx.update_editor(|editor, window, cx| {
16189        editor.move_page_up(&MovePageUp::default(), window, cx);
16190        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16191        {
16192            assert!(
16193                menu.selected_item == 0,
16194                "expected PageUp to select the first item from the context menu"
16195            );
16196        } else {
16197            panic!("expected completion menu to stay open after PageUp");
16198        }
16199    });
16200}
16201
16202#[gpui::test]
16203async fn test_as_is_completions(cx: &mut TestAppContext) {
16204    init_test(cx, |_| {});
16205    let mut cx = EditorLspTestContext::new_rust(
16206        lsp::ServerCapabilities {
16207            completion_provider: Some(lsp::CompletionOptions {
16208                ..Default::default()
16209            }),
16210            ..Default::default()
16211        },
16212        cx,
16213    )
16214    .await;
16215    cx.lsp
16216        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16217            Ok(Some(lsp::CompletionResponse::Array(vec![
16218                lsp::CompletionItem {
16219                    label: "unsafe".into(),
16220                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16221                        range: lsp::Range {
16222                            start: lsp::Position {
16223                                line: 1,
16224                                character: 2,
16225                            },
16226                            end: lsp::Position {
16227                                line: 1,
16228                                character: 3,
16229                            },
16230                        },
16231                        new_text: "unsafe".to_string(),
16232                    })),
16233                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16234                    ..Default::default()
16235                },
16236            ])))
16237        });
16238    cx.set_state("fn a() {}\n");
16239    cx.executor().run_until_parked();
16240    cx.update_editor(|editor, window, cx| {
16241        editor.trigger_completion_on_input("n", true, window, cx)
16242    });
16243    cx.executor().run_until_parked();
16244
16245    cx.update_editor(|editor, window, cx| {
16246        editor.confirm_completion(&Default::default(), window, cx)
16247    });
16248    cx.executor().run_until_parked();
16249    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16250}
16251
16252#[gpui::test]
16253async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16254    init_test(cx, |_| {});
16255    let language =
16256        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16257    let mut cx = EditorLspTestContext::new(
16258        language,
16259        lsp::ServerCapabilities {
16260            completion_provider: Some(lsp::CompletionOptions {
16261                ..lsp::CompletionOptions::default()
16262            }),
16263            ..lsp::ServerCapabilities::default()
16264        },
16265        cx,
16266    )
16267    .await;
16268
16269    cx.set_state(
16270        "#ifndef BAR_H
16271#define BAR_H
16272
16273#include <stdbool.h>
16274
16275int fn_branch(bool do_branch1, bool do_branch2);
16276
16277#endif // BAR_H
16278ˇ",
16279    );
16280    cx.executor().run_until_parked();
16281    cx.update_editor(|editor, window, cx| {
16282        editor.handle_input("#", window, cx);
16283    });
16284    cx.executor().run_until_parked();
16285    cx.update_editor(|editor, window, cx| {
16286        editor.handle_input("i", window, cx);
16287    });
16288    cx.executor().run_until_parked();
16289    cx.update_editor(|editor, window, cx| {
16290        editor.handle_input("n", window, cx);
16291    });
16292    cx.executor().run_until_parked();
16293    cx.assert_editor_state(
16294        "#ifndef BAR_H
16295#define BAR_H
16296
16297#include <stdbool.h>
16298
16299int fn_branch(bool do_branch1, bool do_branch2);
16300
16301#endif // BAR_H
16302#inˇ",
16303    );
16304
16305    cx.lsp
16306        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16307            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16308                is_incomplete: false,
16309                item_defaults: None,
16310                items: vec![lsp::CompletionItem {
16311                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16312                    label_details: Some(lsp::CompletionItemLabelDetails {
16313                        detail: Some("header".to_string()),
16314                        description: None,
16315                    }),
16316                    label: " include".to_string(),
16317                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16318                        range: lsp::Range {
16319                            start: lsp::Position {
16320                                line: 8,
16321                                character: 1,
16322                            },
16323                            end: lsp::Position {
16324                                line: 8,
16325                                character: 1,
16326                            },
16327                        },
16328                        new_text: "include \"$0\"".to_string(),
16329                    })),
16330                    sort_text: Some("40b67681include".to_string()),
16331                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16332                    filter_text: Some("include".to_string()),
16333                    insert_text: Some("include \"$0\"".to_string()),
16334                    ..lsp::CompletionItem::default()
16335                }],
16336            })))
16337        });
16338    cx.update_editor(|editor, window, cx| {
16339        editor.show_completions(&ShowCompletions, window, cx);
16340    });
16341    cx.executor().run_until_parked();
16342    cx.update_editor(|editor, window, cx| {
16343        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16344    });
16345    cx.executor().run_until_parked();
16346    cx.assert_editor_state(
16347        "#ifndef BAR_H
16348#define BAR_H
16349
16350#include <stdbool.h>
16351
16352int fn_branch(bool do_branch1, bool do_branch2);
16353
16354#endif // BAR_H
16355#include \"ˇ\"",
16356    );
16357
16358    cx.lsp
16359        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16360            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16361                is_incomplete: true,
16362                item_defaults: None,
16363                items: vec![lsp::CompletionItem {
16364                    kind: Some(lsp::CompletionItemKind::FILE),
16365                    label: "AGL/".to_string(),
16366                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16367                        range: lsp::Range {
16368                            start: lsp::Position {
16369                                line: 8,
16370                                character: 10,
16371                            },
16372                            end: lsp::Position {
16373                                line: 8,
16374                                character: 11,
16375                            },
16376                        },
16377                        new_text: "AGL/".to_string(),
16378                    })),
16379                    sort_text: Some("40b67681AGL/".to_string()),
16380                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16381                    filter_text: Some("AGL/".to_string()),
16382                    insert_text: Some("AGL/".to_string()),
16383                    ..lsp::CompletionItem::default()
16384                }],
16385            })))
16386        });
16387    cx.update_editor(|editor, window, cx| {
16388        editor.show_completions(&ShowCompletions, window, cx);
16389    });
16390    cx.executor().run_until_parked();
16391    cx.update_editor(|editor, window, cx| {
16392        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16393    });
16394    cx.executor().run_until_parked();
16395    cx.assert_editor_state(
16396        r##"#ifndef BAR_H
16397#define BAR_H
16398
16399#include <stdbool.h>
16400
16401int fn_branch(bool do_branch1, bool do_branch2);
16402
16403#endif // BAR_H
16404#include "AGL/ˇ"##,
16405    );
16406
16407    cx.update_editor(|editor, window, cx| {
16408        editor.handle_input("\"", window, cx);
16409    });
16410    cx.executor().run_until_parked();
16411    cx.assert_editor_state(
16412        r##"#ifndef BAR_H
16413#define BAR_H
16414
16415#include <stdbool.h>
16416
16417int fn_branch(bool do_branch1, bool do_branch2);
16418
16419#endif // BAR_H
16420#include "AGL/"ˇ"##,
16421    );
16422}
16423
16424#[gpui::test]
16425async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16426    init_test(cx, |_| {});
16427
16428    let mut cx = EditorLspTestContext::new_rust(
16429        lsp::ServerCapabilities {
16430            completion_provider: Some(lsp::CompletionOptions {
16431                trigger_characters: Some(vec![".".to_string()]),
16432                resolve_provider: Some(true),
16433                ..Default::default()
16434            }),
16435            ..Default::default()
16436        },
16437        cx,
16438    )
16439    .await;
16440
16441    cx.set_state("fn main() { let a = 2ˇ; }");
16442    cx.simulate_keystroke(".");
16443    let completion_item = lsp::CompletionItem {
16444        label: "Some".into(),
16445        kind: Some(lsp::CompletionItemKind::SNIPPET),
16446        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16447        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16448            kind: lsp::MarkupKind::Markdown,
16449            value: "```rust\nSome(2)\n```".to_string(),
16450        })),
16451        deprecated: Some(false),
16452        sort_text: Some("Some".to_string()),
16453        filter_text: Some("Some".to_string()),
16454        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16455        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16456            range: lsp::Range {
16457                start: lsp::Position {
16458                    line: 0,
16459                    character: 22,
16460                },
16461                end: lsp::Position {
16462                    line: 0,
16463                    character: 22,
16464                },
16465            },
16466            new_text: "Some(2)".to_string(),
16467        })),
16468        additional_text_edits: Some(vec![lsp::TextEdit {
16469            range: lsp::Range {
16470                start: lsp::Position {
16471                    line: 0,
16472                    character: 20,
16473                },
16474                end: lsp::Position {
16475                    line: 0,
16476                    character: 22,
16477                },
16478            },
16479            new_text: "".to_string(),
16480        }]),
16481        ..Default::default()
16482    };
16483
16484    let closure_completion_item = completion_item.clone();
16485    let counter = Arc::new(AtomicUsize::new(0));
16486    let counter_clone = counter.clone();
16487    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16488        let task_completion_item = closure_completion_item.clone();
16489        counter_clone.fetch_add(1, atomic::Ordering::Release);
16490        async move {
16491            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16492                is_incomplete: true,
16493                item_defaults: None,
16494                items: vec![task_completion_item],
16495            })))
16496        }
16497    });
16498
16499    cx.condition(|editor, _| editor.context_menu_visible())
16500        .await;
16501    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16502    assert!(request.next().await.is_some());
16503    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16504
16505    cx.simulate_keystrokes("S o m");
16506    cx.condition(|editor, _| editor.context_menu_visible())
16507        .await;
16508    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16509    assert!(request.next().await.is_some());
16510    assert!(request.next().await.is_some());
16511    assert!(request.next().await.is_some());
16512    request.close();
16513    assert!(request.next().await.is_none());
16514    assert_eq!(
16515        counter.load(atomic::Ordering::Acquire),
16516        4,
16517        "With the completions menu open, only one LSP request should happen per input"
16518    );
16519}
16520
16521#[gpui::test]
16522async fn test_toggle_comment(cx: &mut TestAppContext) {
16523    init_test(cx, |_| {});
16524    let mut cx = EditorTestContext::new(cx).await;
16525    let language = Arc::new(Language::new(
16526        LanguageConfig {
16527            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16528            ..Default::default()
16529        },
16530        Some(tree_sitter_rust::LANGUAGE.into()),
16531    ));
16532    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16533
16534    // If multiple selections intersect a line, the line is only toggled once.
16535    cx.set_state(indoc! {"
16536        fn a() {
16537            «//b();
16538            ˇ»// «c();
16539            //ˇ»  d();
16540        }
16541    "});
16542
16543    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16544
16545    cx.assert_editor_state(indoc! {"
16546        fn a() {
16547            «b();
16548            ˇ»«c();
16549            ˇ» d();
16550        }
16551    "});
16552
16553    // The comment prefix is inserted at the same column for every line in a
16554    // selection.
16555    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16556
16557    cx.assert_editor_state(indoc! {"
16558        fn a() {
16559            // «b();
16560            ˇ»// «c();
16561            ˇ» // d();
16562        }
16563    "});
16564
16565    // If a selection ends at the beginning of a line, that line is not toggled.
16566    cx.set_selections_state(indoc! {"
16567        fn a() {
16568            // b();
16569            «// c();
16570        ˇ»     // d();
16571        }
16572    "});
16573
16574    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16575
16576    cx.assert_editor_state(indoc! {"
16577        fn a() {
16578            // b();
16579            «c();
16580        ˇ»     // d();
16581        }
16582    "});
16583
16584    // If a selection span a single line and is empty, the line is toggled.
16585    cx.set_state(indoc! {"
16586        fn a() {
16587            a();
16588            b();
16589        ˇ
16590        }
16591    "});
16592
16593    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16594
16595    cx.assert_editor_state(indoc! {"
16596        fn a() {
16597            a();
16598            b();
16599        //•ˇ
16600        }
16601    "});
16602
16603    // If a selection span multiple lines, empty lines are not toggled.
16604    cx.set_state(indoc! {"
16605        fn a() {
16606            «a();
16607
16608            c();ˇ»
16609        }
16610    "});
16611
16612    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16613
16614    cx.assert_editor_state(indoc! {"
16615        fn a() {
16616            // «a();
16617
16618            // c();ˇ»
16619        }
16620    "});
16621
16622    // If a selection includes multiple comment prefixes, all lines are uncommented.
16623    cx.set_state(indoc! {"
16624        fn a() {
16625            «// a();
16626            /// b();
16627            //! c();ˇ»
16628        }
16629    "});
16630
16631    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16632
16633    cx.assert_editor_state(indoc! {"
16634        fn a() {
16635            «a();
16636            b();
16637            c();ˇ»
16638        }
16639    "});
16640}
16641
16642#[gpui::test]
16643async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16644    init_test(cx, |_| {});
16645    let mut cx = EditorTestContext::new(cx).await;
16646    let language = Arc::new(Language::new(
16647        LanguageConfig {
16648            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16649            ..Default::default()
16650        },
16651        Some(tree_sitter_rust::LANGUAGE.into()),
16652    ));
16653    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16654
16655    let toggle_comments = &ToggleComments {
16656        advance_downwards: false,
16657        ignore_indent: true,
16658    };
16659
16660    // If multiple selections intersect a line, the line is only toggled once.
16661    cx.set_state(indoc! {"
16662        fn a() {
16663        //    «b();
16664        //    c();
16665        //    ˇ» d();
16666        }
16667    "});
16668
16669    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16670
16671    cx.assert_editor_state(indoc! {"
16672        fn a() {
16673            «b();
16674            c();
16675            ˇ» d();
16676        }
16677    "});
16678
16679    // The comment prefix is inserted at the beginning of each line
16680    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16681
16682    cx.assert_editor_state(indoc! {"
16683        fn a() {
16684        //    «b();
16685        //    c();
16686        //    ˇ» d();
16687        }
16688    "});
16689
16690    // If a selection ends at the beginning of a line, that line is not toggled.
16691    cx.set_selections_state(indoc! {"
16692        fn a() {
16693        //    b();
16694        //    «c();
16695        ˇ»//     d();
16696        }
16697    "});
16698
16699    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16700
16701    cx.assert_editor_state(indoc! {"
16702        fn a() {
16703        //    b();
16704            «c();
16705        ˇ»//     d();
16706        }
16707    "});
16708
16709    // If a selection span a single line and is empty, the line is toggled.
16710    cx.set_state(indoc! {"
16711        fn a() {
16712            a();
16713            b();
16714        ˇ
16715        }
16716    "});
16717
16718    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16719
16720    cx.assert_editor_state(indoc! {"
16721        fn a() {
16722            a();
16723            b();
16724        //ˇ
16725        }
16726    "});
16727
16728    // If a selection span multiple lines, empty lines are not toggled.
16729    cx.set_state(indoc! {"
16730        fn a() {
16731            «a();
16732
16733            c();ˇ»
16734        }
16735    "});
16736
16737    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16738
16739    cx.assert_editor_state(indoc! {"
16740        fn a() {
16741        //    «a();
16742
16743        //    c();ˇ»
16744        }
16745    "});
16746
16747    // If a selection includes multiple comment prefixes, all lines are uncommented.
16748    cx.set_state(indoc! {"
16749        fn a() {
16750        //    «a();
16751        ///    b();
16752        //!    c();ˇ»
16753        }
16754    "});
16755
16756    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16757
16758    cx.assert_editor_state(indoc! {"
16759        fn a() {
16760            «a();
16761            b();
16762            c();ˇ»
16763        }
16764    "});
16765}
16766
16767#[gpui::test]
16768async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16769    init_test(cx, |_| {});
16770
16771    let language = Arc::new(Language::new(
16772        LanguageConfig {
16773            line_comments: vec!["// ".into()],
16774            ..Default::default()
16775        },
16776        Some(tree_sitter_rust::LANGUAGE.into()),
16777    ));
16778
16779    let mut cx = EditorTestContext::new(cx).await;
16780
16781    cx.language_registry().add(language.clone());
16782    cx.update_buffer(|buffer, cx| {
16783        buffer.set_language(Some(language), cx);
16784    });
16785
16786    let toggle_comments = &ToggleComments {
16787        advance_downwards: true,
16788        ignore_indent: false,
16789    };
16790
16791    // Single cursor on one line -> advance
16792    // Cursor moves horizontally 3 characters as well on non-blank line
16793    cx.set_state(indoc!(
16794        "fn a() {
16795             ˇdog();
16796             cat();
16797        }"
16798    ));
16799    cx.update_editor(|editor, window, cx| {
16800        editor.toggle_comments(toggle_comments, window, cx);
16801    });
16802    cx.assert_editor_state(indoc!(
16803        "fn a() {
16804             // dog();
16805             catˇ();
16806        }"
16807    ));
16808
16809    // Single selection on one line -> don't advance
16810    cx.set_state(indoc!(
16811        "fn a() {
16812             «dog()ˇ»;
16813             cat();
16814        }"
16815    ));
16816    cx.update_editor(|editor, window, cx| {
16817        editor.toggle_comments(toggle_comments, window, cx);
16818    });
16819    cx.assert_editor_state(indoc!(
16820        "fn a() {
16821             // «dog()ˇ»;
16822             cat();
16823        }"
16824    ));
16825
16826    // Multiple cursors on one line -> advance
16827    cx.set_state(indoc!(
16828        "fn a() {
16829             ˇdˇog();
16830             cat();
16831        }"
16832    ));
16833    cx.update_editor(|editor, window, cx| {
16834        editor.toggle_comments(toggle_comments, window, cx);
16835    });
16836    cx.assert_editor_state(indoc!(
16837        "fn a() {
16838             // dog();
16839             catˇ(ˇ);
16840        }"
16841    ));
16842
16843    // Multiple cursors on one line, with selection -> don't advance
16844    cx.set_state(indoc!(
16845        "fn a() {
16846             ˇdˇog«()ˇ»;
16847             cat();
16848        }"
16849    ));
16850    cx.update_editor(|editor, window, cx| {
16851        editor.toggle_comments(toggle_comments, window, cx);
16852    });
16853    cx.assert_editor_state(indoc!(
16854        "fn a() {
16855             // ˇdˇog«()ˇ»;
16856             cat();
16857        }"
16858    ));
16859
16860    // Single cursor on one line -> advance
16861    // Cursor moves to column 0 on blank line
16862    cx.set_state(indoc!(
16863        "fn a() {
16864             ˇdog();
16865
16866             cat();
16867        }"
16868    ));
16869    cx.update_editor(|editor, window, cx| {
16870        editor.toggle_comments(toggle_comments, window, cx);
16871    });
16872    cx.assert_editor_state(indoc!(
16873        "fn a() {
16874             // dog();
16875        ˇ
16876             cat();
16877        }"
16878    ));
16879
16880    // Single cursor on one line -> advance
16881    // Cursor starts and ends at column 0
16882    cx.set_state(indoc!(
16883        "fn a() {
16884         ˇ    dog();
16885             cat();
16886        }"
16887    ));
16888    cx.update_editor(|editor, window, cx| {
16889        editor.toggle_comments(toggle_comments, window, cx);
16890    });
16891    cx.assert_editor_state(indoc!(
16892        "fn a() {
16893             // dog();
16894         ˇ    cat();
16895        }"
16896    ));
16897}
16898
16899#[gpui::test]
16900async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16901    init_test(cx, |_| {});
16902
16903    let mut cx = EditorTestContext::new(cx).await;
16904
16905    let html_language = Arc::new(
16906        Language::new(
16907            LanguageConfig {
16908                name: "HTML".into(),
16909                block_comment: Some(BlockCommentConfig {
16910                    start: "<!-- ".into(),
16911                    prefix: "".into(),
16912                    end: " -->".into(),
16913                    tab_size: 0,
16914                }),
16915                ..Default::default()
16916            },
16917            Some(tree_sitter_html::LANGUAGE.into()),
16918        )
16919        .with_injection_query(
16920            r#"
16921            (script_element
16922                (raw_text) @injection.content
16923                (#set! injection.language "javascript"))
16924            "#,
16925        )
16926        .unwrap(),
16927    );
16928
16929    let javascript_language = Arc::new(Language::new(
16930        LanguageConfig {
16931            name: "JavaScript".into(),
16932            line_comments: vec!["// ".into()],
16933            ..Default::default()
16934        },
16935        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16936    ));
16937
16938    cx.language_registry().add(html_language.clone());
16939    cx.language_registry().add(javascript_language);
16940    cx.update_buffer(|buffer, cx| {
16941        buffer.set_language(Some(html_language), cx);
16942    });
16943
16944    // Toggle comments for empty selections
16945    cx.set_state(
16946        &r#"
16947            <p>A</p>ˇ
16948            <p>B</p>ˇ
16949            <p>C</p>ˇ
16950        "#
16951        .unindent(),
16952    );
16953    cx.update_editor(|editor, window, cx| {
16954        editor.toggle_comments(&ToggleComments::default(), window, cx)
16955    });
16956    cx.assert_editor_state(
16957        &r#"
16958            <!-- <p>A</p>ˇ -->
16959            <!-- <p>B</p>ˇ -->
16960            <!-- <p>C</p>ˇ -->
16961        "#
16962        .unindent(),
16963    );
16964    cx.update_editor(|editor, window, cx| {
16965        editor.toggle_comments(&ToggleComments::default(), window, cx)
16966    });
16967    cx.assert_editor_state(
16968        &r#"
16969            <p>A</p>ˇ
16970            <p>B</p>ˇ
16971            <p>C</p>ˇ
16972        "#
16973        .unindent(),
16974    );
16975
16976    // Toggle comments for mixture of empty and non-empty selections, where
16977    // multiple selections occupy a given line.
16978    cx.set_state(
16979        &r#"
16980            <p>A«</p>
16981            <p>ˇ»B</p>ˇ
16982            <p>C«</p>
16983            <p>ˇ»D</p>ˇ
16984        "#
16985        .unindent(),
16986    );
16987
16988    cx.update_editor(|editor, window, cx| {
16989        editor.toggle_comments(&ToggleComments::default(), window, cx)
16990    });
16991    cx.assert_editor_state(
16992        &r#"
16993            <!-- <p>A«</p>
16994            <p>ˇ»B</p>ˇ -->
16995            <!-- <p>C«</p>
16996            <p>ˇ»D</p>ˇ -->
16997        "#
16998        .unindent(),
16999    );
17000    cx.update_editor(|editor, window, cx| {
17001        editor.toggle_comments(&ToggleComments::default(), window, cx)
17002    });
17003    cx.assert_editor_state(
17004        &r#"
17005            <p>A«</p>
17006            <p>ˇ»B</p>ˇ
17007            <p>C«</p>
17008            <p>ˇ»D</p>ˇ
17009        "#
17010        .unindent(),
17011    );
17012
17013    // Toggle comments when different languages are active for different
17014    // selections.
17015    cx.set_state(
17016        &r#"
17017            ˇ<script>
17018                ˇvar x = new Y();
17019            ˇ</script>
17020        "#
17021        .unindent(),
17022    );
17023    cx.executor().run_until_parked();
17024    cx.update_editor(|editor, window, cx| {
17025        editor.toggle_comments(&ToggleComments::default(), window, cx)
17026    });
17027    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17028    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17029    cx.assert_editor_state(
17030        &r#"
17031            <!-- ˇ<script> -->
17032                // ˇvar x = new Y();
17033            <!-- ˇ</script> -->
17034        "#
17035        .unindent(),
17036    );
17037}
17038
17039#[gpui::test]
17040fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17041    init_test(cx, |_| {});
17042
17043    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17044    let multibuffer = cx.new(|cx| {
17045        let mut multibuffer = MultiBuffer::new(ReadWrite);
17046        multibuffer.push_excerpts(
17047            buffer.clone(),
17048            [
17049                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17050                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17051            ],
17052            cx,
17053        );
17054        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17055        multibuffer
17056    });
17057
17058    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17059    editor.update_in(cx, |editor, window, cx| {
17060        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17062            s.select_ranges([
17063                Point::new(0, 0)..Point::new(0, 0),
17064                Point::new(1, 0)..Point::new(1, 0),
17065            ])
17066        });
17067
17068        editor.handle_input("X", window, cx);
17069        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17070        assert_eq!(
17071            editor.selections.ranges(&editor.display_snapshot(cx)),
17072            [
17073                Point::new(0, 1)..Point::new(0, 1),
17074                Point::new(1, 1)..Point::new(1, 1),
17075            ]
17076        );
17077
17078        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17080            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17081        });
17082        editor.backspace(&Default::default(), window, cx);
17083        assert_eq!(editor.text(cx), "Xa\nbbb");
17084        assert_eq!(
17085            editor.selections.ranges(&editor.display_snapshot(cx)),
17086            [Point::new(1, 0)..Point::new(1, 0)]
17087        );
17088
17089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17090            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17091        });
17092        editor.backspace(&Default::default(), window, cx);
17093        assert_eq!(editor.text(cx), "X\nbb");
17094        assert_eq!(
17095            editor.selections.ranges(&editor.display_snapshot(cx)),
17096            [Point::new(0, 1)..Point::new(0, 1)]
17097        );
17098    });
17099}
17100
17101#[gpui::test]
17102fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17103    init_test(cx, |_| {});
17104
17105    let markers = vec![('[', ']').into(), ('(', ')').into()];
17106    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17107        indoc! {"
17108            [aaaa
17109            (bbbb]
17110            cccc)",
17111        },
17112        markers.clone(),
17113    );
17114    let excerpt_ranges = markers.into_iter().map(|marker| {
17115        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17116        ExcerptRange::new(context)
17117    });
17118    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17119    let multibuffer = cx.new(|cx| {
17120        let mut multibuffer = MultiBuffer::new(ReadWrite);
17121        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17122        multibuffer
17123    });
17124
17125    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17126    editor.update_in(cx, |editor, window, cx| {
17127        let (expected_text, selection_ranges) = marked_text_ranges(
17128            indoc! {"
17129                aaaa
17130                bˇbbb
17131                bˇbbˇb
17132                cccc"
17133            },
17134            true,
17135        );
17136        assert_eq!(editor.text(cx), expected_text);
17137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17138            s.select_ranges(
17139                selection_ranges
17140                    .iter()
17141                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17142            )
17143        });
17144
17145        editor.handle_input("X", window, cx);
17146
17147        let (expected_text, expected_selections) = marked_text_ranges(
17148            indoc! {"
17149                aaaa
17150                bXˇbbXb
17151                bXˇbbXˇb
17152                cccc"
17153            },
17154            false,
17155        );
17156        assert_eq!(editor.text(cx), expected_text);
17157        assert_eq!(
17158            editor.selections.ranges(&editor.display_snapshot(cx)),
17159            expected_selections
17160                .iter()
17161                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17162                .collect::<Vec<_>>()
17163        );
17164
17165        editor.newline(&Newline, window, cx);
17166        let (expected_text, expected_selections) = marked_text_ranges(
17167            indoc! {"
17168                aaaa
17169                bX
17170                ˇbbX
17171                b
17172                bX
17173                ˇbbX
17174                ˇb
17175                cccc"
17176            },
17177            false,
17178        );
17179        assert_eq!(editor.text(cx), expected_text);
17180        assert_eq!(
17181            editor.selections.ranges(&editor.display_snapshot(cx)),
17182            expected_selections
17183                .iter()
17184                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17185                .collect::<Vec<_>>()
17186        );
17187    });
17188}
17189
17190#[gpui::test]
17191fn test_refresh_selections(cx: &mut TestAppContext) {
17192    init_test(cx, |_| {});
17193
17194    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17195    let mut excerpt1_id = None;
17196    let multibuffer = cx.new(|cx| {
17197        let mut multibuffer = MultiBuffer::new(ReadWrite);
17198        excerpt1_id = multibuffer
17199            .push_excerpts(
17200                buffer.clone(),
17201                [
17202                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17203                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17204                ],
17205                cx,
17206            )
17207            .into_iter()
17208            .next();
17209        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17210        multibuffer
17211    });
17212
17213    let editor = cx.add_window(|window, cx| {
17214        let mut editor = build_editor(multibuffer.clone(), window, cx);
17215        let snapshot = editor.snapshot(window, cx);
17216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17217            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17218        });
17219        editor.begin_selection(
17220            Point::new(2, 1).to_display_point(&snapshot),
17221            true,
17222            1,
17223            window,
17224            cx,
17225        );
17226        assert_eq!(
17227            editor.selections.ranges(&editor.display_snapshot(cx)),
17228            [
17229                Point::new(1, 3)..Point::new(1, 3),
17230                Point::new(2, 1)..Point::new(2, 1),
17231            ]
17232        );
17233        editor
17234    });
17235
17236    // Refreshing selections is a no-op when excerpts haven't changed.
17237    _ = editor.update(cx, |editor, window, cx| {
17238        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17239        assert_eq!(
17240            editor.selections.ranges(&editor.display_snapshot(cx)),
17241            [
17242                Point::new(1, 3)..Point::new(1, 3),
17243                Point::new(2, 1)..Point::new(2, 1),
17244            ]
17245        );
17246    });
17247
17248    multibuffer.update(cx, |multibuffer, cx| {
17249        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17250    });
17251    _ = editor.update(cx, |editor, window, cx| {
17252        // Removing an excerpt causes the first selection to become degenerate.
17253        assert_eq!(
17254            editor.selections.ranges(&editor.display_snapshot(cx)),
17255            [
17256                Point::new(0, 0)..Point::new(0, 0),
17257                Point::new(0, 1)..Point::new(0, 1)
17258            ]
17259        );
17260
17261        // Refreshing selections will relocate the first selection to the original buffer
17262        // location.
17263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17264        assert_eq!(
17265            editor.selections.ranges(&editor.display_snapshot(cx)),
17266            [
17267                Point::new(0, 1)..Point::new(0, 1),
17268                Point::new(0, 3)..Point::new(0, 3)
17269            ]
17270        );
17271        assert!(editor.selections.pending_anchor().is_some());
17272    });
17273}
17274
17275#[gpui::test]
17276fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17277    init_test(cx, |_| {});
17278
17279    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17280    let mut excerpt1_id = None;
17281    let multibuffer = cx.new(|cx| {
17282        let mut multibuffer = MultiBuffer::new(ReadWrite);
17283        excerpt1_id = multibuffer
17284            .push_excerpts(
17285                buffer.clone(),
17286                [
17287                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17288                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17289                ],
17290                cx,
17291            )
17292            .into_iter()
17293            .next();
17294        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17295        multibuffer
17296    });
17297
17298    let editor = cx.add_window(|window, cx| {
17299        let mut editor = build_editor(multibuffer.clone(), window, cx);
17300        let snapshot = editor.snapshot(window, cx);
17301        editor.begin_selection(
17302            Point::new(1, 3).to_display_point(&snapshot),
17303            false,
17304            1,
17305            window,
17306            cx,
17307        );
17308        assert_eq!(
17309            editor.selections.ranges(&editor.display_snapshot(cx)),
17310            [Point::new(1, 3)..Point::new(1, 3)]
17311        );
17312        editor
17313    });
17314
17315    multibuffer.update(cx, |multibuffer, cx| {
17316        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17317    });
17318    _ = editor.update(cx, |editor, window, cx| {
17319        assert_eq!(
17320            editor.selections.ranges(&editor.display_snapshot(cx)),
17321            [Point::new(0, 0)..Point::new(0, 0)]
17322        );
17323
17324        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17326        assert_eq!(
17327            editor.selections.ranges(&editor.display_snapshot(cx)),
17328            [Point::new(0, 3)..Point::new(0, 3)]
17329        );
17330        assert!(editor.selections.pending_anchor().is_some());
17331    });
17332}
17333
17334#[gpui::test]
17335async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17336    init_test(cx, |_| {});
17337
17338    let language = Arc::new(
17339        Language::new(
17340            LanguageConfig {
17341                brackets: BracketPairConfig {
17342                    pairs: vec![
17343                        BracketPair {
17344                            start: "{".to_string(),
17345                            end: "}".to_string(),
17346                            close: true,
17347                            surround: true,
17348                            newline: true,
17349                        },
17350                        BracketPair {
17351                            start: "/* ".to_string(),
17352                            end: " */".to_string(),
17353                            close: true,
17354                            surround: true,
17355                            newline: true,
17356                        },
17357                    ],
17358                    ..Default::default()
17359                },
17360                ..Default::default()
17361            },
17362            Some(tree_sitter_rust::LANGUAGE.into()),
17363        )
17364        .with_indents_query("")
17365        .unwrap(),
17366    );
17367
17368    let text = concat!(
17369        "{   }\n",     //
17370        "  x\n",       //
17371        "  /*   */\n", //
17372        "x\n",         //
17373        "{{} }\n",     //
17374    );
17375
17376    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17377    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17378    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17379    editor
17380        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17381        .await;
17382
17383    editor.update_in(cx, |editor, window, cx| {
17384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17385            s.select_display_ranges([
17386                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17387                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17388                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17389            ])
17390        });
17391        editor.newline(&Newline, window, cx);
17392
17393        assert_eq!(
17394            editor.buffer().read(cx).read(cx).text(),
17395            concat!(
17396                "{ \n",    // Suppress rustfmt
17397                "\n",      //
17398                "}\n",     //
17399                "  x\n",   //
17400                "  /* \n", //
17401                "  \n",    //
17402                "  */\n",  //
17403                "x\n",     //
17404                "{{} \n",  //
17405                "}\n",     //
17406            )
17407        );
17408    });
17409}
17410
17411#[gpui::test]
17412fn test_highlighted_ranges(cx: &mut TestAppContext) {
17413    init_test(cx, |_| {});
17414
17415    let editor = cx.add_window(|window, cx| {
17416        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17417        build_editor(buffer, window, cx)
17418    });
17419
17420    _ = editor.update(cx, |editor, window, cx| {
17421        struct Type1;
17422        struct Type2;
17423
17424        let buffer = editor.buffer.read(cx).snapshot(cx);
17425
17426        let anchor_range =
17427            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17428
17429        editor.highlight_background::<Type1>(
17430            &[
17431                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17432                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17433                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17434                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17435            ],
17436            |_, _| Hsla::red(),
17437            cx,
17438        );
17439        editor.highlight_background::<Type2>(
17440            &[
17441                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17442                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17443                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17444                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17445            ],
17446            |_, _| Hsla::green(),
17447            cx,
17448        );
17449
17450        let snapshot = editor.snapshot(window, cx);
17451        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17452            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17453            &snapshot,
17454            cx.theme(),
17455        );
17456        assert_eq!(
17457            highlighted_ranges,
17458            &[
17459                (
17460                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17461                    Hsla::green(),
17462                ),
17463                (
17464                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17465                    Hsla::red(),
17466                ),
17467                (
17468                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17469                    Hsla::green(),
17470                ),
17471                (
17472                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17473                    Hsla::red(),
17474                ),
17475            ]
17476        );
17477        assert_eq!(
17478            editor.sorted_background_highlights_in_range(
17479                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17480                &snapshot,
17481                cx.theme(),
17482            ),
17483            &[(
17484                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17485                Hsla::red(),
17486            )]
17487        );
17488    });
17489}
17490
17491#[gpui::test]
17492async fn test_following(cx: &mut TestAppContext) {
17493    init_test(cx, |_| {});
17494
17495    let fs = FakeFs::new(cx.executor());
17496    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17497
17498    let buffer = project.update(cx, |project, cx| {
17499        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17500        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17501    });
17502    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17503    let follower = cx.update(|cx| {
17504        cx.open_window(
17505            WindowOptions {
17506                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17507                    gpui::Point::new(px(0.), px(0.)),
17508                    gpui::Point::new(px(10.), px(80.)),
17509                ))),
17510                ..Default::default()
17511            },
17512            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17513        )
17514        .unwrap()
17515    });
17516
17517    let is_still_following = Rc::new(RefCell::new(true));
17518    let follower_edit_event_count = Rc::new(RefCell::new(0));
17519    let pending_update = Rc::new(RefCell::new(None));
17520    let leader_entity = leader.root(cx).unwrap();
17521    let follower_entity = follower.root(cx).unwrap();
17522    _ = follower.update(cx, {
17523        let update = pending_update.clone();
17524        let is_still_following = is_still_following.clone();
17525        let follower_edit_event_count = follower_edit_event_count.clone();
17526        |_, window, cx| {
17527            cx.subscribe_in(
17528                &leader_entity,
17529                window,
17530                move |_, leader, event, window, cx| {
17531                    leader.read(cx).add_event_to_update_proto(
17532                        event,
17533                        &mut update.borrow_mut(),
17534                        window,
17535                        cx,
17536                    );
17537                },
17538            )
17539            .detach();
17540
17541            cx.subscribe_in(
17542                &follower_entity,
17543                window,
17544                move |_, _, event: &EditorEvent, _window, _cx| {
17545                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17546                        *is_still_following.borrow_mut() = false;
17547                    }
17548
17549                    if let EditorEvent::BufferEdited = event {
17550                        *follower_edit_event_count.borrow_mut() += 1;
17551                    }
17552                },
17553            )
17554            .detach();
17555        }
17556    });
17557
17558    // Update the selections only
17559    _ = leader.update(cx, |leader, window, cx| {
17560        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17561            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17562        });
17563    });
17564    follower
17565        .update(cx, |follower, window, cx| {
17566            follower.apply_update_proto(
17567                &project,
17568                pending_update.borrow_mut().take().unwrap(),
17569                window,
17570                cx,
17571            )
17572        })
17573        .unwrap()
17574        .await
17575        .unwrap();
17576    _ = follower.update(cx, |follower, _, cx| {
17577        assert_eq!(
17578            follower.selections.ranges(&follower.display_snapshot(cx)),
17579            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17580        );
17581    });
17582    assert!(*is_still_following.borrow());
17583    assert_eq!(*follower_edit_event_count.borrow(), 0);
17584
17585    // Update the scroll position only
17586    _ = leader.update(cx, |leader, window, cx| {
17587        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17588    });
17589    follower
17590        .update(cx, |follower, window, cx| {
17591            follower.apply_update_proto(
17592                &project,
17593                pending_update.borrow_mut().take().unwrap(),
17594                window,
17595                cx,
17596            )
17597        })
17598        .unwrap()
17599        .await
17600        .unwrap();
17601    assert_eq!(
17602        follower
17603            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17604            .unwrap(),
17605        gpui::Point::new(1.5, 3.5)
17606    );
17607    assert!(*is_still_following.borrow());
17608    assert_eq!(*follower_edit_event_count.borrow(), 0);
17609
17610    // Update the selections and scroll position. The follower's scroll position is updated
17611    // via autoscroll, not via the leader's exact scroll position.
17612    _ = leader.update(cx, |leader, window, cx| {
17613        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17614            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17615        });
17616        leader.request_autoscroll(Autoscroll::newest(), cx);
17617        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17618    });
17619    follower
17620        .update(cx, |follower, window, cx| {
17621            follower.apply_update_proto(
17622                &project,
17623                pending_update.borrow_mut().take().unwrap(),
17624                window,
17625                cx,
17626            )
17627        })
17628        .unwrap()
17629        .await
17630        .unwrap();
17631    _ = follower.update(cx, |follower, _, cx| {
17632        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17633        assert_eq!(
17634            follower.selections.ranges(&follower.display_snapshot(cx)),
17635            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17636        );
17637    });
17638    assert!(*is_still_following.borrow());
17639
17640    // Creating a pending selection that precedes another selection
17641    _ = leader.update(cx, |leader, window, cx| {
17642        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17643            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17644        });
17645        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17646    });
17647    follower
17648        .update(cx, |follower, window, cx| {
17649            follower.apply_update_proto(
17650                &project,
17651                pending_update.borrow_mut().take().unwrap(),
17652                window,
17653                cx,
17654            )
17655        })
17656        .unwrap()
17657        .await
17658        .unwrap();
17659    _ = follower.update(cx, |follower, _, cx| {
17660        assert_eq!(
17661            follower.selections.ranges(&follower.display_snapshot(cx)),
17662            vec![
17663                MultiBufferOffset(0)..MultiBufferOffset(0),
17664                MultiBufferOffset(1)..MultiBufferOffset(1)
17665            ]
17666        );
17667    });
17668    assert!(*is_still_following.borrow());
17669
17670    // Extend the pending selection so that it surrounds another selection
17671    _ = leader.update(cx, |leader, window, cx| {
17672        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17673    });
17674    follower
17675        .update(cx, |follower, window, cx| {
17676            follower.apply_update_proto(
17677                &project,
17678                pending_update.borrow_mut().take().unwrap(),
17679                window,
17680                cx,
17681            )
17682        })
17683        .unwrap()
17684        .await
17685        .unwrap();
17686    _ = follower.update(cx, |follower, _, cx| {
17687        assert_eq!(
17688            follower.selections.ranges(&follower.display_snapshot(cx)),
17689            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17690        );
17691    });
17692
17693    // Scrolling locally breaks the follow
17694    _ = follower.update(cx, |follower, window, cx| {
17695        let top_anchor = follower
17696            .buffer()
17697            .read(cx)
17698            .read(cx)
17699            .anchor_after(MultiBufferOffset(0));
17700        follower.set_scroll_anchor(
17701            ScrollAnchor {
17702                anchor: top_anchor,
17703                offset: gpui::Point::new(0.0, 0.5),
17704            },
17705            window,
17706            cx,
17707        );
17708    });
17709    assert!(!(*is_still_following.borrow()));
17710}
17711
17712#[gpui::test]
17713async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17714    init_test(cx, |_| {});
17715
17716    let fs = FakeFs::new(cx.executor());
17717    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17718    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17719    let pane = workspace
17720        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17721        .unwrap();
17722
17723    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17724
17725    let leader = pane.update_in(cx, |_, window, cx| {
17726        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17727        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17728    });
17729
17730    // Start following the editor when it has no excerpts.
17731    let mut state_message =
17732        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17733    let workspace_entity = workspace.root(cx).unwrap();
17734    let follower_1 = cx
17735        .update_window(*workspace.deref(), |_, window, cx| {
17736            Editor::from_state_proto(
17737                workspace_entity,
17738                ViewId {
17739                    creator: CollaboratorId::PeerId(PeerId::default()),
17740                    id: 0,
17741                },
17742                &mut state_message,
17743                window,
17744                cx,
17745            )
17746        })
17747        .unwrap()
17748        .unwrap()
17749        .await
17750        .unwrap();
17751
17752    let update_message = Rc::new(RefCell::new(None));
17753    follower_1.update_in(cx, {
17754        let update = update_message.clone();
17755        |_, window, cx| {
17756            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17757                leader.read(cx).add_event_to_update_proto(
17758                    event,
17759                    &mut update.borrow_mut(),
17760                    window,
17761                    cx,
17762                );
17763            })
17764            .detach();
17765        }
17766    });
17767
17768    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17769        (
17770            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17771            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17772        )
17773    });
17774
17775    // Insert some excerpts.
17776    leader.update(cx, |leader, cx| {
17777        leader.buffer.update(cx, |multibuffer, cx| {
17778            multibuffer.set_excerpts_for_path(
17779                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17780                buffer_1.clone(),
17781                vec![
17782                    Point::row_range(0..3),
17783                    Point::row_range(1..6),
17784                    Point::row_range(12..15),
17785                ],
17786                0,
17787                cx,
17788            );
17789            multibuffer.set_excerpts_for_path(
17790                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17791                buffer_2.clone(),
17792                vec![Point::row_range(0..6), Point::row_range(8..12)],
17793                0,
17794                cx,
17795            );
17796        });
17797    });
17798
17799    // Apply the update of adding the excerpts.
17800    follower_1
17801        .update_in(cx, |follower, window, cx| {
17802            follower.apply_update_proto(
17803                &project,
17804                update_message.borrow().clone().unwrap(),
17805                window,
17806                cx,
17807            )
17808        })
17809        .await
17810        .unwrap();
17811    assert_eq!(
17812        follower_1.update(cx, |editor, cx| editor.text(cx)),
17813        leader.update(cx, |editor, cx| editor.text(cx))
17814    );
17815    update_message.borrow_mut().take();
17816
17817    // Start following separately after it already has excerpts.
17818    let mut state_message =
17819        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17820    let workspace_entity = workspace.root(cx).unwrap();
17821    let follower_2 = cx
17822        .update_window(*workspace.deref(), |_, window, cx| {
17823            Editor::from_state_proto(
17824                workspace_entity,
17825                ViewId {
17826                    creator: CollaboratorId::PeerId(PeerId::default()),
17827                    id: 0,
17828                },
17829                &mut state_message,
17830                window,
17831                cx,
17832            )
17833        })
17834        .unwrap()
17835        .unwrap()
17836        .await
17837        .unwrap();
17838    assert_eq!(
17839        follower_2.update(cx, |editor, cx| editor.text(cx)),
17840        leader.update(cx, |editor, cx| editor.text(cx))
17841    );
17842
17843    // Remove some excerpts.
17844    leader.update(cx, |leader, cx| {
17845        leader.buffer.update(cx, |multibuffer, cx| {
17846            let excerpt_ids = multibuffer.excerpt_ids();
17847            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17848            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17849        });
17850    });
17851
17852    // Apply the update of removing the excerpts.
17853    follower_1
17854        .update_in(cx, |follower, window, cx| {
17855            follower.apply_update_proto(
17856                &project,
17857                update_message.borrow().clone().unwrap(),
17858                window,
17859                cx,
17860            )
17861        })
17862        .await
17863        .unwrap();
17864    follower_2
17865        .update_in(cx, |follower, window, cx| {
17866            follower.apply_update_proto(
17867                &project,
17868                update_message.borrow().clone().unwrap(),
17869                window,
17870                cx,
17871            )
17872        })
17873        .await
17874        .unwrap();
17875    update_message.borrow_mut().take();
17876    assert_eq!(
17877        follower_1.update(cx, |editor, cx| editor.text(cx)),
17878        leader.update(cx, |editor, cx| editor.text(cx))
17879    );
17880}
17881
17882#[gpui::test]
17883async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17884    init_test(cx, |_| {});
17885
17886    let mut cx = EditorTestContext::new(cx).await;
17887    let lsp_store =
17888        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17889
17890    cx.set_state(indoc! {"
17891        ˇfn func(abc def: i32) -> u32 {
17892        }
17893    "});
17894
17895    cx.update(|_, cx| {
17896        lsp_store.update(cx, |lsp_store, cx| {
17897            lsp_store
17898                .update_diagnostics(
17899                    LanguageServerId(0),
17900                    lsp::PublishDiagnosticsParams {
17901                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17902                        version: None,
17903                        diagnostics: vec![
17904                            lsp::Diagnostic {
17905                                range: lsp::Range::new(
17906                                    lsp::Position::new(0, 11),
17907                                    lsp::Position::new(0, 12),
17908                                ),
17909                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17910                                ..Default::default()
17911                            },
17912                            lsp::Diagnostic {
17913                                range: lsp::Range::new(
17914                                    lsp::Position::new(0, 12),
17915                                    lsp::Position::new(0, 15),
17916                                ),
17917                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17918                                ..Default::default()
17919                            },
17920                            lsp::Diagnostic {
17921                                range: lsp::Range::new(
17922                                    lsp::Position::new(0, 25),
17923                                    lsp::Position::new(0, 28),
17924                                ),
17925                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17926                                ..Default::default()
17927                            },
17928                        ],
17929                    },
17930                    None,
17931                    DiagnosticSourceKind::Pushed,
17932                    &[],
17933                    cx,
17934                )
17935                .unwrap()
17936        });
17937    });
17938
17939    executor.run_until_parked();
17940
17941    cx.update_editor(|editor, window, cx| {
17942        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17943    });
17944
17945    cx.assert_editor_state(indoc! {"
17946        fn func(abc def: i32) -> ˇu32 {
17947        }
17948    "});
17949
17950    cx.update_editor(|editor, window, cx| {
17951        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17952    });
17953
17954    cx.assert_editor_state(indoc! {"
17955        fn func(abc ˇdef: i32) -> u32 {
17956        }
17957    "});
17958
17959    cx.update_editor(|editor, window, cx| {
17960        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17961    });
17962
17963    cx.assert_editor_state(indoc! {"
17964        fn func(abcˇ def: i32) -> u32 {
17965        }
17966    "});
17967
17968    cx.update_editor(|editor, window, cx| {
17969        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17970    });
17971
17972    cx.assert_editor_state(indoc! {"
17973        fn func(abc def: i32) -> ˇu32 {
17974        }
17975    "});
17976}
17977
17978#[gpui::test]
17979async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17980    init_test(cx, |_| {});
17981
17982    let mut cx = EditorTestContext::new(cx).await;
17983
17984    let diff_base = r#"
17985        use some::mod;
17986
17987        const A: u32 = 42;
17988
17989        fn main() {
17990            println!("hello");
17991
17992            println!("world");
17993        }
17994        "#
17995    .unindent();
17996
17997    // Edits are modified, removed, modified, added
17998    cx.set_state(
17999        &r#"
18000        use some::modified;
18001
18002        ˇ
18003        fn main() {
18004            println!("hello there");
18005
18006            println!("around the");
18007            println!("world");
18008        }
18009        "#
18010        .unindent(),
18011    );
18012
18013    cx.set_head_text(&diff_base);
18014    executor.run_until_parked();
18015
18016    cx.update_editor(|editor, window, cx| {
18017        //Wrap around the bottom of the buffer
18018        for _ in 0..3 {
18019            editor.go_to_next_hunk(&GoToHunk, window, cx);
18020        }
18021    });
18022
18023    cx.assert_editor_state(
18024        &r#"
18025        ˇuse some::modified;
18026
18027
18028        fn main() {
18029            println!("hello there");
18030
18031            println!("around the");
18032            println!("world");
18033        }
18034        "#
18035        .unindent(),
18036    );
18037
18038    cx.update_editor(|editor, window, cx| {
18039        //Wrap around the top of the buffer
18040        for _ in 0..2 {
18041            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18042        }
18043    });
18044
18045    cx.assert_editor_state(
18046        &r#"
18047        use some::modified;
18048
18049
18050        fn main() {
18051        ˇ    println!("hello there");
18052
18053            println!("around the");
18054            println!("world");
18055        }
18056        "#
18057        .unindent(),
18058    );
18059
18060    cx.update_editor(|editor, window, cx| {
18061        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18062    });
18063
18064    cx.assert_editor_state(
18065        &r#"
18066        use some::modified;
18067
18068        ˇ
18069        fn main() {
18070            println!("hello there");
18071
18072            println!("around the");
18073            println!("world");
18074        }
18075        "#
18076        .unindent(),
18077    );
18078
18079    cx.update_editor(|editor, window, cx| {
18080        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18081    });
18082
18083    cx.assert_editor_state(
18084        &r#"
18085        ˇuse some::modified;
18086
18087
18088        fn main() {
18089            println!("hello there");
18090
18091            println!("around the");
18092            println!("world");
18093        }
18094        "#
18095        .unindent(),
18096    );
18097
18098    cx.update_editor(|editor, window, cx| {
18099        for _ in 0..2 {
18100            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18101        }
18102    });
18103
18104    cx.assert_editor_state(
18105        &r#"
18106        use some::modified;
18107
18108
18109        fn main() {
18110        ˇ    println!("hello there");
18111
18112            println!("around the");
18113            println!("world");
18114        }
18115        "#
18116        .unindent(),
18117    );
18118
18119    cx.update_editor(|editor, window, cx| {
18120        editor.fold(&Fold, window, cx);
18121    });
18122
18123    cx.update_editor(|editor, window, cx| {
18124        editor.go_to_next_hunk(&GoToHunk, window, cx);
18125    });
18126
18127    cx.assert_editor_state(
18128        &r#"
18129        ˇuse some::modified;
18130
18131
18132        fn main() {
18133            println!("hello there");
18134
18135            println!("around the");
18136            println!("world");
18137        }
18138        "#
18139        .unindent(),
18140    );
18141}
18142
18143#[test]
18144fn test_split_words() {
18145    fn split(text: &str) -> Vec<&str> {
18146        split_words(text).collect()
18147    }
18148
18149    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18150    assert_eq!(split("hello_world"), &["hello_", "world"]);
18151    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18152    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18153    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18154    assert_eq!(split("helloworld"), &["helloworld"]);
18155
18156    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18157}
18158
18159#[test]
18160fn test_split_words_for_snippet_prefix() {
18161    fn split(text: &str) -> Vec<&str> {
18162        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18163    }
18164
18165    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18166    assert_eq!(split("hello_world"), &["hello_world"]);
18167    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18168    assert_eq!(split("Hello_World"), &["Hello_World"]);
18169    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18170    assert_eq!(split("helloworld"), &["helloworld"]);
18171    assert_eq!(
18172        split("this@is!@#$^many   . symbols"),
18173        &[
18174            "symbols",
18175            " symbols",
18176            ". symbols",
18177            " . symbols",
18178            "  . symbols",
18179            "   . symbols",
18180            "many   . symbols",
18181            "^many   . symbols",
18182            "$^many   . symbols",
18183            "#$^many   . symbols",
18184            "@#$^many   . symbols",
18185            "!@#$^many   . symbols",
18186            "is!@#$^many   . symbols",
18187            "@is!@#$^many   . symbols",
18188            "this@is!@#$^many   . symbols",
18189        ],
18190    );
18191    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18192}
18193
18194#[gpui::test]
18195async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18196    init_test(cx, |_| {});
18197
18198    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18199
18200    #[track_caller]
18201    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18202        let _state_context = cx.set_state(before);
18203        cx.run_until_parked();
18204        cx.update_editor(|editor, window, cx| {
18205            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18206        });
18207        cx.run_until_parked();
18208        cx.assert_editor_state(after);
18209    }
18210
18211    // Outside bracket jumps to outside of matching bracket
18212    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18213    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18214
18215    // Inside bracket jumps to inside of matching bracket
18216    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18217    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18218
18219    // When outside a bracket and inside, favor jumping to the inside bracket
18220    assert(
18221        "console.log('foo', [1, 2, 3]ˇ);",
18222        "console.log('foo', ˇ[1, 2, 3]);",
18223        &mut cx,
18224    );
18225    assert(
18226        "console.log(ˇ'foo', [1, 2, 3]);",
18227        "console.log('foo'ˇ, [1, 2, 3]);",
18228        &mut cx,
18229    );
18230
18231    // Bias forward if two options are equally likely
18232    assert(
18233        "let result = curried_fun()ˇ();",
18234        "let result = curried_fun()()ˇ;",
18235        &mut cx,
18236    );
18237
18238    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18239    assert(
18240        indoc! {"
18241            function test() {
18242                console.log('test')ˇ
18243            }"},
18244        indoc! {"
18245            function test() {
18246                console.logˇ('test')
18247            }"},
18248        &mut cx,
18249    );
18250}
18251
18252#[gpui::test]
18253async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18254    init_test(cx, |_| {});
18255    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18256    language_registry.add(markdown_lang());
18257    language_registry.add(rust_lang());
18258    let buffer = cx.new(|cx| {
18259        let mut buffer = language::Buffer::local(
18260            indoc! {"
18261            ```rs
18262            impl Worktree {
18263                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18264                }
18265            }
18266            ```
18267        "},
18268            cx,
18269        );
18270        buffer.set_language_registry(language_registry.clone());
18271        buffer.set_language(Some(markdown_lang()), cx);
18272        buffer
18273    });
18274    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18275    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18276    cx.executor().run_until_parked();
18277    _ = editor.update(cx, |editor, window, cx| {
18278        // Case 1: Test outer enclosing brackets
18279        select_ranges(
18280            editor,
18281            &indoc! {"
18282                ```rs
18283                impl Worktree {
18284                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18285                    }
1828618287                ```
18288            "},
18289            window,
18290            cx,
18291        );
18292        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18293        assert_text_with_selections(
18294            editor,
18295            &indoc! {"
18296                ```rs
18297                impl Worktree ˇ{
18298                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18299                    }
18300                }
18301                ```
18302            "},
18303            cx,
18304        );
18305        // Case 2: Test inner enclosing brackets
18306        select_ranges(
18307            editor,
18308            &indoc! {"
18309                ```rs
18310                impl Worktree {
18311                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1831218313                }
18314                ```
18315            "},
18316            window,
18317            cx,
18318        );
18319        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18320        assert_text_with_selections(
18321            editor,
18322            &indoc! {"
18323                ```rs
18324                impl Worktree {
18325                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18326                    }
18327                }
18328                ```
18329            "},
18330            cx,
18331        );
18332    });
18333}
18334
18335#[gpui::test]
18336async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18337    init_test(cx, |_| {});
18338
18339    let fs = FakeFs::new(cx.executor());
18340    fs.insert_tree(
18341        path!("/a"),
18342        json!({
18343            "main.rs": "fn main() { let a = 5; }",
18344            "other.rs": "// Test file",
18345        }),
18346    )
18347    .await;
18348    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18349
18350    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18351    language_registry.add(Arc::new(Language::new(
18352        LanguageConfig {
18353            name: "Rust".into(),
18354            matcher: LanguageMatcher {
18355                path_suffixes: vec!["rs".to_string()],
18356                ..Default::default()
18357            },
18358            brackets: BracketPairConfig {
18359                pairs: vec![BracketPair {
18360                    start: "{".to_string(),
18361                    end: "}".to_string(),
18362                    close: true,
18363                    surround: true,
18364                    newline: true,
18365                }],
18366                disabled_scopes_by_bracket_ix: Vec::new(),
18367            },
18368            ..Default::default()
18369        },
18370        Some(tree_sitter_rust::LANGUAGE.into()),
18371    )));
18372    let mut fake_servers = language_registry.register_fake_lsp(
18373        "Rust",
18374        FakeLspAdapter {
18375            capabilities: lsp::ServerCapabilities {
18376                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18377                    first_trigger_character: "{".to_string(),
18378                    more_trigger_character: None,
18379                }),
18380                ..Default::default()
18381            },
18382            ..Default::default()
18383        },
18384    );
18385
18386    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18387
18388    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18389
18390    let worktree_id = workspace
18391        .update(cx, |workspace, _, cx| {
18392            workspace.project().update(cx, |project, cx| {
18393                project.worktrees(cx).next().unwrap().read(cx).id()
18394            })
18395        })
18396        .unwrap();
18397
18398    let buffer = project
18399        .update(cx, |project, cx| {
18400            project.open_local_buffer(path!("/a/main.rs"), cx)
18401        })
18402        .await
18403        .unwrap();
18404    let editor_handle = workspace
18405        .update(cx, |workspace, window, cx| {
18406            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18407        })
18408        .unwrap()
18409        .await
18410        .unwrap()
18411        .downcast::<Editor>()
18412        .unwrap();
18413
18414    let fake_server = fake_servers.next().await.unwrap();
18415
18416    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18417        |params, _| async move {
18418            assert_eq!(
18419                params.text_document_position.text_document.uri,
18420                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18421            );
18422            assert_eq!(
18423                params.text_document_position.position,
18424                lsp::Position::new(0, 21),
18425            );
18426
18427            Ok(Some(vec![lsp::TextEdit {
18428                new_text: "]".to_string(),
18429                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18430            }]))
18431        },
18432    );
18433
18434    editor_handle.update_in(cx, |editor, window, cx| {
18435        window.focus(&editor.focus_handle(cx), cx);
18436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18437            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18438        });
18439        editor.handle_input("{", window, cx);
18440    });
18441
18442    cx.executor().run_until_parked();
18443
18444    buffer.update(cx, |buffer, _| {
18445        assert_eq!(
18446            buffer.text(),
18447            "fn main() { let a = {5}; }",
18448            "No extra braces from on type formatting should appear in the buffer"
18449        )
18450    });
18451}
18452
18453#[gpui::test(iterations = 20, seeds(31))]
18454async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18455    init_test(cx, |_| {});
18456
18457    let mut cx = EditorLspTestContext::new_rust(
18458        lsp::ServerCapabilities {
18459            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18460                first_trigger_character: ".".to_string(),
18461                more_trigger_character: None,
18462            }),
18463            ..Default::default()
18464        },
18465        cx,
18466    )
18467    .await;
18468
18469    cx.update_buffer(|buffer, _| {
18470        // This causes autoindent to be async.
18471        buffer.set_sync_parse_timeout(None)
18472    });
18473
18474    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18475    cx.simulate_keystroke("\n");
18476    cx.run_until_parked();
18477
18478    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18479    let mut request =
18480        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18481            let buffer_cloned = buffer_cloned.clone();
18482            async move {
18483                buffer_cloned.update(&mut cx, |buffer, _| {
18484                    assert_eq!(
18485                        buffer.text(),
18486                        "fn c() {\n    d()\n        .\n}\n",
18487                        "OnTypeFormatting should triggered after autoindent applied"
18488                    )
18489                });
18490
18491                Ok(Some(vec![]))
18492            }
18493        });
18494
18495    cx.simulate_keystroke(".");
18496    cx.run_until_parked();
18497
18498    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18499    assert!(request.next().await.is_some());
18500    request.close();
18501    assert!(request.next().await.is_none());
18502}
18503
18504#[gpui::test]
18505async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18506    init_test(cx, |_| {});
18507
18508    let fs = FakeFs::new(cx.executor());
18509    fs.insert_tree(
18510        path!("/a"),
18511        json!({
18512            "main.rs": "fn main() { let a = 5; }",
18513            "other.rs": "// Test file",
18514        }),
18515    )
18516    .await;
18517
18518    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18519
18520    let server_restarts = Arc::new(AtomicUsize::new(0));
18521    let closure_restarts = Arc::clone(&server_restarts);
18522    let language_server_name = "test language server";
18523    let language_name: LanguageName = "Rust".into();
18524
18525    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18526    language_registry.add(Arc::new(Language::new(
18527        LanguageConfig {
18528            name: language_name.clone(),
18529            matcher: LanguageMatcher {
18530                path_suffixes: vec!["rs".to_string()],
18531                ..Default::default()
18532            },
18533            ..Default::default()
18534        },
18535        Some(tree_sitter_rust::LANGUAGE.into()),
18536    )));
18537    let mut fake_servers = language_registry.register_fake_lsp(
18538        "Rust",
18539        FakeLspAdapter {
18540            name: language_server_name,
18541            initialization_options: Some(json!({
18542                "testOptionValue": true
18543            })),
18544            initializer: Some(Box::new(move |fake_server| {
18545                let task_restarts = Arc::clone(&closure_restarts);
18546                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18547                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18548                    futures::future::ready(Ok(()))
18549                });
18550            })),
18551            ..Default::default()
18552        },
18553    );
18554
18555    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18556    let _buffer = project
18557        .update(cx, |project, cx| {
18558            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18559        })
18560        .await
18561        .unwrap();
18562    let _fake_server = fake_servers.next().await.unwrap();
18563    update_test_language_settings(cx, |language_settings| {
18564        language_settings.languages.0.insert(
18565            language_name.clone().0.to_string(),
18566            LanguageSettingsContent {
18567                tab_size: NonZeroU32::new(8),
18568                ..Default::default()
18569            },
18570        );
18571    });
18572    cx.executor().run_until_parked();
18573    assert_eq!(
18574        server_restarts.load(atomic::Ordering::Acquire),
18575        0,
18576        "Should not restart LSP server on an unrelated change"
18577    );
18578
18579    update_test_project_settings(cx, |project_settings| {
18580        project_settings.lsp.0.insert(
18581            "Some other server name".into(),
18582            LspSettings {
18583                binary: None,
18584                settings: None,
18585                initialization_options: Some(json!({
18586                    "some other init value": false
18587                })),
18588                enable_lsp_tasks: false,
18589                fetch: None,
18590            },
18591        );
18592    });
18593    cx.executor().run_until_parked();
18594    assert_eq!(
18595        server_restarts.load(atomic::Ordering::Acquire),
18596        0,
18597        "Should not restart LSP server on an unrelated LSP settings change"
18598    );
18599
18600    update_test_project_settings(cx, |project_settings| {
18601        project_settings.lsp.0.insert(
18602            language_server_name.into(),
18603            LspSettings {
18604                binary: None,
18605                settings: None,
18606                initialization_options: Some(json!({
18607                    "anotherInitValue": false
18608                })),
18609                enable_lsp_tasks: false,
18610                fetch: None,
18611            },
18612        );
18613    });
18614    cx.executor().run_until_parked();
18615    assert_eq!(
18616        server_restarts.load(atomic::Ordering::Acquire),
18617        1,
18618        "Should restart LSP server on a related LSP settings change"
18619    );
18620
18621    update_test_project_settings(cx, |project_settings| {
18622        project_settings.lsp.0.insert(
18623            language_server_name.into(),
18624            LspSettings {
18625                binary: None,
18626                settings: None,
18627                initialization_options: Some(json!({
18628                    "anotherInitValue": false
18629                })),
18630                enable_lsp_tasks: false,
18631                fetch: None,
18632            },
18633        );
18634    });
18635    cx.executor().run_until_parked();
18636    assert_eq!(
18637        server_restarts.load(atomic::Ordering::Acquire),
18638        1,
18639        "Should not restart LSP server on a related LSP settings change that is the same"
18640    );
18641
18642    update_test_project_settings(cx, |project_settings| {
18643        project_settings.lsp.0.insert(
18644            language_server_name.into(),
18645            LspSettings {
18646                binary: None,
18647                settings: None,
18648                initialization_options: None,
18649                enable_lsp_tasks: false,
18650                fetch: None,
18651            },
18652        );
18653    });
18654    cx.executor().run_until_parked();
18655    assert_eq!(
18656        server_restarts.load(atomic::Ordering::Acquire),
18657        2,
18658        "Should restart LSP server on another related LSP settings change"
18659    );
18660}
18661
18662#[gpui::test]
18663async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18664    init_test(cx, |_| {});
18665
18666    let mut cx = EditorLspTestContext::new_rust(
18667        lsp::ServerCapabilities {
18668            completion_provider: Some(lsp::CompletionOptions {
18669                trigger_characters: Some(vec![".".to_string()]),
18670                resolve_provider: Some(true),
18671                ..Default::default()
18672            }),
18673            ..Default::default()
18674        },
18675        cx,
18676    )
18677    .await;
18678
18679    cx.set_state("fn main() { let a = 2ˇ; }");
18680    cx.simulate_keystroke(".");
18681    let completion_item = lsp::CompletionItem {
18682        label: "some".into(),
18683        kind: Some(lsp::CompletionItemKind::SNIPPET),
18684        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18685        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18686            kind: lsp::MarkupKind::Markdown,
18687            value: "```rust\nSome(2)\n```".to_string(),
18688        })),
18689        deprecated: Some(false),
18690        sort_text: Some("fffffff2".to_string()),
18691        filter_text: Some("some".to_string()),
18692        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18693        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18694            range: lsp::Range {
18695                start: lsp::Position {
18696                    line: 0,
18697                    character: 22,
18698                },
18699                end: lsp::Position {
18700                    line: 0,
18701                    character: 22,
18702                },
18703            },
18704            new_text: "Some(2)".to_string(),
18705        })),
18706        additional_text_edits: Some(vec![lsp::TextEdit {
18707            range: lsp::Range {
18708                start: lsp::Position {
18709                    line: 0,
18710                    character: 20,
18711                },
18712                end: lsp::Position {
18713                    line: 0,
18714                    character: 22,
18715                },
18716            },
18717            new_text: "".to_string(),
18718        }]),
18719        ..Default::default()
18720    };
18721
18722    let closure_completion_item = completion_item.clone();
18723    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18724        let task_completion_item = closure_completion_item.clone();
18725        async move {
18726            Ok(Some(lsp::CompletionResponse::Array(vec![
18727                task_completion_item,
18728            ])))
18729        }
18730    });
18731
18732    request.next().await;
18733
18734    cx.condition(|editor, _| editor.context_menu_visible())
18735        .await;
18736    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18737        editor
18738            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18739            .unwrap()
18740    });
18741    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18742
18743    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18744        let task_completion_item = completion_item.clone();
18745        async move { Ok(task_completion_item) }
18746    })
18747    .next()
18748    .await
18749    .unwrap();
18750    apply_additional_edits.await.unwrap();
18751    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18752}
18753
18754#[gpui::test]
18755async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18756    init_test(cx, |_| {});
18757
18758    let mut cx = EditorLspTestContext::new_rust(
18759        lsp::ServerCapabilities {
18760            completion_provider: Some(lsp::CompletionOptions {
18761                trigger_characters: Some(vec![".".to_string()]),
18762                resolve_provider: Some(true),
18763                ..Default::default()
18764            }),
18765            ..Default::default()
18766        },
18767        cx,
18768    )
18769    .await;
18770
18771    cx.set_state("fn main() { let a = 2ˇ; }");
18772    cx.simulate_keystroke(".");
18773
18774    let item1 = lsp::CompletionItem {
18775        label: "method id()".to_string(),
18776        filter_text: Some("id".to_string()),
18777        detail: None,
18778        documentation: None,
18779        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18780            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18781            new_text: ".id".to_string(),
18782        })),
18783        ..lsp::CompletionItem::default()
18784    };
18785
18786    let item2 = lsp::CompletionItem {
18787        label: "other".to_string(),
18788        filter_text: Some("other".to_string()),
18789        detail: None,
18790        documentation: None,
18791        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18792            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18793            new_text: ".other".to_string(),
18794        })),
18795        ..lsp::CompletionItem::default()
18796    };
18797
18798    let item1 = item1.clone();
18799    cx.set_request_handler::<lsp::request::Completion, _, _>({
18800        let item1 = item1.clone();
18801        move |_, _, _| {
18802            let item1 = item1.clone();
18803            let item2 = item2.clone();
18804            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18805        }
18806    })
18807    .next()
18808    .await;
18809
18810    cx.condition(|editor, _| editor.context_menu_visible())
18811        .await;
18812    cx.update_editor(|editor, _, _| {
18813        let context_menu = editor.context_menu.borrow_mut();
18814        let context_menu = context_menu
18815            .as_ref()
18816            .expect("Should have the context menu deployed");
18817        match context_menu {
18818            CodeContextMenu::Completions(completions_menu) => {
18819                let completions = completions_menu.completions.borrow_mut();
18820                assert_eq!(
18821                    completions
18822                        .iter()
18823                        .map(|completion| &completion.label.text)
18824                        .collect::<Vec<_>>(),
18825                    vec!["method id()", "other"]
18826                )
18827            }
18828            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18829        }
18830    });
18831
18832    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18833        let item1 = item1.clone();
18834        move |_, item_to_resolve, _| {
18835            let item1 = item1.clone();
18836            async move {
18837                if item1 == item_to_resolve {
18838                    Ok(lsp::CompletionItem {
18839                        label: "method id()".to_string(),
18840                        filter_text: Some("id".to_string()),
18841                        detail: Some("Now resolved!".to_string()),
18842                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18843                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18844                            range: lsp::Range::new(
18845                                lsp::Position::new(0, 22),
18846                                lsp::Position::new(0, 22),
18847                            ),
18848                            new_text: ".id".to_string(),
18849                        })),
18850                        ..lsp::CompletionItem::default()
18851                    })
18852                } else {
18853                    Ok(item_to_resolve)
18854                }
18855            }
18856        }
18857    })
18858    .next()
18859    .await
18860    .unwrap();
18861    cx.run_until_parked();
18862
18863    cx.update_editor(|editor, window, cx| {
18864        editor.context_menu_next(&Default::default(), window, cx);
18865    });
18866    cx.run_until_parked();
18867
18868    cx.update_editor(|editor, _, _| {
18869        let context_menu = editor.context_menu.borrow_mut();
18870        let context_menu = context_menu
18871            .as_ref()
18872            .expect("Should have the context menu deployed");
18873        match context_menu {
18874            CodeContextMenu::Completions(completions_menu) => {
18875                let completions = completions_menu.completions.borrow_mut();
18876                assert_eq!(
18877                    completions
18878                        .iter()
18879                        .map(|completion| &completion.label.text)
18880                        .collect::<Vec<_>>(),
18881                    vec!["method id() Now resolved!", "other"],
18882                    "Should update first completion label, but not second as the filter text did not match."
18883                );
18884            }
18885            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18886        }
18887    });
18888}
18889
18890#[gpui::test]
18891async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18892    init_test(cx, |_| {});
18893    let mut cx = EditorLspTestContext::new_rust(
18894        lsp::ServerCapabilities {
18895            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18896            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18897            completion_provider: Some(lsp::CompletionOptions {
18898                resolve_provider: Some(true),
18899                ..Default::default()
18900            }),
18901            ..Default::default()
18902        },
18903        cx,
18904    )
18905    .await;
18906    cx.set_state(indoc! {"
18907        struct TestStruct {
18908            field: i32
18909        }
18910
18911        fn mainˇ() {
18912            let unused_var = 42;
18913            let test_struct = TestStruct { field: 42 };
18914        }
18915    "});
18916    let symbol_range = cx.lsp_range(indoc! {"
18917        struct TestStruct {
18918            field: i32
18919        }
18920
18921        «fn main»() {
18922            let unused_var = 42;
18923            let test_struct = TestStruct { field: 42 };
18924        }
18925    "});
18926    let mut hover_requests =
18927        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18928            Ok(Some(lsp::Hover {
18929                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18930                    kind: lsp::MarkupKind::Markdown,
18931                    value: "Function documentation".to_string(),
18932                }),
18933                range: Some(symbol_range),
18934            }))
18935        });
18936
18937    // Case 1: Test that code action menu hide hover popover
18938    cx.dispatch_action(Hover);
18939    hover_requests.next().await;
18940    cx.condition(|editor, _| editor.hover_state.visible()).await;
18941    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18942        move |_, _, _| async move {
18943            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18944                lsp::CodeAction {
18945                    title: "Remove unused variable".to_string(),
18946                    kind: Some(CodeActionKind::QUICKFIX),
18947                    edit: Some(lsp::WorkspaceEdit {
18948                        changes: Some(
18949                            [(
18950                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18951                                vec![lsp::TextEdit {
18952                                    range: lsp::Range::new(
18953                                        lsp::Position::new(5, 4),
18954                                        lsp::Position::new(5, 27),
18955                                    ),
18956                                    new_text: "".to_string(),
18957                                }],
18958                            )]
18959                            .into_iter()
18960                            .collect(),
18961                        ),
18962                        ..Default::default()
18963                    }),
18964                    ..Default::default()
18965                },
18966            )]))
18967        },
18968    );
18969    cx.update_editor(|editor, window, cx| {
18970        editor.toggle_code_actions(
18971            &ToggleCodeActions {
18972                deployed_from: None,
18973                quick_launch: false,
18974            },
18975            window,
18976            cx,
18977        );
18978    });
18979    code_action_requests.next().await;
18980    cx.run_until_parked();
18981    cx.condition(|editor, _| editor.context_menu_visible())
18982        .await;
18983    cx.update_editor(|editor, _, _| {
18984        assert!(
18985            !editor.hover_state.visible(),
18986            "Hover popover should be hidden when code action menu is shown"
18987        );
18988        // Hide code actions
18989        editor.context_menu.take();
18990    });
18991
18992    // Case 2: Test that code completions hide hover popover
18993    cx.dispatch_action(Hover);
18994    hover_requests.next().await;
18995    cx.condition(|editor, _| editor.hover_state.visible()).await;
18996    let counter = Arc::new(AtomicUsize::new(0));
18997    let mut completion_requests =
18998        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18999            let counter = counter.clone();
19000            async move {
19001                counter.fetch_add(1, atomic::Ordering::Release);
19002                Ok(Some(lsp::CompletionResponse::Array(vec![
19003                    lsp::CompletionItem {
19004                        label: "main".into(),
19005                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19006                        detail: Some("() -> ()".to_string()),
19007                        ..Default::default()
19008                    },
19009                    lsp::CompletionItem {
19010                        label: "TestStruct".into(),
19011                        kind: Some(lsp::CompletionItemKind::STRUCT),
19012                        detail: Some("struct TestStruct".to_string()),
19013                        ..Default::default()
19014                    },
19015                ])))
19016            }
19017        });
19018    cx.update_editor(|editor, window, cx| {
19019        editor.show_completions(&ShowCompletions, window, cx);
19020    });
19021    completion_requests.next().await;
19022    cx.condition(|editor, _| editor.context_menu_visible())
19023        .await;
19024    cx.update_editor(|editor, _, _| {
19025        assert!(
19026            !editor.hover_state.visible(),
19027            "Hover popover should be hidden when completion menu is shown"
19028        );
19029    });
19030}
19031
19032#[gpui::test]
19033async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19034    init_test(cx, |_| {});
19035
19036    let mut cx = EditorLspTestContext::new_rust(
19037        lsp::ServerCapabilities {
19038            completion_provider: Some(lsp::CompletionOptions {
19039                trigger_characters: Some(vec![".".to_string()]),
19040                resolve_provider: Some(true),
19041                ..Default::default()
19042            }),
19043            ..Default::default()
19044        },
19045        cx,
19046    )
19047    .await;
19048
19049    cx.set_state("fn main() { let a = 2ˇ; }");
19050    cx.simulate_keystroke(".");
19051
19052    let unresolved_item_1 = lsp::CompletionItem {
19053        label: "id".to_string(),
19054        filter_text: Some("id".to_string()),
19055        detail: None,
19056        documentation: None,
19057        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19058            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19059            new_text: ".id".to_string(),
19060        })),
19061        ..lsp::CompletionItem::default()
19062    };
19063    let resolved_item_1 = lsp::CompletionItem {
19064        additional_text_edits: Some(vec![lsp::TextEdit {
19065            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19066            new_text: "!!".to_string(),
19067        }]),
19068        ..unresolved_item_1.clone()
19069    };
19070    let unresolved_item_2 = lsp::CompletionItem {
19071        label: "other".to_string(),
19072        filter_text: Some("other".to_string()),
19073        detail: None,
19074        documentation: None,
19075        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19076            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19077            new_text: ".other".to_string(),
19078        })),
19079        ..lsp::CompletionItem::default()
19080    };
19081    let resolved_item_2 = lsp::CompletionItem {
19082        additional_text_edits: Some(vec![lsp::TextEdit {
19083            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19084            new_text: "??".to_string(),
19085        }]),
19086        ..unresolved_item_2.clone()
19087    };
19088
19089    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19090    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19091    cx.lsp
19092        .server
19093        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19094            let unresolved_item_1 = unresolved_item_1.clone();
19095            let resolved_item_1 = resolved_item_1.clone();
19096            let unresolved_item_2 = unresolved_item_2.clone();
19097            let resolved_item_2 = resolved_item_2.clone();
19098            let resolve_requests_1 = resolve_requests_1.clone();
19099            let resolve_requests_2 = resolve_requests_2.clone();
19100            move |unresolved_request, _| {
19101                let unresolved_item_1 = unresolved_item_1.clone();
19102                let resolved_item_1 = resolved_item_1.clone();
19103                let unresolved_item_2 = unresolved_item_2.clone();
19104                let resolved_item_2 = resolved_item_2.clone();
19105                let resolve_requests_1 = resolve_requests_1.clone();
19106                let resolve_requests_2 = resolve_requests_2.clone();
19107                async move {
19108                    if unresolved_request == unresolved_item_1 {
19109                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19110                        Ok(resolved_item_1.clone())
19111                    } else if unresolved_request == unresolved_item_2 {
19112                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19113                        Ok(resolved_item_2.clone())
19114                    } else {
19115                        panic!("Unexpected completion item {unresolved_request:?}")
19116                    }
19117                }
19118            }
19119        })
19120        .detach();
19121
19122    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19123        let unresolved_item_1 = unresolved_item_1.clone();
19124        let unresolved_item_2 = unresolved_item_2.clone();
19125        async move {
19126            Ok(Some(lsp::CompletionResponse::Array(vec![
19127                unresolved_item_1,
19128                unresolved_item_2,
19129            ])))
19130        }
19131    })
19132    .next()
19133    .await;
19134
19135    cx.condition(|editor, _| editor.context_menu_visible())
19136        .await;
19137    cx.update_editor(|editor, _, _| {
19138        let context_menu = editor.context_menu.borrow_mut();
19139        let context_menu = context_menu
19140            .as_ref()
19141            .expect("Should have the context menu deployed");
19142        match context_menu {
19143            CodeContextMenu::Completions(completions_menu) => {
19144                let completions = completions_menu.completions.borrow_mut();
19145                assert_eq!(
19146                    completions
19147                        .iter()
19148                        .map(|completion| &completion.label.text)
19149                        .collect::<Vec<_>>(),
19150                    vec!["id", "other"]
19151                )
19152            }
19153            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19154        }
19155    });
19156    cx.run_until_parked();
19157
19158    cx.update_editor(|editor, window, cx| {
19159        editor.context_menu_next(&ContextMenuNext, window, cx);
19160    });
19161    cx.run_until_parked();
19162    cx.update_editor(|editor, window, cx| {
19163        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19164    });
19165    cx.run_until_parked();
19166    cx.update_editor(|editor, window, cx| {
19167        editor.context_menu_next(&ContextMenuNext, window, cx);
19168    });
19169    cx.run_until_parked();
19170    cx.update_editor(|editor, window, cx| {
19171        editor
19172            .compose_completion(&ComposeCompletion::default(), window, cx)
19173            .expect("No task returned")
19174    })
19175    .await
19176    .expect("Completion failed");
19177    cx.run_until_parked();
19178
19179    cx.update_editor(|editor, _, cx| {
19180        assert_eq!(
19181            resolve_requests_1.load(atomic::Ordering::Acquire),
19182            1,
19183            "Should always resolve once despite multiple selections"
19184        );
19185        assert_eq!(
19186            resolve_requests_2.load(atomic::Ordering::Acquire),
19187            1,
19188            "Should always resolve once after multiple selections and applying the completion"
19189        );
19190        assert_eq!(
19191            editor.text(cx),
19192            "fn main() { let a = ??.other; }",
19193            "Should use resolved data when applying the completion"
19194        );
19195    });
19196}
19197
19198#[gpui::test]
19199async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19200    init_test(cx, |_| {});
19201
19202    let item_0 = lsp::CompletionItem {
19203        label: "abs".into(),
19204        insert_text: Some("abs".into()),
19205        data: Some(json!({ "very": "special"})),
19206        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19207        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19208            lsp::InsertReplaceEdit {
19209                new_text: "abs".to_string(),
19210                insert: lsp::Range::default(),
19211                replace: lsp::Range::default(),
19212            },
19213        )),
19214        ..lsp::CompletionItem::default()
19215    };
19216    let items = iter::once(item_0.clone())
19217        .chain((11..51).map(|i| lsp::CompletionItem {
19218            label: format!("item_{}", i),
19219            insert_text: Some(format!("item_{}", i)),
19220            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19221            ..lsp::CompletionItem::default()
19222        }))
19223        .collect::<Vec<_>>();
19224
19225    let default_commit_characters = vec!["?".to_string()];
19226    let default_data = json!({ "default": "data"});
19227    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19228    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19229    let default_edit_range = lsp::Range {
19230        start: lsp::Position {
19231            line: 0,
19232            character: 5,
19233        },
19234        end: lsp::Position {
19235            line: 0,
19236            character: 5,
19237        },
19238    };
19239
19240    let mut cx = EditorLspTestContext::new_rust(
19241        lsp::ServerCapabilities {
19242            completion_provider: Some(lsp::CompletionOptions {
19243                trigger_characters: Some(vec![".".to_string()]),
19244                resolve_provider: Some(true),
19245                ..Default::default()
19246            }),
19247            ..Default::default()
19248        },
19249        cx,
19250    )
19251    .await;
19252
19253    cx.set_state("fn main() { let a = 2ˇ; }");
19254    cx.simulate_keystroke(".");
19255
19256    let completion_data = default_data.clone();
19257    let completion_characters = default_commit_characters.clone();
19258    let completion_items = items.clone();
19259    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19260        let default_data = completion_data.clone();
19261        let default_commit_characters = completion_characters.clone();
19262        let items = completion_items.clone();
19263        async move {
19264            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19265                items,
19266                item_defaults: Some(lsp::CompletionListItemDefaults {
19267                    data: Some(default_data.clone()),
19268                    commit_characters: Some(default_commit_characters.clone()),
19269                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19270                        default_edit_range,
19271                    )),
19272                    insert_text_format: Some(default_insert_text_format),
19273                    insert_text_mode: Some(default_insert_text_mode),
19274                }),
19275                ..lsp::CompletionList::default()
19276            })))
19277        }
19278    })
19279    .next()
19280    .await;
19281
19282    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19283    cx.lsp
19284        .server
19285        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19286            let closure_resolved_items = resolved_items.clone();
19287            move |item_to_resolve, _| {
19288                let closure_resolved_items = closure_resolved_items.clone();
19289                async move {
19290                    closure_resolved_items.lock().push(item_to_resolve.clone());
19291                    Ok(item_to_resolve)
19292                }
19293            }
19294        })
19295        .detach();
19296
19297    cx.condition(|editor, _| editor.context_menu_visible())
19298        .await;
19299    cx.run_until_parked();
19300    cx.update_editor(|editor, _, _| {
19301        let menu = editor.context_menu.borrow_mut();
19302        match menu.as_ref().expect("should have the completions menu") {
19303            CodeContextMenu::Completions(completions_menu) => {
19304                assert_eq!(
19305                    completions_menu
19306                        .entries
19307                        .borrow()
19308                        .iter()
19309                        .map(|mat| mat.string.clone())
19310                        .collect::<Vec<String>>(),
19311                    items
19312                        .iter()
19313                        .map(|completion| completion.label.clone())
19314                        .collect::<Vec<String>>()
19315                );
19316            }
19317            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19318        }
19319    });
19320    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19321    // with 4 from the end.
19322    assert_eq!(
19323        *resolved_items.lock(),
19324        [&items[0..16], &items[items.len() - 4..items.len()]]
19325            .concat()
19326            .iter()
19327            .cloned()
19328            .map(|mut item| {
19329                if item.data.is_none() {
19330                    item.data = Some(default_data.clone());
19331                }
19332                item
19333            })
19334            .collect::<Vec<lsp::CompletionItem>>(),
19335        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19336    );
19337    resolved_items.lock().clear();
19338
19339    cx.update_editor(|editor, window, cx| {
19340        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19341    });
19342    cx.run_until_parked();
19343    // Completions that have already been resolved are skipped.
19344    assert_eq!(
19345        *resolved_items.lock(),
19346        items[items.len() - 17..items.len() - 4]
19347            .iter()
19348            .cloned()
19349            .map(|mut item| {
19350                if item.data.is_none() {
19351                    item.data = Some(default_data.clone());
19352                }
19353                item
19354            })
19355            .collect::<Vec<lsp::CompletionItem>>()
19356    );
19357    resolved_items.lock().clear();
19358}
19359
19360#[gpui::test]
19361async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19362    init_test(cx, |_| {});
19363
19364    let mut cx = EditorLspTestContext::new(
19365        Language::new(
19366            LanguageConfig {
19367                matcher: LanguageMatcher {
19368                    path_suffixes: vec!["jsx".into()],
19369                    ..Default::default()
19370                },
19371                overrides: [(
19372                    "element".into(),
19373                    LanguageConfigOverride {
19374                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19375                        ..Default::default()
19376                    },
19377                )]
19378                .into_iter()
19379                .collect(),
19380                ..Default::default()
19381            },
19382            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19383        )
19384        .with_override_query("(jsx_self_closing_element) @element")
19385        .unwrap(),
19386        lsp::ServerCapabilities {
19387            completion_provider: Some(lsp::CompletionOptions {
19388                trigger_characters: Some(vec![":".to_string()]),
19389                ..Default::default()
19390            }),
19391            ..Default::default()
19392        },
19393        cx,
19394    )
19395    .await;
19396
19397    cx.lsp
19398        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19399            Ok(Some(lsp::CompletionResponse::Array(vec![
19400                lsp::CompletionItem {
19401                    label: "bg-blue".into(),
19402                    ..Default::default()
19403                },
19404                lsp::CompletionItem {
19405                    label: "bg-red".into(),
19406                    ..Default::default()
19407                },
19408                lsp::CompletionItem {
19409                    label: "bg-yellow".into(),
19410                    ..Default::default()
19411                },
19412            ])))
19413        });
19414
19415    cx.set_state(r#"<p class="bgˇ" />"#);
19416
19417    // Trigger completion when typing a dash, because the dash is an extra
19418    // word character in the 'element' scope, which contains the cursor.
19419    cx.simulate_keystroke("-");
19420    cx.executor().run_until_parked();
19421    cx.update_editor(|editor, _, _| {
19422        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19423        {
19424            assert_eq!(
19425                completion_menu_entries(menu),
19426                &["bg-blue", "bg-red", "bg-yellow"]
19427            );
19428        } else {
19429            panic!("expected completion menu to be open");
19430        }
19431    });
19432
19433    cx.simulate_keystroke("l");
19434    cx.executor().run_until_parked();
19435    cx.update_editor(|editor, _, _| {
19436        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19437        {
19438            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19439        } else {
19440            panic!("expected completion menu to be open");
19441        }
19442    });
19443
19444    // When filtering completions, consider the character after the '-' to
19445    // be the start of a subword.
19446    cx.set_state(r#"<p class="yelˇ" />"#);
19447    cx.simulate_keystroke("l");
19448    cx.executor().run_until_parked();
19449    cx.update_editor(|editor, _, _| {
19450        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19451        {
19452            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19453        } else {
19454            panic!("expected completion menu to be open");
19455        }
19456    });
19457}
19458
19459fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19460    let entries = menu.entries.borrow();
19461    entries.iter().map(|mat| mat.string.clone()).collect()
19462}
19463
19464#[gpui::test]
19465async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19466    init_test(cx, |settings| {
19467        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19468    });
19469
19470    let fs = FakeFs::new(cx.executor());
19471    fs.insert_file(path!("/file.ts"), Default::default()).await;
19472
19473    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19474    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19475
19476    language_registry.add(Arc::new(Language::new(
19477        LanguageConfig {
19478            name: "TypeScript".into(),
19479            matcher: LanguageMatcher {
19480                path_suffixes: vec!["ts".to_string()],
19481                ..Default::default()
19482            },
19483            ..Default::default()
19484        },
19485        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19486    )));
19487    update_test_language_settings(cx, |settings| {
19488        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19489    });
19490
19491    let test_plugin = "test_plugin";
19492    let _ = language_registry.register_fake_lsp(
19493        "TypeScript",
19494        FakeLspAdapter {
19495            prettier_plugins: vec![test_plugin],
19496            ..Default::default()
19497        },
19498    );
19499
19500    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19501    let buffer = project
19502        .update(cx, |project, cx| {
19503            project.open_local_buffer(path!("/file.ts"), cx)
19504        })
19505        .await
19506        .unwrap();
19507
19508    let buffer_text = "one\ntwo\nthree\n";
19509    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19510    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19511    editor.update_in(cx, |editor, window, cx| {
19512        editor.set_text(buffer_text, window, cx)
19513    });
19514
19515    editor
19516        .update_in(cx, |editor, window, cx| {
19517            editor.perform_format(
19518                project.clone(),
19519                FormatTrigger::Manual,
19520                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19521                window,
19522                cx,
19523            )
19524        })
19525        .unwrap()
19526        .await;
19527    assert_eq!(
19528        editor.update(cx, |editor, cx| editor.text(cx)),
19529        buffer_text.to_string() + prettier_format_suffix,
19530        "Test prettier formatting was not applied to the original buffer text",
19531    );
19532
19533    update_test_language_settings(cx, |settings| {
19534        settings.defaults.formatter = Some(FormatterList::default())
19535    });
19536    let format = editor.update_in(cx, |editor, window, cx| {
19537        editor.perform_format(
19538            project.clone(),
19539            FormatTrigger::Manual,
19540            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19541            window,
19542            cx,
19543        )
19544    });
19545    format.await.unwrap();
19546    assert_eq!(
19547        editor.update(cx, |editor, cx| editor.text(cx)),
19548        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19549        "Autoformatting (via test prettier) was not applied to the original buffer text",
19550    );
19551}
19552
19553#[gpui::test]
19554async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19555    init_test(cx, |settings| {
19556        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19557    });
19558
19559    let fs = FakeFs::new(cx.executor());
19560    fs.insert_file(path!("/file.settings"), Default::default())
19561        .await;
19562
19563    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19564    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19565
19566    let ts_lang = Arc::new(Language::new(
19567        LanguageConfig {
19568            name: "TypeScript".into(),
19569            matcher: LanguageMatcher {
19570                path_suffixes: vec!["ts".to_string()],
19571                ..LanguageMatcher::default()
19572            },
19573            prettier_parser_name: Some("typescript".to_string()),
19574            ..LanguageConfig::default()
19575        },
19576        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19577    ));
19578
19579    language_registry.add(ts_lang.clone());
19580
19581    update_test_language_settings(cx, |settings| {
19582        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19583    });
19584
19585    let test_plugin = "test_plugin";
19586    let _ = language_registry.register_fake_lsp(
19587        "TypeScript",
19588        FakeLspAdapter {
19589            prettier_plugins: vec![test_plugin],
19590            ..Default::default()
19591        },
19592    );
19593
19594    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19595    let buffer = project
19596        .update(cx, |project, cx| {
19597            project.open_local_buffer(path!("/file.settings"), cx)
19598        })
19599        .await
19600        .unwrap();
19601
19602    project.update(cx, |project, cx| {
19603        project.set_language_for_buffer(&buffer, ts_lang, cx)
19604    });
19605
19606    let buffer_text = "one\ntwo\nthree\n";
19607    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19608    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19609    editor.update_in(cx, |editor, window, cx| {
19610        editor.set_text(buffer_text, window, cx)
19611    });
19612
19613    editor
19614        .update_in(cx, |editor, window, cx| {
19615            editor.perform_format(
19616                project.clone(),
19617                FormatTrigger::Manual,
19618                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19619                window,
19620                cx,
19621            )
19622        })
19623        .unwrap()
19624        .await;
19625    assert_eq!(
19626        editor.update(cx, |editor, cx| editor.text(cx)),
19627        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19628        "Test prettier formatting was not applied to the original buffer text",
19629    );
19630
19631    update_test_language_settings(cx, |settings| {
19632        settings.defaults.formatter = Some(FormatterList::default())
19633    });
19634    let format = editor.update_in(cx, |editor, window, cx| {
19635        editor.perform_format(
19636            project.clone(),
19637            FormatTrigger::Manual,
19638            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19639            window,
19640            cx,
19641        )
19642    });
19643    format.await.unwrap();
19644
19645    assert_eq!(
19646        editor.update(cx, |editor, cx| editor.text(cx)),
19647        buffer_text.to_string()
19648            + prettier_format_suffix
19649            + "\ntypescript\n"
19650            + prettier_format_suffix
19651            + "\ntypescript",
19652        "Autoformatting (via test prettier) was not applied to the original buffer text",
19653    );
19654}
19655
19656#[gpui::test]
19657async fn test_addition_reverts(cx: &mut TestAppContext) {
19658    init_test(cx, |_| {});
19659    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19660    let base_text = indoc! {r#"
19661        struct Row;
19662        struct Row1;
19663        struct Row2;
19664
19665        struct Row4;
19666        struct Row5;
19667        struct Row6;
19668
19669        struct Row8;
19670        struct Row9;
19671        struct Row10;"#};
19672
19673    // When addition hunks are not adjacent to carets, no hunk revert is performed
19674    assert_hunk_revert(
19675        indoc! {r#"struct Row;
19676                   struct Row1;
19677                   struct Row1.1;
19678                   struct Row1.2;
19679                   struct Row2;ˇ
19680
19681                   struct Row4;
19682                   struct Row5;
19683                   struct Row6;
19684
19685                   struct Row8;
19686                   ˇstruct Row9;
19687                   struct Row9.1;
19688                   struct Row9.2;
19689                   struct Row9.3;
19690                   struct Row10;"#},
19691        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19692        indoc! {r#"struct Row;
19693                   struct Row1;
19694                   struct Row1.1;
19695                   struct Row1.2;
19696                   struct Row2;ˇ
19697
19698                   struct Row4;
19699                   struct Row5;
19700                   struct Row6;
19701
19702                   struct Row8;
19703                   ˇstruct Row9;
19704                   struct Row9.1;
19705                   struct Row9.2;
19706                   struct Row9.3;
19707                   struct Row10;"#},
19708        base_text,
19709        &mut cx,
19710    );
19711    // Same for selections
19712    assert_hunk_revert(
19713        indoc! {r#"struct Row;
19714                   struct Row1;
19715                   struct Row2;
19716                   struct Row2.1;
19717                   struct Row2.2;
19718                   «ˇ
19719                   struct Row4;
19720                   struct» Row5;
19721                   «struct Row6;
19722                   ˇ»
19723                   struct Row9.1;
19724                   struct Row9.2;
19725                   struct Row9.3;
19726                   struct Row8;
19727                   struct Row9;
19728                   struct Row10;"#},
19729        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19730        indoc! {r#"struct Row;
19731                   struct Row1;
19732                   struct Row2;
19733                   struct Row2.1;
19734                   struct Row2.2;
19735                   «ˇ
19736                   struct Row4;
19737                   struct» Row5;
19738                   «struct Row6;
19739                   ˇ»
19740                   struct Row9.1;
19741                   struct Row9.2;
19742                   struct Row9.3;
19743                   struct Row8;
19744                   struct Row9;
19745                   struct Row10;"#},
19746        base_text,
19747        &mut cx,
19748    );
19749
19750    // When carets and selections intersect the addition hunks, those are reverted.
19751    // Adjacent carets got merged.
19752    assert_hunk_revert(
19753        indoc! {r#"struct Row;
19754                   ˇ// something on the top
19755                   struct Row1;
19756                   struct Row2;
19757                   struct Roˇw3.1;
19758                   struct Row2.2;
19759                   struct Row2.3;ˇ
19760
19761                   struct Row4;
19762                   struct ˇRow5.1;
19763                   struct Row5.2;
19764                   struct «Rowˇ»5.3;
19765                   struct Row5;
19766                   struct Row6;
19767                   ˇ
19768                   struct Row9.1;
19769                   struct «Rowˇ»9.2;
19770                   struct «ˇRow»9.3;
19771                   struct Row8;
19772                   struct Row9;
19773                   «ˇ// something on bottom»
19774                   struct Row10;"#},
19775        vec![
19776            DiffHunkStatusKind::Added,
19777            DiffHunkStatusKind::Added,
19778            DiffHunkStatusKind::Added,
19779            DiffHunkStatusKind::Added,
19780            DiffHunkStatusKind::Added,
19781        ],
19782        indoc! {r#"struct Row;
19783                   ˇstruct Row1;
19784                   struct Row2;
19785                   ˇ
19786                   struct Row4;
19787                   ˇstruct Row5;
19788                   struct Row6;
19789                   ˇ
19790                   ˇstruct Row8;
19791                   struct Row9;
19792                   ˇstruct Row10;"#},
19793        base_text,
19794        &mut cx,
19795    );
19796}
19797
19798#[gpui::test]
19799async fn test_modification_reverts(cx: &mut TestAppContext) {
19800    init_test(cx, |_| {});
19801    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19802    let base_text = indoc! {r#"
19803        struct Row;
19804        struct Row1;
19805        struct Row2;
19806
19807        struct Row4;
19808        struct Row5;
19809        struct Row6;
19810
19811        struct Row8;
19812        struct Row9;
19813        struct Row10;"#};
19814
19815    // Modification hunks behave the same as the addition ones.
19816    assert_hunk_revert(
19817        indoc! {r#"struct Row;
19818                   struct Row1;
19819                   struct Row33;
19820                   ˇ
19821                   struct Row4;
19822                   struct Row5;
19823                   struct Row6;
19824                   ˇ
19825                   struct Row99;
19826                   struct Row9;
19827                   struct Row10;"#},
19828        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19829        indoc! {r#"struct Row;
19830                   struct Row1;
19831                   struct Row33;
19832                   ˇ
19833                   struct Row4;
19834                   struct Row5;
19835                   struct Row6;
19836                   ˇ
19837                   struct Row99;
19838                   struct Row9;
19839                   struct Row10;"#},
19840        base_text,
19841        &mut cx,
19842    );
19843    assert_hunk_revert(
19844        indoc! {r#"struct Row;
19845                   struct Row1;
19846                   struct Row33;
19847                   «ˇ
19848                   struct Row4;
19849                   struct» Row5;
19850                   «struct Row6;
19851                   ˇ»
19852                   struct Row99;
19853                   struct Row9;
19854                   struct Row10;"#},
19855        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19856        indoc! {r#"struct Row;
19857                   struct Row1;
19858                   struct Row33;
19859                   «ˇ
19860                   struct Row4;
19861                   struct» Row5;
19862                   «struct Row6;
19863                   ˇ»
19864                   struct Row99;
19865                   struct Row9;
19866                   struct Row10;"#},
19867        base_text,
19868        &mut cx,
19869    );
19870
19871    assert_hunk_revert(
19872        indoc! {r#"ˇstruct Row1.1;
19873                   struct Row1;
19874                   «ˇstr»uct Row22;
19875
19876                   struct ˇRow44;
19877                   struct Row5;
19878                   struct «Rˇ»ow66;ˇ
19879
19880                   «struˇ»ct Row88;
19881                   struct Row9;
19882                   struct Row1011;ˇ"#},
19883        vec![
19884            DiffHunkStatusKind::Modified,
19885            DiffHunkStatusKind::Modified,
19886            DiffHunkStatusKind::Modified,
19887            DiffHunkStatusKind::Modified,
19888            DiffHunkStatusKind::Modified,
19889            DiffHunkStatusKind::Modified,
19890        ],
19891        indoc! {r#"struct Row;
19892                   ˇstruct Row1;
19893                   struct Row2;
19894                   ˇ
19895                   struct Row4;
19896                   ˇstruct Row5;
19897                   struct Row6;
19898                   ˇ
19899                   struct Row8;
19900                   ˇstruct Row9;
19901                   struct Row10;ˇ"#},
19902        base_text,
19903        &mut cx,
19904    );
19905}
19906
19907#[gpui::test]
19908async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19909    init_test(cx, |_| {});
19910    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19911    let base_text = indoc! {r#"
19912        one
19913
19914        two
19915        three
19916        "#};
19917
19918    cx.set_head_text(base_text);
19919    cx.set_state("\nˇ\n");
19920    cx.executor().run_until_parked();
19921    cx.update_editor(|editor, _window, cx| {
19922        editor.expand_selected_diff_hunks(cx);
19923    });
19924    cx.executor().run_until_parked();
19925    cx.update_editor(|editor, window, cx| {
19926        editor.backspace(&Default::default(), window, cx);
19927    });
19928    cx.run_until_parked();
19929    cx.assert_state_with_diff(
19930        indoc! {r#"
19931
19932        - two
19933        - threeˇ
19934        +
19935        "#}
19936        .to_string(),
19937    );
19938}
19939
19940#[gpui::test]
19941async fn test_deletion_reverts(cx: &mut TestAppContext) {
19942    init_test(cx, |_| {});
19943    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19944    let base_text = indoc! {r#"struct Row;
19945struct Row1;
19946struct Row2;
19947
19948struct Row4;
19949struct Row5;
19950struct Row6;
19951
19952struct Row8;
19953struct Row9;
19954struct Row10;"#};
19955
19956    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19957    assert_hunk_revert(
19958        indoc! {r#"struct Row;
19959                   struct Row2;
19960
19961                   ˇstruct Row4;
19962                   struct Row5;
19963                   struct Row6;
19964                   ˇ
19965                   struct Row8;
19966                   struct Row10;"#},
19967        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19968        indoc! {r#"struct Row;
19969                   struct Row2;
19970
19971                   ˇstruct Row4;
19972                   struct Row5;
19973                   struct Row6;
19974                   ˇ
19975                   struct Row8;
19976                   struct Row10;"#},
19977        base_text,
19978        &mut cx,
19979    );
19980    assert_hunk_revert(
19981        indoc! {r#"struct Row;
19982                   struct Row2;
19983
19984                   «ˇstruct Row4;
19985                   struct» Row5;
19986                   «struct Row6;
19987                   ˇ»
19988                   struct Row8;
19989                   struct Row10;"#},
19990        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19991        indoc! {r#"struct Row;
19992                   struct Row2;
19993
19994                   «ˇstruct Row4;
19995                   struct» Row5;
19996                   «struct Row6;
19997                   ˇ»
19998                   struct Row8;
19999                   struct Row10;"#},
20000        base_text,
20001        &mut cx,
20002    );
20003
20004    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20005    assert_hunk_revert(
20006        indoc! {r#"struct Row;
20007                   ˇstruct Row2;
20008
20009                   struct Row4;
20010                   struct Row5;
20011                   struct Row6;
20012
20013                   struct Row8;ˇ
20014                   struct Row10;"#},
20015        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20016        indoc! {r#"struct Row;
20017                   struct Row1;
20018                   ˇstruct Row2;
20019
20020                   struct Row4;
20021                   struct Row5;
20022                   struct Row6;
20023
20024                   struct Row8;ˇ
20025                   struct Row9;
20026                   struct Row10;"#},
20027        base_text,
20028        &mut cx,
20029    );
20030    assert_hunk_revert(
20031        indoc! {r#"struct Row;
20032                   struct Row2«ˇ;
20033                   struct Row4;
20034                   struct» Row5;
20035                   «struct Row6;
20036
20037                   struct Row8;ˇ»
20038                   struct Row10;"#},
20039        vec![
20040            DiffHunkStatusKind::Deleted,
20041            DiffHunkStatusKind::Deleted,
20042            DiffHunkStatusKind::Deleted,
20043        ],
20044        indoc! {r#"struct Row;
20045                   struct Row1;
20046                   struct Row2«ˇ;
20047
20048                   struct Row4;
20049                   struct» Row5;
20050                   «struct Row6;
20051
20052                   struct Row8;ˇ»
20053                   struct Row9;
20054                   struct Row10;"#},
20055        base_text,
20056        &mut cx,
20057    );
20058}
20059
20060#[gpui::test]
20061async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20062    init_test(cx, |_| {});
20063
20064    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20065    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20066    let base_text_3 =
20067        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20068
20069    let text_1 = edit_first_char_of_every_line(base_text_1);
20070    let text_2 = edit_first_char_of_every_line(base_text_2);
20071    let text_3 = edit_first_char_of_every_line(base_text_3);
20072
20073    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20074    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20075    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20076
20077    let multibuffer = cx.new(|cx| {
20078        let mut multibuffer = MultiBuffer::new(ReadWrite);
20079        multibuffer.push_excerpts(
20080            buffer_1.clone(),
20081            [
20082                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20083                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20084                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20085            ],
20086            cx,
20087        );
20088        multibuffer.push_excerpts(
20089            buffer_2.clone(),
20090            [
20091                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20092                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20093                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20094            ],
20095            cx,
20096        );
20097        multibuffer.push_excerpts(
20098            buffer_3.clone(),
20099            [
20100                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20101                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20102                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20103            ],
20104            cx,
20105        );
20106        multibuffer
20107    });
20108
20109    let fs = FakeFs::new(cx.executor());
20110    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20111    let (editor, cx) = cx
20112        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20113    editor.update_in(cx, |editor, _window, cx| {
20114        for (buffer, diff_base) in [
20115            (buffer_1.clone(), base_text_1),
20116            (buffer_2.clone(), base_text_2),
20117            (buffer_3.clone(), base_text_3),
20118        ] {
20119            let diff = cx.new(|cx| {
20120                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20121            });
20122            editor
20123                .buffer
20124                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20125        }
20126    });
20127    cx.executor().run_until_parked();
20128
20129    editor.update_in(cx, |editor, window, cx| {
20130        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}");
20131        editor.select_all(&SelectAll, window, cx);
20132        editor.git_restore(&Default::default(), window, cx);
20133    });
20134    cx.executor().run_until_parked();
20135
20136    // When all ranges are selected, all buffer hunks are reverted.
20137    editor.update(cx, |editor, cx| {
20138        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");
20139    });
20140    buffer_1.update(cx, |buffer, _| {
20141        assert_eq!(buffer.text(), base_text_1);
20142    });
20143    buffer_2.update(cx, |buffer, _| {
20144        assert_eq!(buffer.text(), base_text_2);
20145    });
20146    buffer_3.update(cx, |buffer, _| {
20147        assert_eq!(buffer.text(), base_text_3);
20148    });
20149
20150    editor.update_in(cx, |editor, window, cx| {
20151        editor.undo(&Default::default(), window, cx);
20152    });
20153
20154    editor.update_in(cx, |editor, window, cx| {
20155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20156            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20157        });
20158        editor.git_restore(&Default::default(), window, cx);
20159    });
20160
20161    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20162    // but not affect buffer_2 and its related excerpts.
20163    editor.update(cx, |editor, cx| {
20164        assert_eq!(
20165            editor.text(cx),
20166            "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}"
20167        );
20168    });
20169    buffer_1.update(cx, |buffer, _| {
20170        assert_eq!(buffer.text(), base_text_1);
20171    });
20172    buffer_2.update(cx, |buffer, _| {
20173        assert_eq!(
20174            buffer.text(),
20175            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20176        );
20177    });
20178    buffer_3.update(cx, |buffer, _| {
20179        assert_eq!(
20180            buffer.text(),
20181            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20182        );
20183    });
20184
20185    fn edit_first_char_of_every_line(text: &str) -> String {
20186        text.split('\n')
20187            .map(|line| format!("X{}", &line[1..]))
20188            .collect::<Vec<_>>()
20189            .join("\n")
20190    }
20191}
20192
20193#[gpui::test]
20194async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20195    init_test(cx, |_| {});
20196
20197    let cols = 4;
20198    let rows = 10;
20199    let sample_text_1 = sample_text(rows, cols, 'a');
20200    assert_eq!(
20201        sample_text_1,
20202        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20203    );
20204    let sample_text_2 = sample_text(rows, cols, 'l');
20205    assert_eq!(
20206        sample_text_2,
20207        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20208    );
20209    let sample_text_3 = sample_text(rows, cols, 'v');
20210    assert_eq!(
20211        sample_text_3,
20212        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20213    );
20214
20215    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20216    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20217    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20218
20219    let multi_buffer = cx.new(|cx| {
20220        let mut multibuffer = MultiBuffer::new(ReadWrite);
20221        multibuffer.push_excerpts(
20222            buffer_1.clone(),
20223            [
20224                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20225                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20226                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20227            ],
20228            cx,
20229        );
20230        multibuffer.push_excerpts(
20231            buffer_2.clone(),
20232            [
20233                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20234                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20235                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20236            ],
20237            cx,
20238        );
20239        multibuffer.push_excerpts(
20240            buffer_3.clone(),
20241            [
20242                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20243                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20244                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20245            ],
20246            cx,
20247        );
20248        multibuffer
20249    });
20250
20251    let fs = FakeFs::new(cx.executor());
20252    fs.insert_tree(
20253        "/a",
20254        json!({
20255            "main.rs": sample_text_1,
20256            "other.rs": sample_text_2,
20257            "lib.rs": sample_text_3,
20258        }),
20259    )
20260    .await;
20261    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20262    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20263    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20264    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20265        Editor::new(
20266            EditorMode::full(),
20267            multi_buffer,
20268            Some(project.clone()),
20269            window,
20270            cx,
20271        )
20272    });
20273    let multibuffer_item_id = workspace
20274        .update(cx, |workspace, window, cx| {
20275            assert!(
20276                workspace.active_item(cx).is_none(),
20277                "active item should be None before the first item is added"
20278            );
20279            workspace.add_item_to_active_pane(
20280                Box::new(multi_buffer_editor.clone()),
20281                None,
20282                true,
20283                window,
20284                cx,
20285            );
20286            let active_item = workspace
20287                .active_item(cx)
20288                .expect("should have an active item after adding the multi buffer");
20289            assert_eq!(
20290                active_item.buffer_kind(cx),
20291                ItemBufferKind::Multibuffer,
20292                "A multi buffer was expected to active after adding"
20293            );
20294            active_item.item_id()
20295        })
20296        .unwrap();
20297    cx.executor().run_until_parked();
20298
20299    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20300        editor.change_selections(
20301            SelectionEffects::scroll(Autoscroll::Next),
20302            window,
20303            cx,
20304            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20305        );
20306        editor.open_excerpts(&OpenExcerpts, window, cx);
20307    });
20308    cx.executor().run_until_parked();
20309    let first_item_id = workspace
20310        .update(cx, |workspace, window, cx| {
20311            let active_item = workspace
20312                .active_item(cx)
20313                .expect("should have an active item after navigating into the 1st buffer");
20314            let first_item_id = active_item.item_id();
20315            assert_ne!(
20316                first_item_id, multibuffer_item_id,
20317                "Should navigate into the 1st buffer and activate it"
20318            );
20319            assert_eq!(
20320                active_item.buffer_kind(cx),
20321                ItemBufferKind::Singleton,
20322                "New active item should be a singleton buffer"
20323            );
20324            assert_eq!(
20325                active_item
20326                    .act_as::<Editor>(cx)
20327                    .expect("should have navigated into an editor for the 1st buffer")
20328                    .read(cx)
20329                    .text(cx),
20330                sample_text_1
20331            );
20332
20333            workspace
20334                .go_back(workspace.active_pane().downgrade(), window, cx)
20335                .detach_and_log_err(cx);
20336
20337            first_item_id
20338        })
20339        .unwrap();
20340    cx.executor().run_until_parked();
20341    workspace
20342        .update(cx, |workspace, _, cx| {
20343            let active_item = workspace
20344                .active_item(cx)
20345                .expect("should have an active item after navigating back");
20346            assert_eq!(
20347                active_item.item_id(),
20348                multibuffer_item_id,
20349                "Should navigate back to the multi buffer"
20350            );
20351            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20352        })
20353        .unwrap();
20354
20355    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20356        editor.change_selections(
20357            SelectionEffects::scroll(Autoscroll::Next),
20358            window,
20359            cx,
20360            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20361        );
20362        editor.open_excerpts(&OpenExcerpts, window, cx);
20363    });
20364    cx.executor().run_until_parked();
20365    let second_item_id = workspace
20366        .update(cx, |workspace, window, cx| {
20367            let active_item = workspace
20368                .active_item(cx)
20369                .expect("should have an active item after navigating into the 2nd buffer");
20370            let second_item_id = active_item.item_id();
20371            assert_ne!(
20372                second_item_id, multibuffer_item_id,
20373                "Should navigate away from the multibuffer"
20374            );
20375            assert_ne!(
20376                second_item_id, first_item_id,
20377                "Should navigate into the 2nd buffer and activate it"
20378            );
20379            assert_eq!(
20380                active_item.buffer_kind(cx),
20381                ItemBufferKind::Singleton,
20382                "New active item should be a singleton buffer"
20383            );
20384            assert_eq!(
20385                active_item
20386                    .act_as::<Editor>(cx)
20387                    .expect("should have navigated into an editor")
20388                    .read(cx)
20389                    .text(cx),
20390                sample_text_2
20391            );
20392
20393            workspace
20394                .go_back(workspace.active_pane().downgrade(), window, cx)
20395                .detach_and_log_err(cx);
20396
20397            second_item_id
20398        })
20399        .unwrap();
20400    cx.executor().run_until_parked();
20401    workspace
20402        .update(cx, |workspace, _, cx| {
20403            let active_item = workspace
20404                .active_item(cx)
20405                .expect("should have an active item after navigating back from the 2nd buffer");
20406            assert_eq!(
20407                active_item.item_id(),
20408                multibuffer_item_id,
20409                "Should navigate back from the 2nd buffer to the multi buffer"
20410            );
20411            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20412        })
20413        .unwrap();
20414
20415    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20416        editor.change_selections(
20417            SelectionEffects::scroll(Autoscroll::Next),
20418            window,
20419            cx,
20420            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20421        );
20422        editor.open_excerpts(&OpenExcerpts, window, cx);
20423    });
20424    cx.executor().run_until_parked();
20425    workspace
20426        .update(cx, |workspace, window, cx| {
20427            let active_item = workspace
20428                .active_item(cx)
20429                .expect("should have an active item after navigating into the 3rd buffer");
20430            let third_item_id = active_item.item_id();
20431            assert_ne!(
20432                third_item_id, multibuffer_item_id,
20433                "Should navigate into the 3rd buffer and activate it"
20434            );
20435            assert_ne!(third_item_id, first_item_id);
20436            assert_ne!(third_item_id, second_item_id);
20437            assert_eq!(
20438                active_item.buffer_kind(cx),
20439                ItemBufferKind::Singleton,
20440                "New active item should be a singleton buffer"
20441            );
20442            assert_eq!(
20443                active_item
20444                    .act_as::<Editor>(cx)
20445                    .expect("should have navigated into an editor")
20446                    .read(cx)
20447                    .text(cx),
20448                sample_text_3
20449            );
20450
20451            workspace
20452                .go_back(workspace.active_pane().downgrade(), window, cx)
20453                .detach_and_log_err(cx);
20454        })
20455        .unwrap();
20456    cx.executor().run_until_parked();
20457    workspace
20458        .update(cx, |workspace, _, cx| {
20459            let active_item = workspace
20460                .active_item(cx)
20461                .expect("should have an active item after navigating back from the 3rd buffer");
20462            assert_eq!(
20463                active_item.item_id(),
20464                multibuffer_item_id,
20465                "Should navigate back from the 3rd buffer to the multi buffer"
20466            );
20467            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20468        })
20469        .unwrap();
20470}
20471
20472#[gpui::test]
20473async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20474    init_test(cx, |_| {});
20475
20476    let mut cx = EditorTestContext::new(cx).await;
20477
20478    let diff_base = r#"
20479        use some::mod;
20480
20481        const A: u32 = 42;
20482
20483        fn main() {
20484            println!("hello");
20485
20486            println!("world");
20487        }
20488        "#
20489    .unindent();
20490
20491    cx.set_state(
20492        &r#"
20493        use some::modified;
20494
20495        ˇ
20496        fn main() {
20497            println!("hello there");
20498
20499            println!("around the");
20500            println!("world");
20501        }
20502        "#
20503        .unindent(),
20504    );
20505
20506    cx.set_head_text(&diff_base);
20507    executor.run_until_parked();
20508
20509    cx.update_editor(|editor, window, cx| {
20510        editor.go_to_next_hunk(&GoToHunk, window, cx);
20511        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20512    });
20513    executor.run_until_parked();
20514    cx.assert_state_with_diff(
20515        r#"
20516          use some::modified;
20517
20518
20519          fn main() {
20520        -     println!("hello");
20521        + ˇ    println!("hello there");
20522
20523              println!("around the");
20524              println!("world");
20525          }
20526        "#
20527        .unindent(),
20528    );
20529
20530    cx.update_editor(|editor, window, cx| {
20531        for _ in 0..2 {
20532            editor.go_to_next_hunk(&GoToHunk, window, cx);
20533            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20534        }
20535    });
20536    executor.run_until_parked();
20537    cx.assert_state_with_diff(
20538        r#"
20539        - use some::mod;
20540        + ˇuse some::modified;
20541
20542
20543          fn main() {
20544        -     println!("hello");
20545        +     println!("hello there");
20546
20547        +     println!("around the");
20548              println!("world");
20549          }
20550        "#
20551        .unindent(),
20552    );
20553
20554    cx.update_editor(|editor, window, cx| {
20555        editor.go_to_next_hunk(&GoToHunk, window, cx);
20556        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20557    });
20558    executor.run_until_parked();
20559    cx.assert_state_with_diff(
20560        r#"
20561        - use some::mod;
20562        + use some::modified;
20563
20564        - const A: u32 = 42;
20565          ˇ
20566          fn main() {
20567        -     println!("hello");
20568        +     println!("hello there");
20569
20570        +     println!("around the");
20571              println!("world");
20572          }
20573        "#
20574        .unindent(),
20575    );
20576
20577    cx.update_editor(|editor, window, cx| {
20578        editor.cancel(&Cancel, window, cx);
20579    });
20580
20581    cx.assert_state_with_diff(
20582        r#"
20583          use some::modified;
20584
20585          ˇ
20586          fn main() {
20587              println!("hello there");
20588
20589              println!("around the");
20590              println!("world");
20591          }
20592        "#
20593        .unindent(),
20594    );
20595}
20596
20597#[gpui::test]
20598async fn test_diff_base_change_with_expanded_diff_hunks(
20599    executor: BackgroundExecutor,
20600    cx: &mut TestAppContext,
20601) {
20602    init_test(cx, |_| {});
20603
20604    let mut cx = EditorTestContext::new(cx).await;
20605
20606    let diff_base = r#"
20607        use some::mod1;
20608        use some::mod2;
20609
20610        const A: u32 = 42;
20611        const B: u32 = 42;
20612        const C: u32 = 42;
20613
20614        fn main() {
20615            println!("hello");
20616
20617            println!("world");
20618        }
20619        "#
20620    .unindent();
20621
20622    cx.set_state(
20623        &r#"
20624        use some::mod2;
20625
20626        const A: u32 = 42;
20627        const C: u32 = 42;
20628
20629        fn main(ˇ) {
20630            //println!("hello");
20631
20632            println!("world");
20633            //
20634            //
20635        }
20636        "#
20637        .unindent(),
20638    );
20639
20640    cx.set_head_text(&diff_base);
20641    executor.run_until_parked();
20642
20643    cx.update_editor(|editor, window, cx| {
20644        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20645    });
20646    executor.run_until_parked();
20647    cx.assert_state_with_diff(
20648        r#"
20649        - use some::mod1;
20650          use some::mod2;
20651
20652          const A: u32 = 42;
20653        - const B: u32 = 42;
20654          const C: u32 = 42;
20655
20656          fn main(ˇ) {
20657        -     println!("hello");
20658        +     //println!("hello");
20659
20660              println!("world");
20661        +     //
20662        +     //
20663          }
20664        "#
20665        .unindent(),
20666    );
20667
20668    cx.set_head_text("new diff base!");
20669    executor.run_until_parked();
20670    cx.assert_state_with_diff(
20671        r#"
20672        - new diff base!
20673        + use some::mod2;
20674        +
20675        + const A: u32 = 42;
20676        + const C: u32 = 42;
20677        +
20678        + fn main(ˇ) {
20679        +     //println!("hello");
20680        +
20681        +     println!("world");
20682        +     //
20683        +     //
20684        + }
20685        "#
20686        .unindent(),
20687    );
20688}
20689
20690#[gpui::test]
20691async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20692    init_test(cx, |_| {});
20693
20694    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20695    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20696    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20697    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20698    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20699    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20700
20701    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20702    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20703    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20704
20705    let multi_buffer = cx.new(|cx| {
20706        let mut multibuffer = MultiBuffer::new(ReadWrite);
20707        multibuffer.push_excerpts(
20708            buffer_1.clone(),
20709            [
20710                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20711                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20712                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20713            ],
20714            cx,
20715        );
20716        multibuffer.push_excerpts(
20717            buffer_2.clone(),
20718            [
20719                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20720                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20721                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20722            ],
20723            cx,
20724        );
20725        multibuffer.push_excerpts(
20726            buffer_3.clone(),
20727            [
20728                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20729                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20730                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20731            ],
20732            cx,
20733        );
20734        multibuffer
20735    });
20736
20737    let editor =
20738        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20739    editor
20740        .update(cx, |editor, _window, cx| {
20741            for (buffer, diff_base) in [
20742                (buffer_1.clone(), file_1_old),
20743                (buffer_2.clone(), file_2_old),
20744                (buffer_3.clone(), file_3_old),
20745            ] {
20746                let diff = cx.new(|cx| {
20747                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20748                });
20749                editor
20750                    .buffer
20751                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20752            }
20753        })
20754        .unwrap();
20755
20756    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20757    cx.run_until_parked();
20758
20759    cx.assert_editor_state(
20760        &"
20761            ˇaaa
20762            ccc
20763            ddd
20764
20765            ggg
20766            hhh
20767
20768
20769            lll
20770            mmm
20771            NNN
20772
20773            qqq
20774            rrr
20775
20776            uuu
20777            111
20778            222
20779            333
20780
20781            666
20782            777
20783
20784            000
20785            !!!"
20786        .unindent(),
20787    );
20788
20789    cx.update_editor(|editor, window, cx| {
20790        editor.select_all(&SelectAll, window, cx);
20791        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20792    });
20793    cx.executor().run_until_parked();
20794
20795    cx.assert_state_with_diff(
20796        "
20797            «aaa
20798          - bbb
20799            ccc
20800            ddd
20801
20802            ggg
20803            hhh
20804
20805
20806            lll
20807            mmm
20808          - nnn
20809          + NNN
20810
20811            qqq
20812            rrr
20813
20814            uuu
20815            111
20816            222
20817            333
20818
20819          + 666
20820            777
20821
20822            000
20823            !!!ˇ»"
20824            .unindent(),
20825    );
20826}
20827
20828#[gpui::test]
20829async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20830    init_test(cx, |_| {});
20831
20832    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20833    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20834
20835    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20836    let multi_buffer = cx.new(|cx| {
20837        let mut multibuffer = MultiBuffer::new(ReadWrite);
20838        multibuffer.push_excerpts(
20839            buffer.clone(),
20840            [
20841                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20842                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20843                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20844            ],
20845            cx,
20846        );
20847        multibuffer
20848    });
20849
20850    let editor =
20851        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20852    editor
20853        .update(cx, |editor, _window, cx| {
20854            let diff = cx.new(|cx| {
20855                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
20856            });
20857            editor
20858                .buffer
20859                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20860        })
20861        .unwrap();
20862
20863    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20864    cx.run_until_parked();
20865
20866    cx.update_editor(|editor, window, cx| {
20867        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20868    });
20869    cx.executor().run_until_parked();
20870
20871    // When the start of a hunk coincides with the start of its excerpt,
20872    // the hunk is expanded. When the start of a hunk is earlier than
20873    // the start of its excerpt, the hunk is not expanded.
20874    cx.assert_state_with_diff(
20875        "
20876            ˇaaa
20877          - bbb
20878          + BBB
20879
20880          - ddd
20881          - eee
20882          + DDD
20883          + EEE
20884            fff
20885
20886            iii
20887        "
20888        .unindent(),
20889    );
20890}
20891
20892#[gpui::test]
20893async fn test_edits_around_expanded_insertion_hunks(
20894    executor: BackgroundExecutor,
20895    cx: &mut TestAppContext,
20896) {
20897    init_test(cx, |_| {});
20898
20899    let mut cx = EditorTestContext::new(cx).await;
20900
20901    let diff_base = r#"
20902        use some::mod1;
20903        use some::mod2;
20904
20905        const A: u32 = 42;
20906
20907        fn main() {
20908            println!("hello");
20909
20910            println!("world");
20911        }
20912        "#
20913    .unindent();
20914    executor.run_until_parked();
20915    cx.set_state(
20916        &r#"
20917        use some::mod1;
20918        use some::mod2;
20919
20920        const A: u32 = 42;
20921        const B: u32 = 42;
20922        const C: u32 = 42;
20923        ˇ
20924
20925        fn main() {
20926            println!("hello");
20927
20928            println!("world");
20929        }
20930        "#
20931        .unindent(),
20932    );
20933
20934    cx.set_head_text(&diff_base);
20935    executor.run_until_parked();
20936
20937    cx.update_editor(|editor, window, cx| {
20938        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20939    });
20940    executor.run_until_parked();
20941
20942    cx.assert_state_with_diff(
20943        r#"
20944        use some::mod1;
20945        use some::mod2;
20946
20947        const A: u32 = 42;
20948      + const B: u32 = 42;
20949      + const C: u32 = 42;
20950      + ˇ
20951
20952        fn main() {
20953            println!("hello");
20954
20955            println!("world");
20956        }
20957      "#
20958        .unindent(),
20959    );
20960
20961    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20962    executor.run_until_parked();
20963
20964    cx.assert_state_with_diff(
20965        r#"
20966        use some::mod1;
20967        use some::mod2;
20968
20969        const A: u32 = 42;
20970      + const B: u32 = 42;
20971      + const C: u32 = 42;
20972      + const D: u32 = 42;
20973      + ˇ
20974
20975        fn main() {
20976            println!("hello");
20977
20978            println!("world");
20979        }
20980      "#
20981        .unindent(),
20982    );
20983
20984    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20985    executor.run_until_parked();
20986
20987    cx.assert_state_with_diff(
20988        r#"
20989        use some::mod1;
20990        use some::mod2;
20991
20992        const A: u32 = 42;
20993      + const B: u32 = 42;
20994      + const C: u32 = 42;
20995      + const D: u32 = 42;
20996      + const E: u32 = 42;
20997      + ˇ
20998
20999        fn main() {
21000            println!("hello");
21001
21002            println!("world");
21003        }
21004      "#
21005        .unindent(),
21006    );
21007
21008    cx.update_editor(|editor, window, cx| {
21009        editor.delete_line(&DeleteLine, window, cx);
21010    });
21011    executor.run_until_parked();
21012
21013    cx.assert_state_with_diff(
21014        r#"
21015        use some::mod1;
21016        use some::mod2;
21017
21018        const A: u32 = 42;
21019      + const B: u32 = 42;
21020      + const C: u32 = 42;
21021      + const D: u32 = 42;
21022      + const E: u32 = 42;
21023        ˇ
21024        fn main() {
21025            println!("hello");
21026
21027            println!("world");
21028        }
21029      "#
21030        .unindent(),
21031    );
21032
21033    cx.update_editor(|editor, window, cx| {
21034        editor.move_up(&MoveUp, window, cx);
21035        editor.delete_line(&DeleteLine, window, cx);
21036        editor.move_up(&MoveUp, window, cx);
21037        editor.delete_line(&DeleteLine, window, cx);
21038        editor.move_up(&MoveUp, window, cx);
21039        editor.delete_line(&DeleteLine, window, cx);
21040    });
21041    executor.run_until_parked();
21042    cx.assert_state_with_diff(
21043        r#"
21044        use some::mod1;
21045        use some::mod2;
21046
21047        const A: u32 = 42;
21048      + const B: u32 = 42;
21049        ˇ
21050        fn main() {
21051            println!("hello");
21052
21053            println!("world");
21054        }
21055      "#
21056        .unindent(),
21057    );
21058
21059    cx.update_editor(|editor, window, cx| {
21060        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21061        editor.delete_line(&DeleteLine, window, cx);
21062    });
21063    executor.run_until_parked();
21064    cx.assert_state_with_diff(
21065        r#"
21066        ˇ
21067        fn main() {
21068            println!("hello");
21069
21070            println!("world");
21071        }
21072      "#
21073        .unindent(),
21074    );
21075}
21076
21077#[gpui::test]
21078async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21079    init_test(cx, |_| {});
21080
21081    let mut cx = EditorTestContext::new(cx).await;
21082    cx.set_head_text(indoc! { "
21083        one
21084        two
21085        three
21086        four
21087        five
21088        "
21089    });
21090    cx.set_state(indoc! { "
21091        one
21092        ˇthree
21093        five
21094    "});
21095    cx.run_until_parked();
21096    cx.update_editor(|editor, window, cx| {
21097        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21098    });
21099    cx.assert_state_with_diff(
21100        indoc! { "
21101        one
21102      - two
21103        ˇthree
21104      - four
21105        five
21106    "}
21107        .to_string(),
21108    );
21109    cx.update_editor(|editor, window, cx| {
21110        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21111    });
21112
21113    cx.assert_state_with_diff(
21114        indoc! { "
21115        one
21116        ˇthree
21117        five
21118    "}
21119        .to_string(),
21120    );
21121
21122    cx.update_editor(|editor, window, cx| {
21123        editor.move_up(&MoveUp, window, cx);
21124        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21125    });
21126    cx.assert_state_with_diff(
21127        indoc! { "
21128        ˇone
21129      - two
21130        three
21131        five
21132    "}
21133        .to_string(),
21134    );
21135
21136    cx.update_editor(|editor, window, cx| {
21137        editor.move_down(&MoveDown, window, cx);
21138        editor.move_down(&MoveDown, window, cx);
21139        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21140    });
21141    cx.assert_state_with_diff(
21142        indoc! { "
21143        one
21144      - two
21145        ˇthree
21146      - four
21147        five
21148    "}
21149        .to_string(),
21150    );
21151
21152    cx.set_state(indoc! { "
21153        one
21154        ˇTWO
21155        three
21156        four
21157        five
21158    "});
21159    cx.run_until_parked();
21160    cx.update_editor(|editor, window, cx| {
21161        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21162    });
21163
21164    cx.assert_state_with_diff(
21165        indoc! { "
21166            one
21167          - two
21168          + ˇTWO
21169            three
21170            four
21171            five
21172        "}
21173        .to_string(),
21174    );
21175    cx.update_editor(|editor, window, cx| {
21176        editor.move_up(&Default::default(), window, cx);
21177        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21178    });
21179    cx.assert_state_with_diff(
21180        indoc! { "
21181            one
21182            ˇTWO
21183            three
21184            four
21185            five
21186        "}
21187        .to_string(),
21188    );
21189}
21190
21191#[gpui::test]
21192async fn test_toggling_adjacent_diff_hunks_2(
21193    executor: BackgroundExecutor,
21194    cx: &mut TestAppContext,
21195) {
21196    init_test(cx, |_| {});
21197
21198    let mut cx = EditorTestContext::new(cx).await;
21199
21200    let diff_base = r#"
21201        lineA
21202        lineB
21203        lineC
21204        lineD
21205        "#
21206    .unindent();
21207
21208    cx.set_state(
21209        &r#"
21210        ˇlineA1
21211        lineB
21212        lineD
21213        "#
21214        .unindent(),
21215    );
21216    cx.set_head_text(&diff_base);
21217    executor.run_until_parked();
21218
21219    cx.update_editor(|editor, window, cx| {
21220        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21221    });
21222    executor.run_until_parked();
21223    cx.assert_state_with_diff(
21224        r#"
21225        - lineA
21226        + ˇlineA1
21227          lineB
21228          lineD
21229        "#
21230        .unindent(),
21231    );
21232
21233    cx.update_editor(|editor, window, cx| {
21234        editor.move_down(&MoveDown, window, cx);
21235        editor.move_right(&MoveRight, window, cx);
21236        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21237    });
21238    executor.run_until_parked();
21239    cx.assert_state_with_diff(
21240        r#"
21241        - lineA
21242        + lineA1
21243          lˇineB
21244        - lineC
21245          lineD
21246        "#
21247        .unindent(),
21248    );
21249}
21250
21251#[gpui::test]
21252async fn test_edits_around_expanded_deletion_hunks(
21253    executor: BackgroundExecutor,
21254    cx: &mut TestAppContext,
21255) {
21256    init_test(cx, |_| {});
21257
21258    let mut cx = EditorTestContext::new(cx).await;
21259
21260    let diff_base = r#"
21261        use some::mod1;
21262        use some::mod2;
21263
21264        const A: u32 = 42;
21265        const B: u32 = 42;
21266        const C: u32 = 42;
21267
21268
21269        fn main() {
21270            println!("hello");
21271
21272            println!("world");
21273        }
21274    "#
21275    .unindent();
21276    executor.run_until_parked();
21277    cx.set_state(
21278        &r#"
21279        use some::mod1;
21280        use some::mod2;
21281
21282        ˇconst B: u32 = 42;
21283        const C: u32 = 42;
21284
21285
21286        fn main() {
21287            println!("hello");
21288
21289            println!("world");
21290        }
21291        "#
21292        .unindent(),
21293    );
21294
21295    cx.set_head_text(&diff_base);
21296    executor.run_until_parked();
21297
21298    cx.update_editor(|editor, window, cx| {
21299        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21300    });
21301    executor.run_until_parked();
21302
21303    cx.assert_state_with_diff(
21304        r#"
21305        use some::mod1;
21306        use some::mod2;
21307
21308      - const A: u32 = 42;
21309        ˇconst B: u32 = 42;
21310        const C: u32 = 42;
21311
21312
21313        fn main() {
21314            println!("hello");
21315
21316            println!("world");
21317        }
21318      "#
21319        .unindent(),
21320    );
21321
21322    cx.update_editor(|editor, window, cx| {
21323        editor.delete_line(&DeleteLine, window, cx);
21324    });
21325    executor.run_until_parked();
21326    cx.assert_state_with_diff(
21327        r#"
21328        use some::mod1;
21329        use some::mod2;
21330
21331      - const A: u32 = 42;
21332      - const B: u32 = 42;
21333        ˇconst C: u32 = 42;
21334
21335
21336        fn main() {
21337            println!("hello");
21338
21339            println!("world");
21340        }
21341      "#
21342        .unindent(),
21343    );
21344
21345    cx.update_editor(|editor, window, cx| {
21346        editor.delete_line(&DeleteLine, window, cx);
21347    });
21348    executor.run_until_parked();
21349    cx.assert_state_with_diff(
21350        r#"
21351        use some::mod1;
21352        use some::mod2;
21353
21354      - const A: u32 = 42;
21355      - const B: u32 = 42;
21356      - const C: u32 = 42;
21357        ˇ
21358
21359        fn main() {
21360            println!("hello");
21361
21362            println!("world");
21363        }
21364      "#
21365        .unindent(),
21366    );
21367
21368    cx.update_editor(|editor, window, cx| {
21369        editor.handle_input("replacement", window, cx);
21370    });
21371    executor.run_until_parked();
21372    cx.assert_state_with_diff(
21373        r#"
21374        use some::mod1;
21375        use some::mod2;
21376
21377      - const A: u32 = 42;
21378      - const B: u32 = 42;
21379      - const C: u32 = 42;
21380      -
21381      + replacementˇ
21382
21383        fn main() {
21384            println!("hello");
21385
21386            println!("world");
21387        }
21388      "#
21389        .unindent(),
21390    );
21391}
21392
21393#[gpui::test]
21394async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21395    init_test(cx, |_| {});
21396
21397    let mut cx = EditorTestContext::new(cx).await;
21398
21399    let base_text = r#"
21400        one
21401        two
21402        three
21403        four
21404        five
21405    "#
21406    .unindent();
21407    executor.run_until_parked();
21408    cx.set_state(
21409        &r#"
21410        one
21411        two
21412        fˇour
21413        five
21414        "#
21415        .unindent(),
21416    );
21417
21418    cx.set_head_text(&base_text);
21419    executor.run_until_parked();
21420
21421    cx.update_editor(|editor, window, cx| {
21422        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21423    });
21424    executor.run_until_parked();
21425
21426    cx.assert_state_with_diff(
21427        r#"
21428          one
21429          two
21430        - three
21431          fˇour
21432          five
21433        "#
21434        .unindent(),
21435    );
21436
21437    cx.update_editor(|editor, window, cx| {
21438        editor.backspace(&Backspace, window, cx);
21439        editor.backspace(&Backspace, window, cx);
21440    });
21441    executor.run_until_parked();
21442    cx.assert_state_with_diff(
21443        r#"
21444          one
21445          two
21446        - threeˇ
21447        - four
21448        + our
21449          five
21450        "#
21451        .unindent(),
21452    );
21453}
21454
21455#[gpui::test]
21456async fn test_edit_after_expanded_modification_hunk(
21457    executor: BackgroundExecutor,
21458    cx: &mut TestAppContext,
21459) {
21460    init_test(cx, |_| {});
21461
21462    let mut cx = EditorTestContext::new(cx).await;
21463
21464    let diff_base = r#"
21465        use some::mod1;
21466        use some::mod2;
21467
21468        const A: u32 = 42;
21469        const B: u32 = 42;
21470        const C: u32 = 42;
21471        const D: u32 = 42;
21472
21473
21474        fn main() {
21475            println!("hello");
21476
21477            println!("world");
21478        }"#
21479    .unindent();
21480
21481    cx.set_state(
21482        &r#"
21483        use some::mod1;
21484        use some::mod2;
21485
21486        const A: u32 = 42;
21487        const B: u32 = 42;
21488        const C: u32 = 43ˇ
21489        const D: u32 = 42;
21490
21491
21492        fn main() {
21493            println!("hello");
21494
21495            println!("world");
21496        }"#
21497        .unindent(),
21498    );
21499
21500    cx.set_head_text(&diff_base);
21501    executor.run_until_parked();
21502    cx.update_editor(|editor, window, cx| {
21503        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21504    });
21505    executor.run_until_parked();
21506
21507    cx.assert_state_with_diff(
21508        r#"
21509        use some::mod1;
21510        use some::mod2;
21511
21512        const A: u32 = 42;
21513        const B: u32 = 42;
21514      - const C: u32 = 42;
21515      + const C: u32 = 43ˇ
21516        const D: u32 = 42;
21517
21518
21519        fn main() {
21520            println!("hello");
21521
21522            println!("world");
21523        }"#
21524        .unindent(),
21525    );
21526
21527    cx.update_editor(|editor, window, cx| {
21528        editor.handle_input("\nnew_line\n", window, cx);
21529    });
21530    executor.run_until_parked();
21531
21532    cx.assert_state_with_diff(
21533        r#"
21534        use some::mod1;
21535        use some::mod2;
21536
21537        const A: u32 = 42;
21538        const B: u32 = 42;
21539      - const C: u32 = 42;
21540      + const C: u32 = 43
21541      + new_line
21542      + ˇ
21543        const D: u32 = 42;
21544
21545
21546        fn main() {
21547            println!("hello");
21548
21549            println!("world");
21550        }"#
21551        .unindent(),
21552    );
21553}
21554
21555#[gpui::test]
21556async fn test_stage_and_unstage_added_file_hunk(
21557    executor: BackgroundExecutor,
21558    cx: &mut TestAppContext,
21559) {
21560    init_test(cx, |_| {});
21561
21562    let mut cx = EditorTestContext::new(cx).await;
21563    cx.update_editor(|editor, _, cx| {
21564        editor.set_expand_all_diff_hunks(cx);
21565    });
21566
21567    let working_copy = r#"
21568            ˇfn main() {
21569                println!("hello, world!");
21570            }
21571        "#
21572    .unindent();
21573
21574    cx.set_state(&working_copy);
21575    executor.run_until_parked();
21576
21577    cx.assert_state_with_diff(
21578        r#"
21579            + ˇfn main() {
21580            +     println!("hello, world!");
21581            + }
21582        "#
21583        .unindent(),
21584    );
21585    cx.assert_index_text(None);
21586
21587    cx.update_editor(|editor, window, cx| {
21588        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21589    });
21590    executor.run_until_parked();
21591    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21592    cx.assert_state_with_diff(
21593        r#"
21594            + ˇfn main() {
21595            +     println!("hello, world!");
21596            + }
21597        "#
21598        .unindent(),
21599    );
21600
21601    cx.update_editor(|editor, window, cx| {
21602        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21603    });
21604    executor.run_until_parked();
21605    cx.assert_index_text(None);
21606}
21607
21608async fn setup_indent_guides_editor(
21609    text: &str,
21610    cx: &mut TestAppContext,
21611) -> (BufferId, EditorTestContext) {
21612    init_test(cx, |_| {});
21613
21614    let mut cx = EditorTestContext::new(cx).await;
21615
21616    let buffer_id = cx.update_editor(|editor, window, cx| {
21617        editor.set_text(text, window, cx);
21618        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21619
21620        buffer_ids[0]
21621    });
21622
21623    (buffer_id, cx)
21624}
21625
21626fn assert_indent_guides(
21627    range: Range<u32>,
21628    expected: Vec<IndentGuide>,
21629    active_indices: Option<Vec<usize>>,
21630    cx: &mut EditorTestContext,
21631) {
21632    let indent_guides = cx.update_editor(|editor, window, cx| {
21633        let snapshot = editor.snapshot(window, cx).display_snapshot;
21634        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21635            editor,
21636            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21637            true,
21638            &snapshot,
21639            cx,
21640        );
21641
21642        indent_guides.sort_by(|a, b| {
21643            a.depth.cmp(&b.depth).then(
21644                a.start_row
21645                    .cmp(&b.start_row)
21646                    .then(a.end_row.cmp(&b.end_row)),
21647            )
21648        });
21649        indent_guides
21650    });
21651
21652    if let Some(expected) = active_indices {
21653        let active_indices = cx.update_editor(|editor, window, cx| {
21654            let snapshot = editor.snapshot(window, cx).display_snapshot;
21655            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21656        });
21657
21658        assert_eq!(
21659            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21660            expected,
21661            "Active indent guide indices do not match"
21662        );
21663    }
21664
21665    assert_eq!(indent_guides, expected, "Indent guides do not match");
21666}
21667
21668fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21669    IndentGuide {
21670        buffer_id,
21671        start_row: MultiBufferRow(start_row),
21672        end_row: MultiBufferRow(end_row),
21673        depth,
21674        tab_size: 4,
21675        settings: IndentGuideSettings {
21676            enabled: true,
21677            line_width: 1,
21678            active_line_width: 1,
21679            coloring: IndentGuideColoring::default(),
21680            background_coloring: IndentGuideBackgroundColoring::default(),
21681        },
21682    }
21683}
21684
21685#[gpui::test]
21686async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21687    let (buffer_id, mut cx) = setup_indent_guides_editor(
21688        &"
21689        fn main() {
21690            let a = 1;
21691        }"
21692        .unindent(),
21693        cx,
21694    )
21695    .await;
21696
21697    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21698}
21699
21700#[gpui::test]
21701async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21702    let (buffer_id, mut cx) = setup_indent_guides_editor(
21703        &"
21704        fn main() {
21705            let a = 1;
21706            let b = 2;
21707        }"
21708        .unindent(),
21709        cx,
21710    )
21711    .await;
21712
21713    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21714}
21715
21716#[gpui::test]
21717async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21718    let (buffer_id, mut cx) = setup_indent_guides_editor(
21719        &"
21720        fn main() {
21721            let a = 1;
21722            if a == 3 {
21723                let b = 2;
21724            } else {
21725                let c = 3;
21726            }
21727        }"
21728        .unindent(),
21729        cx,
21730    )
21731    .await;
21732
21733    assert_indent_guides(
21734        0..8,
21735        vec![
21736            indent_guide(buffer_id, 1, 6, 0),
21737            indent_guide(buffer_id, 3, 3, 1),
21738            indent_guide(buffer_id, 5, 5, 1),
21739        ],
21740        None,
21741        &mut cx,
21742    );
21743}
21744
21745#[gpui::test]
21746async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21747    let (buffer_id, mut cx) = setup_indent_guides_editor(
21748        &"
21749        fn main() {
21750            let a = 1;
21751                let b = 2;
21752            let c = 3;
21753        }"
21754        .unindent(),
21755        cx,
21756    )
21757    .await;
21758
21759    assert_indent_guides(
21760        0..5,
21761        vec![
21762            indent_guide(buffer_id, 1, 3, 0),
21763            indent_guide(buffer_id, 2, 2, 1),
21764        ],
21765        None,
21766        &mut cx,
21767    );
21768}
21769
21770#[gpui::test]
21771async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21772    let (buffer_id, mut cx) = setup_indent_guides_editor(
21773        &"
21774        fn main() {
21775            let a = 1;
21776
21777            let c = 3;
21778        }"
21779        .unindent(),
21780        cx,
21781    )
21782    .await;
21783
21784    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21785}
21786
21787#[gpui::test]
21788async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21789    let (buffer_id, mut cx) = setup_indent_guides_editor(
21790        &"
21791        fn main() {
21792            let a = 1;
21793
21794            let c = 3;
21795
21796            if a == 3 {
21797                let b = 2;
21798            } else {
21799                let c = 3;
21800            }
21801        }"
21802        .unindent(),
21803        cx,
21804    )
21805    .await;
21806
21807    assert_indent_guides(
21808        0..11,
21809        vec![
21810            indent_guide(buffer_id, 1, 9, 0),
21811            indent_guide(buffer_id, 6, 6, 1),
21812            indent_guide(buffer_id, 8, 8, 1),
21813        ],
21814        None,
21815        &mut cx,
21816    );
21817}
21818
21819#[gpui::test]
21820async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21821    let (buffer_id, mut cx) = setup_indent_guides_editor(
21822        &"
21823        fn main() {
21824            let a = 1;
21825
21826            let c = 3;
21827
21828            if a == 3 {
21829                let b = 2;
21830            } else {
21831                let c = 3;
21832            }
21833        }"
21834        .unindent(),
21835        cx,
21836    )
21837    .await;
21838
21839    assert_indent_guides(
21840        1..11,
21841        vec![
21842            indent_guide(buffer_id, 1, 9, 0),
21843            indent_guide(buffer_id, 6, 6, 1),
21844            indent_guide(buffer_id, 8, 8, 1),
21845        ],
21846        None,
21847        &mut cx,
21848    );
21849}
21850
21851#[gpui::test]
21852async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21853    let (buffer_id, mut cx) = setup_indent_guides_editor(
21854        &"
21855        fn main() {
21856            let a = 1;
21857
21858            let c = 3;
21859
21860            if a == 3 {
21861                let b = 2;
21862            } else {
21863                let c = 3;
21864            }
21865        }"
21866        .unindent(),
21867        cx,
21868    )
21869    .await;
21870
21871    assert_indent_guides(
21872        1..10,
21873        vec![
21874            indent_guide(buffer_id, 1, 9, 0),
21875            indent_guide(buffer_id, 6, 6, 1),
21876            indent_guide(buffer_id, 8, 8, 1),
21877        ],
21878        None,
21879        &mut cx,
21880    );
21881}
21882
21883#[gpui::test]
21884async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21885    let (buffer_id, mut cx) = setup_indent_guides_editor(
21886        &"
21887        fn main() {
21888            if a {
21889                b(
21890                    c,
21891                    d,
21892                )
21893            } else {
21894                e(
21895                    f
21896                )
21897            }
21898        }"
21899        .unindent(),
21900        cx,
21901    )
21902    .await;
21903
21904    assert_indent_guides(
21905        0..11,
21906        vec![
21907            indent_guide(buffer_id, 1, 10, 0),
21908            indent_guide(buffer_id, 2, 5, 1),
21909            indent_guide(buffer_id, 7, 9, 1),
21910            indent_guide(buffer_id, 3, 4, 2),
21911            indent_guide(buffer_id, 8, 8, 2),
21912        ],
21913        None,
21914        &mut cx,
21915    );
21916
21917    cx.update_editor(|editor, window, cx| {
21918        editor.fold_at(MultiBufferRow(2), window, cx);
21919        assert_eq!(
21920            editor.display_text(cx),
21921            "
21922            fn main() {
21923                if a {
21924                    b(⋯
21925                    )
21926                } else {
21927                    e(
21928                        f
21929                    )
21930                }
21931            }"
21932            .unindent()
21933        );
21934    });
21935
21936    assert_indent_guides(
21937        0..11,
21938        vec![
21939            indent_guide(buffer_id, 1, 10, 0),
21940            indent_guide(buffer_id, 2, 5, 1),
21941            indent_guide(buffer_id, 7, 9, 1),
21942            indent_guide(buffer_id, 8, 8, 2),
21943        ],
21944        None,
21945        &mut cx,
21946    );
21947}
21948
21949#[gpui::test]
21950async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21951    let (buffer_id, mut cx) = setup_indent_guides_editor(
21952        &"
21953        block1
21954            block2
21955                block3
21956                    block4
21957            block2
21958        block1
21959        block1"
21960            .unindent(),
21961        cx,
21962    )
21963    .await;
21964
21965    assert_indent_guides(
21966        1..10,
21967        vec![
21968            indent_guide(buffer_id, 1, 4, 0),
21969            indent_guide(buffer_id, 2, 3, 1),
21970            indent_guide(buffer_id, 3, 3, 2),
21971        ],
21972        None,
21973        &mut cx,
21974    );
21975}
21976
21977#[gpui::test]
21978async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21979    let (buffer_id, mut cx) = setup_indent_guides_editor(
21980        &"
21981        block1
21982            block2
21983                block3
21984
21985        block1
21986        block1"
21987            .unindent(),
21988        cx,
21989    )
21990    .await;
21991
21992    assert_indent_guides(
21993        0..6,
21994        vec![
21995            indent_guide(buffer_id, 1, 2, 0),
21996            indent_guide(buffer_id, 2, 2, 1),
21997        ],
21998        None,
21999        &mut cx,
22000    );
22001}
22002
22003#[gpui::test]
22004async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22005    let (buffer_id, mut cx) = setup_indent_guides_editor(
22006        &"
22007        function component() {
22008        \treturn (
22009        \t\t\t
22010        \t\t<div>
22011        \t\t\t<abc></abc>
22012        \t\t</div>
22013        \t)
22014        }"
22015        .unindent(),
22016        cx,
22017    )
22018    .await;
22019
22020    assert_indent_guides(
22021        0..8,
22022        vec![
22023            indent_guide(buffer_id, 1, 6, 0),
22024            indent_guide(buffer_id, 2, 5, 1),
22025            indent_guide(buffer_id, 4, 4, 2),
22026        ],
22027        None,
22028        &mut cx,
22029    );
22030}
22031
22032#[gpui::test]
22033async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22034    let (buffer_id, mut cx) = setup_indent_guides_editor(
22035        &"
22036        function component() {
22037        \treturn (
22038        \t
22039        \t\t<div>
22040        \t\t\t<abc></abc>
22041        \t\t</div>
22042        \t)
22043        }"
22044        .unindent(),
22045        cx,
22046    )
22047    .await;
22048
22049    assert_indent_guides(
22050        0..8,
22051        vec![
22052            indent_guide(buffer_id, 1, 6, 0),
22053            indent_guide(buffer_id, 2, 5, 1),
22054            indent_guide(buffer_id, 4, 4, 2),
22055        ],
22056        None,
22057        &mut cx,
22058    );
22059}
22060
22061#[gpui::test]
22062async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22063    let (buffer_id, mut cx) = setup_indent_guides_editor(
22064        &"
22065        block1
22066
22067
22068
22069            block2
22070        "
22071        .unindent(),
22072        cx,
22073    )
22074    .await;
22075
22076    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22077}
22078
22079#[gpui::test]
22080async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22081    let (buffer_id, mut cx) = setup_indent_guides_editor(
22082        &"
22083        def a:
22084        \tb = 3
22085        \tif True:
22086        \t\tc = 4
22087        \t\td = 5
22088        \tprint(b)
22089        "
22090        .unindent(),
22091        cx,
22092    )
22093    .await;
22094
22095    assert_indent_guides(
22096        0..6,
22097        vec![
22098            indent_guide(buffer_id, 1, 5, 0),
22099            indent_guide(buffer_id, 3, 4, 1),
22100        ],
22101        None,
22102        &mut cx,
22103    );
22104}
22105
22106#[gpui::test]
22107async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22108    let (buffer_id, mut cx) = setup_indent_guides_editor(
22109        &"
22110    fn main() {
22111        let a = 1;
22112    }"
22113        .unindent(),
22114        cx,
22115    )
22116    .await;
22117
22118    cx.update_editor(|editor, window, cx| {
22119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22120            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22121        });
22122    });
22123
22124    assert_indent_guides(
22125        0..3,
22126        vec![indent_guide(buffer_id, 1, 1, 0)],
22127        Some(vec![0]),
22128        &mut cx,
22129    );
22130}
22131
22132#[gpui::test]
22133async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22134    let (buffer_id, mut cx) = setup_indent_guides_editor(
22135        &"
22136    fn main() {
22137        if 1 == 2 {
22138            let a = 1;
22139        }
22140    }"
22141        .unindent(),
22142        cx,
22143    )
22144    .await;
22145
22146    cx.update_editor(|editor, window, cx| {
22147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22148            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22149        });
22150    });
22151
22152    assert_indent_guides(
22153        0..4,
22154        vec![
22155            indent_guide(buffer_id, 1, 3, 0),
22156            indent_guide(buffer_id, 2, 2, 1),
22157        ],
22158        Some(vec![1]),
22159        &mut cx,
22160    );
22161
22162    cx.update_editor(|editor, window, cx| {
22163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22164            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22165        });
22166    });
22167
22168    assert_indent_guides(
22169        0..4,
22170        vec![
22171            indent_guide(buffer_id, 1, 3, 0),
22172            indent_guide(buffer_id, 2, 2, 1),
22173        ],
22174        Some(vec![1]),
22175        &mut cx,
22176    );
22177
22178    cx.update_editor(|editor, window, cx| {
22179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22180            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22181        });
22182    });
22183
22184    assert_indent_guides(
22185        0..4,
22186        vec![
22187            indent_guide(buffer_id, 1, 3, 0),
22188            indent_guide(buffer_id, 2, 2, 1),
22189        ],
22190        Some(vec![0]),
22191        &mut cx,
22192    );
22193}
22194
22195#[gpui::test]
22196async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22197    let (buffer_id, mut cx) = setup_indent_guides_editor(
22198        &"
22199    fn main() {
22200        let a = 1;
22201
22202        let b = 2;
22203    }"
22204        .unindent(),
22205        cx,
22206    )
22207    .await;
22208
22209    cx.update_editor(|editor, window, cx| {
22210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22211            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22212        });
22213    });
22214
22215    assert_indent_guides(
22216        0..5,
22217        vec![indent_guide(buffer_id, 1, 3, 0)],
22218        Some(vec![0]),
22219        &mut cx,
22220    );
22221}
22222
22223#[gpui::test]
22224async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22225    let (buffer_id, mut cx) = setup_indent_guides_editor(
22226        &"
22227    def m:
22228        a = 1
22229        pass"
22230            .unindent(),
22231        cx,
22232    )
22233    .await;
22234
22235    cx.update_editor(|editor, window, cx| {
22236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22237            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22238        });
22239    });
22240
22241    assert_indent_guides(
22242        0..3,
22243        vec![indent_guide(buffer_id, 1, 2, 0)],
22244        Some(vec![0]),
22245        &mut cx,
22246    );
22247}
22248
22249#[gpui::test]
22250async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22251    init_test(cx, |_| {});
22252    let mut cx = EditorTestContext::new(cx).await;
22253    let text = indoc! {
22254        "
22255        impl A {
22256            fn b() {
22257                0;
22258                3;
22259                5;
22260                6;
22261                7;
22262            }
22263        }
22264        "
22265    };
22266    let base_text = indoc! {
22267        "
22268        impl A {
22269            fn b() {
22270                0;
22271                1;
22272                2;
22273                3;
22274                4;
22275            }
22276            fn c() {
22277                5;
22278                6;
22279                7;
22280            }
22281        }
22282        "
22283    };
22284
22285    cx.update_editor(|editor, window, cx| {
22286        editor.set_text(text, window, cx);
22287
22288        editor.buffer().update(cx, |multibuffer, cx| {
22289            let buffer = multibuffer.as_singleton().unwrap();
22290            let diff = cx.new(|cx| {
22291                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22292            });
22293
22294            multibuffer.set_all_diff_hunks_expanded(cx);
22295            multibuffer.add_diff(diff, cx);
22296
22297            buffer.read(cx).remote_id()
22298        })
22299    });
22300    cx.run_until_parked();
22301
22302    cx.assert_state_with_diff(
22303        indoc! { "
22304          impl A {
22305              fn b() {
22306                  0;
22307        -         1;
22308        -         2;
22309                  3;
22310        -         4;
22311        -     }
22312        -     fn c() {
22313                  5;
22314                  6;
22315                  7;
22316              }
22317          }
22318          ˇ"
22319        }
22320        .to_string(),
22321    );
22322
22323    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22324        editor
22325            .snapshot(window, cx)
22326            .buffer_snapshot()
22327            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22328            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22329            .collect::<Vec<_>>()
22330    });
22331    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22332    assert_eq!(
22333        actual_guides,
22334        vec![
22335            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22336            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22337            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22338        ]
22339    );
22340}
22341
22342#[gpui::test]
22343async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22344    init_test(cx, |_| {});
22345    let mut cx = EditorTestContext::new(cx).await;
22346
22347    let diff_base = r#"
22348        a
22349        b
22350        c
22351        "#
22352    .unindent();
22353
22354    cx.set_state(
22355        &r#"
22356        ˇA
22357        b
22358        C
22359        "#
22360        .unindent(),
22361    );
22362    cx.set_head_text(&diff_base);
22363    cx.update_editor(|editor, window, cx| {
22364        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22365    });
22366    executor.run_until_parked();
22367
22368    let both_hunks_expanded = r#"
22369        - a
22370        + ˇA
22371          b
22372        - c
22373        + C
22374        "#
22375    .unindent();
22376
22377    cx.assert_state_with_diff(both_hunks_expanded.clone());
22378
22379    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22380        let snapshot = editor.snapshot(window, cx);
22381        let hunks = editor
22382            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22383            .collect::<Vec<_>>();
22384        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22385        hunks
22386            .into_iter()
22387            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22388            .collect::<Vec<_>>()
22389    });
22390    assert_eq!(hunk_ranges.len(), 2);
22391
22392    cx.update_editor(|editor, _, cx| {
22393        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22394    });
22395    executor.run_until_parked();
22396
22397    let second_hunk_expanded = r#"
22398          ˇA
22399          b
22400        - c
22401        + C
22402        "#
22403    .unindent();
22404
22405    cx.assert_state_with_diff(second_hunk_expanded);
22406
22407    cx.update_editor(|editor, _, cx| {
22408        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22409    });
22410    executor.run_until_parked();
22411
22412    cx.assert_state_with_diff(both_hunks_expanded.clone());
22413
22414    cx.update_editor(|editor, _, cx| {
22415        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22416    });
22417    executor.run_until_parked();
22418
22419    let first_hunk_expanded = r#"
22420        - a
22421        + ˇA
22422          b
22423          C
22424        "#
22425    .unindent();
22426
22427    cx.assert_state_with_diff(first_hunk_expanded);
22428
22429    cx.update_editor(|editor, _, cx| {
22430        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22431    });
22432    executor.run_until_parked();
22433
22434    cx.assert_state_with_diff(both_hunks_expanded);
22435
22436    cx.set_state(
22437        &r#"
22438        ˇA
22439        b
22440        "#
22441        .unindent(),
22442    );
22443    cx.run_until_parked();
22444
22445    // TODO this cursor position seems bad
22446    cx.assert_state_with_diff(
22447        r#"
22448        - ˇa
22449        + A
22450          b
22451        "#
22452        .unindent(),
22453    );
22454
22455    cx.update_editor(|editor, window, cx| {
22456        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22457    });
22458
22459    cx.assert_state_with_diff(
22460        r#"
22461            - ˇa
22462            + A
22463              b
22464            - c
22465            "#
22466        .unindent(),
22467    );
22468
22469    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22470        let snapshot = editor.snapshot(window, cx);
22471        let hunks = editor
22472            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22473            .collect::<Vec<_>>();
22474        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22475        hunks
22476            .into_iter()
22477            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22478            .collect::<Vec<_>>()
22479    });
22480    assert_eq!(hunk_ranges.len(), 2);
22481
22482    cx.update_editor(|editor, _, cx| {
22483        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22484    });
22485    executor.run_until_parked();
22486
22487    cx.assert_state_with_diff(
22488        r#"
22489        - ˇa
22490        + A
22491          b
22492        "#
22493        .unindent(),
22494    );
22495}
22496
22497#[gpui::test]
22498async fn test_toggle_deletion_hunk_at_start_of_file(
22499    executor: BackgroundExecutor,
22500    cx: &mut TestAppContext,
22501) {
22502    init_test(cx, |_| {});
22503    let mut cx = EditorTestContext::new(cx).await;
22504
22505    let diff_base = r#"
22506        a
22507        b
22508        c
22509        "#
22510    .unindent();
22511
22512    cx.set_state(
22513        &r#"
22514        ˇb
22515        c
22516        "#
22517        .unindent(),
22518    );
22519    cx.set_head_text(&diff_base);
22520    cx.update_editor(|editor, window, cx| {
22521        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22522    });
22523    executor.run_until_parked();
22524
22525    let hunk_expanded = r#"
22526        - a
22527          ˇb
22528          c
22529        "#
22530    .unindent();
22531
22532    cx.assert_state_with_diff(hunk_expanded.clone());
22533
22534    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22535        let snapshot = editor.snapshot(window, cx);
22536        let hunks = editor
22537            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22538            .collect::<Vec<_>>();
22539        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22540        hunks
22541            .into_iter()
22542            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22543            .collect::<Vec<_>>()
22544    });
22545    assert_eq!(hunk_ranges.len(), 1);
22546
22547    cx.update_editor(|editor, _, cx| {
22548        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22549    });
22550    executor.run_until_parked();
22551
22552    let hunk_collapsed = r#"
22553          ˇb
22554          c
22555        "#
22556    .unindent();
22557
22558    cx.assert_state_with_diff(hunk_collapsed);
22559
22560    cx.update_editor(|editor, _, cx| {
22561        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22562    });
22563    executor.run_until_parked();
22564
22565    cx.assert_state_with_diff(hunk_expanded);
22566}
22567
22568#[gpui::test]
22569async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22570    executor: BackgroundExecutor,
22571    cx: &mut TestAppContext,
22572) {
22573    init_test(cx, |_| {});
22574    let mut cx = EditorTestContext::new(cx).await;
22575
22576    cx.set_state("ˇnew\nsecond\nthird\n");
22577    cx.set_head_text("old\nsecond\nthird\n");
22578    cx.update_editor(|editor, window, cx| {
22579        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22580    });
22581    executor.run_until_parked();
22582    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22583
22584    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22585    cx.update_editor(|editor, window, cx| {
22586        let snapshot = editor.snapshot(window, cx);
22587        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22588        let hunks = editor
22589            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22590            .collect::<Vec<_>>();
22591        assert_eq!(hunks.len(), 1);
22592        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22593        editor.toggle_single_diff_hunk(hunk_range, cx)
22594    });
22595    executor.run_until_parked();
22596    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22597
22598    // Keep the editor scrolled to the top so the full hunk remains visible.
22599    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22600}
22601
22602#[gpui::test]
22603async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22604    init_test(cx, |_| {});
22605
22606    let fs = FakeFs::new(cx.executor());
22607    fs.insert_tree(
22608        path!("/test"),
22609        json!({
22610            ".git": {},
22611            "file-1": "ONE\n",
22612            "file-2": "TWO\n",
22613            "file-3": "THREE\n",
22614        }),
22615    )
22616    .await;
22617
22618    fs.set_head_for_repo(
22619        path!("/test/.git").as_ref(),
22620        &[
22621            ("file-1", "one\n".into()),
22622            ("file-2", "two\n".into()),
22623            ("file-3", "three\n".into()),
22624        ],
22625        "deadbeef",
22626    );
22627
22628    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22629    let mut buffers = vec![];
22630    for i in 1..=3 {
22631        let buffer = project
22632            .update(cx, |project, cx| {
22633                let path = format!(path!("/test/file-{}"), i);
22634                project.open_local_buffer(path, cx)
22635            })
22636            .await
22637            .unwrap();
22638        buffers.push(buffer);
22639    }
22640
22641    let multibuffer = cx.new(|cx| {
22642        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22643        multibuffer.set_all_diff_hunks_expanded(cx);
22644        for buffer in &buffers {
22645            let snapshot = buffer.read(cx).snapshot();
22646            multibuffer.set_excerpts_for_path(
22647                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22648                buffer.clone(),
22649                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22650                2,
22651                cx,
22652            );
22653        }
22654        multibuffer
22655    });
22656
22657    let editor = cx.add_window(|window, cx| {
22658        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22659    });
22660    cx.run_until_parked();
22661
22662    let snapshot = editor
22663        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22664        .unwrap();
22665    let hunks = snapshot
22666        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22667        .map(|hunk| match hunk {
22668            DisplayDiffHunk::Unfolded {
22669                display_row_range, ..
22670            } => display_row_range,
22671            DisplayDiffHunk::Folded { .. } => unreachable!(),
22672        })
22673        .collect::<Vec<_>>();
22674    assert_eq!(
22675        hunks,
22676        [
22677            DisplayRow(2)..DisplayRow(4),
22678            DisplayRow(7)..DisplayRow(9),
22679            DisplayRow(12)..DisplayRow(14),
22680        ]
22681    );
22682}
22683
22684#[gpui::test]
22685async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22686    init_test(cx, |_| {});
22687
22688    let mut cx = EditorTestContext::new(cx).await;
22689    cx.set_head_text(indoc! { "
22690        one
22691        two
22692        three
22693        four
22694        five
22695        "
22696    });
22697    cx.set_index_text(indoc! { "
22698        one
22699        two
22700        three
22701        four
22702        five
22703        "
22704    });
22705    cx.set_state(indoc! {"
22706        one
22707        TWO
22708        ˇTHREE
22709        FOUR
22710        five
22711    "});
22712    cx.run_until_parked();
22713    cx.update_editor(|editor, window, cx| {
22714        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22715    });
22716    cx.run_until_parked();
22717    cx.assert_index_text(Some(indoc! {"
22718        one
22719        TWO
22720        THREE
22721        FOUR
22722        five
22723    "}));
22724    cx.set_state(indoc! { "
22725        one
22726        TWO
22727        ˇTHREE-HUNDRED
22728        FOUR
22729        five
22730    "});
22731    cx.run_until_parked();
22732    cx.update_editor(|editor, window, cx| {
22733        let snapshot = editor.snapshot(window, cx);
22734        let hunks = editor
22735            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22736            .collect::<Vec<_>>();
22737        assert_eq!(hunks.len(), 1);
22738        assert_eq!(
22739            hunks[0].status(),
22740            DiffHunkStatus {
22741                kind: DiffHunkStatusKind::Modified,
22742                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22743            }
22744        );
22745
22746        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22747    });
22748    cx.run_until_parked();
22749    cx.assert_index_text(Some(indoc! {"
22750        one
22751        TWO
22752        THREE-HUNDRED
22753        FOUR
22754        five
22755    "}));
22756}
22757
22758#[gpui::test]
22759fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22760    init_test(cx, |_| {});
22761
22762    let editor = cx.add_window(|window, cx| {
22763        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22764        build_editor(buffer, window, cx)
22765    });
22766
22767    let render_args = Arc::new(Mutex::new(None));
22768    let snapshot = editor
22769        .update(cx, |editor, window, cx| {
22770            let snapshot = editor.buffer().read(cx).snapshot(cx);
22771            let range =
22772                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22773
22774            struct RenderArgs {
22775                row: MultiBufferRow,
22776                folded: bool,
22777                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22778            }
22779
22780            let crease = Crease::inline(
22781                range,
22782                FoldPlaceholder::test(),
22783                {
22784                    let toggle_callback = render_args.clone();
22785                    move |row, folded, callback, _window, _cx| {
22786                        *toggle_callback.lock() = Some(RenderArgs {
22787                            row,
22788                            folded,
22789                            callback,
22790                        });
22791                        div()
22792                    }
22793                },
22794                |_row, _folded, _window, _cx| div(),
22795            );
22796
22797            editor.insert_creases(Some(crease), cx);
22798            let snapshot = editor.snapshot(window, cx);
22799            let _div =
22800                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22801            snapshot
22802        })
22803        .unwrap();
22804
22805    let render_args = render_args.lock().take().unwrap();
22806    assert_eq!(render_args.row, MultiBufferRow(1));
22807    assert!(!render_args.folded);
22808    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22809
22810    cx.update_window(*editor, |_, window, cx| {
22811        (render_args.callback)(true, window, cx)
22812    })
22813    .unwrap();
22814    let snapshot = editor
22815        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22816        .unwrap();
22817    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22818
22819    cx.update_window(*editor, |_, window, cx| {
22820        (render_args.callback)(false, window, cx)
22821    })
22822    .unwrap();
22823    let snapshot = editor
22824        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22825        .unwrap();
22826    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22827}
22828
22829#[gpui::test]
22830async fn test_input_text(cx: &mut TestAppContext) {
22831    init_test(cx, |_| {});
22832    let mut cx = EditorTestContext::new(cx).await;
22833
22834    cx.set_state(
22835        &r#"ˇone
22836        two
22837
22838        three
22839        fourˇ
22840        five
22841
22842        siˇx"#
22843            .unindent(),
22844    );
22845
22846    cx.dispatch_action(HandleInput(String::new()));
22847    cx.assert_editor_state(
22848        &r#"ˇone
22849        two
22850
22851        three
22852        fourˇ
22853        five
22854
22855        siˇx"#
22856            .unindent(),
22857    );
22858
22859    cx.dispatch_action(HandleInput("AAAA".to_string()));
22860    cx.assert_editor_state(
22861        &r#"AAAAˇone
22862        two
22863
22864        three
22865        fourAAAAˇ
22866        five
22867
22868        siAAAAˇx"#
22869            .unindent(),
22870    );
22871}
22872
22873#[gpui::test]
22874async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22875    init_test(cx, |_| {});
22876
22877    let mut cx = EditorTestContext::new(cx).await;
22878    cx.set_state(
22879        r#"let foo = 1;
22880let foo = 2;
22881let foo = 3;
22882let fooˇ = 4;
22883let foo = 5;
22884let foo = 6;
22885let foo = 7;
22886let foo = 8;
22887let foo = 9;
22888let foo = 10;
22889let foo = 11;
22890let foo = 12;
22891let foo = 13;
22892let foo = 14;
22893let foo = 15;"#,
22894    );
22895
22896    cx.update_editor(|e, window, cx| {
22897        assert_eq!(
22898            e.next_scroll_position,
22899            NextScrollCursorCenterTopBottom::Center,
22900            "Default next scroll direction is center",
22901        );
22902
22903        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22904        assert_eq!(
22905            e.next_scroll_position,
22906            NextScrollCursorCenterTopBottom::Top,
22907            "After center, next scroll direction should be top",
22908        );
22909
22910        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22911        assert_eq!(
22912            e.next_scroll_position,
22913            NextScrollCursorCenterTopBottom::Bottom,
22914            "After top, next scroll direction should be bottom",
22915        );
22916
22917        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22918        assert_eq!(
22919            e.next_scroll_position,
22920            NextScrollCursorCenterTopBottom::Center,
22921            "After bottom, scrolling should start over",
22922        );
22923
22924        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22925        assert_eq!(
22926            e.next_scroll_position,
22927            NextScrollCursorCenterTopBottom::Top,
22928            "Scrolling continues if retriggered fast enough"
22929        );
22930    });
22931
22932    cx.executor()
22933        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22934    cx.executor().run_until_parked();
22935    cx.update_editor(|e, _, _| {
22936        assert_eq!(
22937            e.next_scroll_position,
22938            NextScrollCursorCenterTopBottom::Center,
22939            "If scrolling is not triggered fast enough, it should reset"
22940        );
22941    });
22942}
22943
22944#[gpui::test]
22945async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22946    init_test(cx, |_| {});
22947    let mut cx = EditorLspTestContext::new_rust(
22948        lsp::ServerCapabilities {
22949            definition_provider: Some(lsp::OneOf::Left(true)),
22950            references_provider: Some(lsp::OneOf::Left(true)),
22951            ..lsp::ServerCapabilities::default()
22952        },
22953        cx,
22954    )
22955    .await;
22956
22957    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22958        let go_to_definition = cx
22959            .lsp
22960            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22961                move |params, _| async move {
22962                    if empty_go_to_definition {
22963                        Ok(None)
22964                    } else {
22965                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22966                            uri: params.text_document_position_params.text_document.uri,
22967                            range: lsp::Range::new(
22968                                lsp::Position::new(4, 3),
22969                                lsp::Position::new(4, 6),
22970                            ),
22971                        })))
22972                    }
22973                },
22974            );
22975        let references = cx
22976            .lsp
22977            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22978                Ok(Some(vec![lsp::Location {
22979                    uri: params.text_document_position.text_document.uri,
22980                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22981                }]))
22982            });
22983        (go_to_definition, references)
22984    };
22985
22986    cx.set_state(
22987        &r#"fn one() {
22988            let mut a = ˇtwo();
22989        }
22990
22991        fn two() {}"#
22992            .unindent(),
22993    );
22994    set_up_lsp_handlers(false, &mut cx);
22995    let navigated = cx
22996        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22997        .await
22998        .expect("Failed to navigate to definition");
22999    assert_eq!(
23000        navigated,
23001        Navigated::Yes,
23002        "Should have navigated to definition from the GetDefinition response"
23003    );
23004    cx.assert_editor_state(
23005        &r#"fn one() {
23006            let mut a = two();
23007        }
23008
23009        fn «twoˇ»() {}"#
23010            .unindent(),
23011    );
23012
23013    let editors = cx.update_workspace(|workspace, _, cx| {
23014        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23015    });
23016    cx.update_editor(|_, _, test_editor_cx| {
23017        assert_eq!(
23018            editors.len(),
23019            1,
23020            "Initially, only one, test, editor should be open in the workspace"
23021        );
23022        assert_eq!(
23023            test_editor_cx.entity(),
23024            editors.last().expect("Asserted len is 1").clone()
23025        );
23026    });
23027
23028    set_up_lsp_handlers(true, &mut cx);
23029    let navigated = cx
23030        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23031        .await
23032        .expect("Failed to navigate to lookup references");
23033    assert_eq!(
23034        navigated,
23035        Navigated::Yes,
23036        "Should have navigated to references as a fallback after empty GoToDefinition response"
23037    );
23038    // We should not change the selections in the existing file,
23039    // if opening another milti buffer with the references
23040    cx.assert_editor_state(
23041        &r#"fn one() {
23042            let mut a = two();
23043        }
23044
23045        fn «twoˇ»() {}"#
23046            .unindent(),
23047    );
23048    let editors = cx.update_workspace(|workspace, _, cx| {
23049        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23050    });
23051    cx.update_editor(|_, _, test_editor_cx| {
23052        assert_eq!(
23053            editors.len(),
23054            2,
23055            "After falling back to references search, we open a new editor with the results"
23056        );
23057        let references_fallback_text = editors
23058            .into_iter()
23059            .find(|new_editor| *new_editor != test_editor_cx.entity())
23060            .expect("Should have one non-test editor now")
23061            .read(test_editor_cx)
23062            .text(test_editor_cx);
23063        assert_eq!(
23064            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23065            "Should use the range from the references response and not the GoToDefinition one"
23066        );
23067    });
23068}
23069
23070#[gpui::test]
23071async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23072    init_test(cx, |_| {});
23073    cx.update(|cx| {
23074        let mut editor_settings = EditorSettings::get_global(cx).clone();
23075        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23076        EditorSettings::override_global(editor_settings, cx);
23077    });
23078    let mut cx = EditorLspTestContext::new_rust(
23079        lsp::ServerCapabilities {
23080            definition_provider: Some(lsp::OneOf::Left(true)),
23081            references_provider: Some(lsp::OneOf::Left(true)),
23082            ..lsp::ServerCapabilities::default()
23083        },
23084        cx,
23085    )
23086    .await;
23087    let original_state = r#"fn one() {
23088        let mut a = ˇtwo();
23089    }
23090
23091    fn two() {}"#
23092        .unindent();
23093    cx.set_state(&original_state);
23094
23095    let mut go_to_definition = cx
23096        .lsp
23097        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23098            move |_, _| async move { Ok(None) },
23099        );
23100    let _references = cx
23101        .lsp
23102        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23103            panic!("Should not call for references with no go to definition fallback")
23104        });
23105
23106    let navigated = cx
23107        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23108        .await
23109        .expect("Failed to navigate to lookup references");
23110    go_to_definition
23111        .next()
23112        .await
23113        .expect("Should have called the go_to_definition handler");
23114
23115    assert_eq!(
23116        navigated,
23117        Navigated::No,
23118        "Should have navigated to references as a fallback after empty GoToDefinition response"
23119    );
23120    cx.assert_editor_state(&original_state);
23121    let editors = cx.update_workspace(|workspace, _, cx| {
23122        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23123    });
23124    cx.update_editor(|_, _, _| {
23125        assert_eq!(
23126            editors.len(),
23127            1,
23128            "After unsuccessful fallback, no other editor should have been opened"
23129        );
23130    });
23131}
23132
23133#[gpui::test]
23134async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23135    init_test(cx, |_| {});
23136    let mut cx = EditorLspTestContext::new_rust(
23137        lsp::ServerCapabilities {
23138            references_provider: Some(lsp::OneOf::Left(true)),
23139            ..lsp::ServerCapabilities::default()
23140        },
23141        cx,
23142    )
23143    .await;
23144
23145    cx.set_state(
23146        &r#"
23147        fn one() {
23148            let mut a = two();
23149        }
23150
23151        fn ˇtwo() {}"#
23152            .unindent(),
23153    );
23154    cx.lsp
23155        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23156            Ok(Some(vec![
23157                lsp::Location {
23158                    uri: params.text_document_position.text_document.uri.clone(),
23159                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23160                },
23161                lsp::Location {
23162                    uri: params.text_document_position.text_document.uri,
23163                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23164                },
23165            ]))
23166        });
23167    let navigated = cx
23168        .update_editor(|editor, window, cx| {
23169            editor.find_all_references(&FindAllReferences::default(), window, cx)
23170        })
23171        .unwrap()
23172        .await
23173        .expect("Failed to navigate to references");
23174    assert_eq!(
23175        navigated,
23176        Navigated::Yes,
23177        "Should have navigated to references from the FindAllReferences response"
23178    );
23179    cx.assert_editor_state(
23180        &r#"fn one() {
23181            let mut a = two();
23182        }
23183
23184        fn ˇtwo() {}"#
23185            .unindent(),
23186    );
23187
23188    let editors = cx.update_workspace(|workspace, _, cx| {
23189        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23190    });
23191    cx.update_editor(|_, _, _| {
23192        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23193    });
23194
23195    cx.set_state(
23196        &r#"fn one() {
23197            let mut a = ˇtwo();
23198        }
23199
23200        fn two() {}"#
23201            .unindent(),
23202    );
23203    let navigated = cx
23204        .update_editor(|editor, window, cx| {
23205            editor.find_all_references(&FindAllReferences::default(), window, cx)
23206        })
23207        .unwrap()
23208        .await
23209        .expect("Failed to navigate to references");
23210    assert_eq!(
23211        navigated,
23212        Navigated::Yes,
23213        "Should have navigated to references from the FindAllReferences response"
23214    );
23215    cx.assert_editor_state(
23216        &r#"fn one() {
23217            let mut a = ˇtwo();
23218        }
23219
23220        fn two() {}"#
23221            .unindent(),
23222    );
23223    let editors = cx.update_workspace(|workspace, _, cx| {
23224        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23225    });
23226    cx.update_editor(|_, _, _| {
23227        assert_eq!(
23228            editors.len(),
23229            2,
23230            "should have re-used the previous multibuffer"
23231        );
23232    });
23233
23234    cx.set_state(
23235        &r#"fn one() {
23236            let mut a = ˇtwo();
23237        }
23238        fn three() {}
23239        fn two() {}"#
23240            .unindent(),
23241    );
23242    cx.lsp
23243        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23244            Ok(Some(vec![
23245                lsp::Location {
23246                    uri: params.text_document_position.text_document.uri.clone(),
23247                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23248                },
23249                lsp::Location {
23250                    uri: params.text_document_position.text_document.uri,
23251                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23252                },
23253            ]))
23254        });
23255    let navigated = cx
23256        .update_editor(|editor, window, cx| {
23257            editor.find_all_references(&FindAllReferences::default(), window, cx)
23258        })
23259        .unwrap()
23260        .await
23261        .expect("Failed to navigate to references");
23262    assert_eq!(
23263        navigated,
23264        Navigated::Yes,
23265        "Should have navigated to references from the FindAllReferences response"
23266    );
23267    cx.assert_editor_state(
23268        &r#"fn one() {
23269                let mut a = ˇtwo();
23270            }
23271            fn three() {}
23272            fn two() {}"#
23273            .unindent(),
23274    );
23275    let editors = cx.update_workspace(|workspace, _, cx| {
23276        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23277    });
23278    cx.update_editor(|_, _, _| {
23279        assert_eq!(
23280            editors.len(),
23281            3,
23282            "should have used a new multibuffer as offsets changed"
23283        );
23284    });
23285}
23286#[gpui::test]
23287async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23288    init_test(cx, |_| {});
23289
23290    let language = Arc::new(Language::new(
23291        LanguageConfig::default(),
23292        Some(tree_sitter_rust::LANGUAGE.into()),
23293    ));
23294
23295    let text = r#"
23296        #[cfg(test)]
23297        mod tests() {
23298            #[test]
23299            fn runnable_1() {
23300                let a = 1;
23301            }
23302
23303            #[test]
23304            fn runnable_2() {
23305                let a = 1;
23306                let b = 2;
23307            }
23308        }
23309    "#
23310    .unindent();
23311
23312    let fs = FakeFs::new(cx.executor());
23313    fs.insert_file("/file.rs", Default::default()).await;
23314
23315    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23316    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23317    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23318    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23319    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23320
23321    let editor = cx.new_window_entity(|window, cx| {
23322        Editor::new(
23323            EditorMode::full(),
23324            multi_buffer,
23325            Some(project.clone()),
23326            window,
23327            cx,
23328        )
23329    });
23330
23331    editor.update_in(cx, |editor, window, cx| {
23332        let snapshot = editor.buffer().read(cx).snapshot(cx);
23333        editor.tasks.insert(
23334            (buffer.read(cx).remote_id(), 3),
23335            RunnableTasks {
23336                templates: vec![],
23337                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23338                column: 0,
23339                extra_variables: HashMap::default(),
23340                context_range: BufferOffset(43)..BufferOffset(85),
23341            },
23342        );
23343        editor.tasks.insert(
23344            (buffer.read(cx).remote_id(), 8),
23345            RunnableTasks {
23346                templates: vec![],
23347                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23348                column: 0,
23349                extra_variables: HashMap::default(),
23350                context_range: BufferOffset(86)..BufferOffset(191),
23351            },
23352        );
23353
23354        // Test finding task when cursor is inside function body
23355        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23356            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23357        });
23358        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23359        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23360
23361        // Test finding task when cursor is on function name
23362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23363            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23364        });
23365        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23366        assert_eq!(row, 8, "Should find task when cursor is on function name");
23367    });
23368}
23369
23370#[gpui::test]
23371async fn test_folding_buffers(cx: &mut TestAppContext) {
23372    init_test(cx, |_| {});
23373
23374    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23375    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23376    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23377
23378    let fs = FakeFs::new(cx.executor());
23379    fs.insert_tree(
23380        path!("/a"),
23381        json!({
23382            "first.rs": sample_text_1,
23383            "second.rs": sample_text_2,
23384            "third.rs": sample_text_3,
23385        }),
23386    )
23387    .await;
23388    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23389    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23390    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23391    let worktree = project.update(cx, |project, cx| {
23392        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23393        assert_eq!(worktrees.len(), 1);
23394        worktrees.pop().unwrap()
23395    });
23396    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23397
23398    let buffer_1 = project
23399        .update(cx, |project, cx| {
23400            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23401        })
23402        .await
23403        .unwrap();
23404    let buffer_2 = project
23405        .update(cx, |project, cx| {
23406            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23407        })
23408        .await
23409        .unwrap();
23410    let buffer_3 = project
23411        .update(cx, |project, cx| {
23412            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23413        })
23414        .await
23415        .unwrap();
23416
23417    let multi_buffer = cx.new(|cx| {
23418        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23419        multi_buffer.push_excerpts(
23420            buffer_1.clone(),
23421            [
23422                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23423                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23424                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23425            ],
23426            cx,
23427        );
23428        multi_buffer.push_excerpts(
23429            buffer_2.clone(),
23430            [
23431                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23432                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23433                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23434            ],
23435            cx,
23436        );
23437        multi_buffer.push_excerpts(
23438            buffer_3.clone(),
23439            [
23440                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23441                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23442                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23443            ],
23444            cx,
23445        );
23446        multi_buffer
23447    });
23448    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23449        Editor::new(
23450            EditorMode::full(),
23451            multi_buffer.clone(),
23452            Some(project.clone()),
23453            window,
23454            cx,
23455        )
23456    });
23457
23458    assert_eq!(
23459        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23460        "\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",
23461    );
23462
23463    multi_buffer_editor.update(cx, |editor, cx| {
23464        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23465    });
23466    assert_eq!(
23467        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23468        "\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",
23469        "After folding the first buffer, its text should not be displayed"
23470    );
23471
23472    multi_buffer_editor.update(cx, |editor, cx| {
23473        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23474    });
23475    assert_eq!(
23476        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23477        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23478        "After folding the second buffer, its text should not be displayed"
23479    );
23480
23481    multi_buffer_editor.update(cx, |editor, cx| {
23482        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23483    });
23484    assert_eq!(
23485        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23486        "\n\n\n\n\n",
23487        "After folding the third buffer, its text should not be displayed"
23488    );
23489
23490    // Emulate selection inside the fold logic, that should work
23491    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23492        editor
23493            .snapshot(window, cx)
23494            .next_line_boundary(Point::new(0, 4));
23495    });
23496
23497    multi_buffer_editor.update(cx, |editor, cx| {
23498        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23499    });
23500    assert_eq!(
23501        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23502        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23503        "After unfolding the second buffer, its text should be displayed"
23504    );
23505
23506    // Typing inside of buffer 1 causes that buffer to be unfolded.
23507    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23508        assert_eq!(
23509            multi_buffer
23510                .read(cx)
23511                .snapshot(cx)
23512                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23513                .collect::<String>(),
23514            "bbbb"
23515        );
23516        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23517            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23518        });
23519        editor.handle_input("B", window, cx);
23520    });
23521
23522    assert_eq!(
23523        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23524        "\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",
23525        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23526    );
23527
23528    multi_buffer_editor.update(cx, |editor, cx| {
23529        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23530    });
23531    assert_eq!(
23532        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23533        "\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",
23534        "After unfolding the all buffers, all original text should be displayed"
23535    );
23536}
23537
23538#[gpui::test]
23539async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23540    init_test(cx, |_| {});
23541
23542    let sample_text_1 = "1111\n2222\n3333".to_string();
23543    let sample_text_2 = "4444\n5555\n6666".to_string();
23544    let sample_text_3 = "7777\n8888\n9999".to_string();
23545
23546    let fs = FakeFs::new(cx.executor());
23547    fs.insert_tree(
23548        path!("/a"),
23549        json!({
23550            "first.rs": sample_text_1,
23551            "second.rs": sample_text_2,
23552            "third.rs": sample_text_3,
23553        }),
23554    )
23555    .await;
23556    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23557    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23558    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23559    let worktree = project.update(cx, |project, cx| {
23560        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23561        assert_eq!(worktrees.len(), 1);
23562        worktrees.pop().unwrap()
23563    });
23564    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23565
23566    let buffer_1 = project
23567        .update(cx, |project, cx| {
23568            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23569        })
23570        .await
23571        .unwrap();
23572    let buffer_2 = project
23573        .update(cx, |project, cx| {
23574            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23575        })
23576        .await
23577        .unwrap();
23578    let buffer_3 = project
23579        .update(cx, |project, cx| {
23580            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23581        })
23582        .await
23583        .unwrap();
23584
23585    let multi_buffer = cx.new(|cx| {
23586        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23587        multi_buffer.push_excerpts(
23588            buffer_1.clone(),
23589            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23590            cx,
23591        );
23592        multi_buffer.push_excerpts(
23593            buffer_2.clone(),
23594            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23595            cx,
23596        );
23597        multi_buffer.push_excerpts(
23598            buffer_3.clone(),
23599            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23600            cx,
23601        );
23602        multi_buffer
23603    });
23604
23605    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23606        Editor::new(
23607            EditorMode::full(),
23608            multi_buffer,
23609            Some(project.clone()),
23610            window,
23611            cx,
23612        )
23613    });
23614
23615    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23616    assert_eq!(
23617        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23618        full_text,
23619    );
23620
23621    multi_buffer_editor.update(cx, |editor, cx| {
23622        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23623    });
23624    assert_eq!(
23625        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23626        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23627        "After folding the first buffer, its text should not be displayed"
23628    );
23629
23630    multi_buffer_editor.update(cx, |editor, cx| {
23631        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23632    });
23633
23634    assert_eq!(
23635        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23636        "\n\n\n\n\n\n7777\n8888\n9999",
23637        "After folding the second buffer, its text should not be displayed"
23638    );
23639
23640    multi_buffer_editor.update(cx, |editor, cx| {
23641        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23642    });
23643    assert_eq!(
23644        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23645        "\n\n\n\n\n",
23646        "After folding the third buffer, its text should not be displayed"
23647    );
23648
23649    multi_buffer_editor.update(cx, |editor, cx| {
23650        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23651    });
23652    assert_eq!(
23653        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23654        "\n\n\n\n4444\n5555\n6666\n\n",
23655        "After unfolding the second buffer, its text should be displayed"
23656    );
23657
23658    multi_buffer_editor.update(cx, |editor, cx| {
23659        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23660    });
23661    assert_eq!(
23662        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23663        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23664        "After unfolding the first buffer, its text should be displayed"
23665    );
23666
23667    multi_buffer_editor.update(cx, |editor, cx| {
23668        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23669    });
23670    assert_eq!(
23671        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23672        full_text,
23673        "After unfolding all buffers, all original text should be displayed"
23674    );
23675}
23676
23677#[gpui::test]
23678async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23679    init_test(cx, |_| {});
23680
23681    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23682
23683    let fs = FakeFs::new(cx.executor());
23684    fs.insert_tree(
23685        path!("/a"),
23686        json!({
23687            "main.rs": sample_text,
23688        }),
23689    )
23690    .await;
23691    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23692    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23693    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23694    let worktree = project.update(cx, |project, cx| {
23695        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23696        assert_eq!(worktrees.len(), 1);
23697        worktrees.pop().unwrap()
23698    });
23699    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23700
23701    let buffer_1 = project
23702        .update(cx, |project, cx| {
23703            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23704        })
23705        .await
23706        .unwrap();
23707
23708    let multi_buffer = cx.new(|cx| {
23709        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23710        multi_buffer.push_excerpts(
23711            buffer_1.clone(),
23712            [ExcerptRange::new(
23713                Point::new(0, 0)
23714                    ..Point::new(
23715                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23716                        0,
23717                    ),
23718            )],
23719            cx,
23720        );
23721        multi_buffer
23722    });
23723    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23724        Editor::new(
23725            EditorMode::full(),
23726            multi_buffer,
23727            Some(project.clone()),
23728            window,
23729            cx,
23730        )
23731    });
23732
23733    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23734    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23735        enum TestHighlight {}
23736        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23737        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23738        editor.highlight_text::<TestHighlight>(
23739            vec![highlight_range.clone()],
23740            HighlightStyle::color(Hsla::green()),
23741            cx,
23742        );
23743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23744            s.select_ranges(Some(highlight_range))
23745        });
23746    });
23747
23748    let full_text = format!("\n\n{sample_text}");
23749    assert_eq!(
23750        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23751        full_text,
23752    );
23753}
23754
23755#[gpui::test]
23756async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23757    init_test(cx, |_| {});
23758    cx.update(|cx| {
23759        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23760            "keymaps/default-linux.json",
23761            cx,
23762        )
23763        .unwrap();
23764        cx.bind_keys(default_key_bindings);
23765    });
23766
23767    let (editor, cx) = cx.add_window_view(|window, cx| {
23768        let multi_buffer = MultiBuffer::build_multi(
23769            [
23770                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23771                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23772                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23773                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23774            ],
23775            cx,
23776        );
23777        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23778
23779        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23780        // fold all but the second buffer, so that we test navigating between two
23781        // adjacent folded buffers, as well as folded buffers at the start and
23782        // end the multibuffer
23783        editor.fold_buffer(buffer_ids[0], cx);
23784        editor.fold_buffer(buffer_ids[2], cx);
23785        editor.fold_buffer(buffer_ids[3], cx);
23786
23787        editor
23788    });
23789    cx.simulate_resize(size(px(1000.), px(1000.)));
23790
23791    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23792    cx.assert_excerpts_with_selections(indoc! {"
23793        [EXCERPT]
23794        ˇ[FOLDED]
23795        [EXCERPT]
23796        a1
23797        b1
23798        [EXCERPT]
23799        [FOLDED]
23800        [EXCERPT]
23801        [FOLDED]
23802        "
23803    });
23804    cx.simulate_keystroke("down");
23805    cx.assert_excerpts_with_selections(indoc! {"
23806        [EXCERPT]
23807        [FOLDED]
23808        [EXCERPT]
23809        ˇa1
23810        b1
23811        [EXCERPT]
23812        [FOLDED]
23813        [EXCERPT]
23814        [FOLDED]
23815        "
23816    });
23817    cx.simulate_keystroke("down");
23818    cx.assert_excerpts_with_selections(indoc! {"
23819        [EXCERPT]
23820        [FOLDED]
23821        [EXCERPT]
23822        a1
23823        ˇb1
23824        [EXCERPT]
23825        [FOLDED]
23826        [EXCERPT]
23827        [FOLDED]
23828        "
23829    });
23830    cx.simulate_keystroke("down");
23831    cx.assert_excerpts_with_selections(indoc! {"
23832        [EXCERPT]
23833        [FOLDED]
23834        [EXCERPT]
23835        a1
23836        b1
23837        ˇ[EXCERPT]
23838        [FOLDED]
23839        [EXCERPT]
23840        [FOLDED]
23841        "
23842    });
23843    cx.simulate_keystroke("down");
23844    cx.assert_excerpts_with_selections(indoc! {"
23845        [EXCERPT]
23846        [FOLDED]
23847        [EXCERPT]
23848        a1
23849        b1
23850        [EXCERPT]
23851        ˇ[FOLDED]
23852        [EXCERPT]
23853        [FOLDED]
23854        "
23855    });
23856    for _ in 0..5 {
23857        cx.simulate_keystroke("down");
23858        cx.assert_excerpts_with_selections(indoc! {"
23859            [EXCERPT]
23860            [FOLDED]
23861            [EXCERPT]
23862            a1
23863            b1
23864            [EXCERPT]
23865            [FOLDED]
23866            [EXCERPT]
23867            ˇ[FOLDED]
23868            "
23869        });
23870    }
23871
23872    cx.simulate_keystroke("up");
23873    cx.assert_excerpts_with_selections(indoc! {"
23874        [EXCERPT]
23875        [FOLDED]
23876        [EXCERPT]
23877        a1
23878        b1
23879        [EXCERPT]
23880        ˇ[FOLDED]
23881        [EXCERPT]
23882        [FOLDED]
23883        "
23884    });
23885    cx.simulate_keystroke("up");
23886    cx.assert_excerpts_with_selections(indoc! {"
23887        [EXCERPT]
23888        [FOLDED]
23889        [EXCERPT]
23890        a1
23891        b1
23892        ˇ[EXCERPT]
23893        [FOLDED]
23894        [EXCERPT]
23895        [FOLDED]
23896        "
23897    });
23898    cx.simulate_keystroke("up");
23899    cx.assert_excerpts_with_selections(indoc! {"
23900        [EXCERPT]
23901        [FOLDED]
23902        [EXCERPT]
23903        a1
23904        ˇb1
23905        [EXCERPT]
23906        [FOLDED]
23907        [EXCERPT]
23908        [FOLDED]
23909        "
23910    });
23911    cx.simulate_keystroke("up");
23912    cx.assert_excerpts_with_selections(indoc! {"
23913        [EXCERPT]
23914        [FOLDED]
23915        [EXCERPT]
23916        ˇa1
23917        b1
23918        [EXCERPT]
23919        [FOLDED]
23920        [EXCERPT]
23921        [FOLDED]
23922        "
23923    });
23924    for _ in 0..5 {
23925        cx.simulate_keystroke("up");
23926        cx.assert_excerpts_with_selections(indoc! {"
23927            [EXCERPT]
23928            ˇ[FOLDED]
23929            [EXCERPT]
23930            a1
23931            b1
23932            [EXCERPT]
23933            [FOLDED]
23934            [EXCERPT]
23935            [FOLDED]
23936            "
23937        });
23938    }
23939}
23940
23941#[gpui::test]
23942async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23943    init_test(cx, |_| {});
23944
23945    // Simple insertion
23946    assert_highlighted_edits(
23947        "Hello, world!",
23948        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23949        true,
23950        cx,
23951        |highlighted_edits, cx| {
23952            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23953            assert_eq!(highlighted_edits.highlights.len(), 1);
23954            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23955            assert_eq!(
23956                highlighted_edits.highlights[0].1.background_color,
23957                Some(cx.theme().status().created_background)
23958            );
23959        },
23960    )
23961    .await;
23962
23963    // Replacement
23964    assert_highlighted_edits(
23965        "This is a test.",
23966        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23967        false,
23968        cx,
23969        |highlighted_edits, cx| {
23970            assert_eq!(highlighted_edits.text, "That is a test.");
23971            assert_eq!(highlighted_edits.highlights.len(), 1);
23972            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23973            assert_eq!(
23974                highlighted_edits.highlights[0].1.background_color,
23975                Some(cx.theme().status().created_background)
23976            );
23977        },
23978    )
23979    .await;
23980
23981    // Multiple edits
23982    assert_highlighted_edits(
23983        "Hello, world!",
23984        vec![
23985            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23986            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23987        ],
23988        false,
23989        cx,
23990        |highlighted_edits, cx| {
23991            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23992            assert_eq!(highlighted_edits.highlights.len(), 2);
23993            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23994            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23995            assert_eq!(
23996                highlighted_edits.highlights[0].1.background_color,
23997                Some(cx.theme().status().created_background)
23998            );
23999            assert_eq!(
24000                highlighted_edits.highlights[1].1.background_color,
24001                Some(cx.theme().status().created_background)
24002            );
24003        },
24004    )
24005    .await;
24006
24007    // Multiple lines with edits
24008    assert_highlighted_edits(
24009        "First line\nSecond line\nThird line\nFourth line",
24010        vec![
24011            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24012            (
24013                Point::new(2, 0)..Point::new(2, 10),
24014                "New third line".to_string(),
24015            ),
24016            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24017        ],
24018        false,
24019        cx,
24020        |highlighted_edits, cx| {
24021            assert_eq!(
24022                highlighted_edits.text,
24023                "Second modified\nNew third line\nFourth updated line"
24024            );
24025            assert_eq!(highlighted_edits.highlights.len(), 3);
24026            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24027            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24028            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24029            for highlight in &highlighted_edits.highlights {
24030                assert_eq!(
24031                    highlight.1.background_color,
24032                    Some(cx.theme().status().created_background)
24033                );
24034            }
24035        },
24036    )
24037    .await;
24038}
24039
24040#[gpui::test]
24041async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24042    init_test(cx, |_| {});
24043
24044    // Deletion
24045    assert_highlighted_edits(
24046        "Hello, world!",
24047        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24048        true,
24049        cx,
24050        |highlighted_edits, cx| {
24051            assert_eq!(highlighted_edits.text, "Hello, world!");
24052            assert_eq!(highlighted_edits.highlights.len(), 1);
24053            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24054            assert_eq!(
24055                highlighted_edits.highlights[0].1.background_color,
24056                Some(cx.theme().status().deleted_background)
24057            );
24058        },
24059    )
24060    .await;
24061
24062    // Insertion
24063    assert_highlighted_edits(
24064        "Hello, world!",
24065        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24066        true,
24067        cx,
24068        |highlighted_edits, cx| {
24069            assert_eq!(highlighted_edits.highlights.len(), 1);
24070            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24071            assert_eq!(
24072                highlighted_edits.highlights[0].1.background_color,
24073                Some(cx.theme().status().created_background)
24074            );
24075        },
24076    )
24077    .await;
24078}
24079
24080async fn assert_highlighted_edits(
24081    text: &str,
24082    edits: Vec<(Range<Point>, String)>,
24083    include_deletions: bool,
24084    cx: &mut TestAppContext,
24085    assertion_fn: impl Fn(HighlightedText, &App),
24086) {
24087    let window = cx.add_window(|window, cx| {
24088        let buffer = MultiBuffer::build_simple(text, cx);
24089        Editor::new(EditorMode::full(), buffer, None, window, cx)
24090    });
24091    let cx = &mut VisualTestContext::from_window(*window, cx);
24092
24093    let (buffer, snapshot) = window
24094        .update(cx, |editor, _window, cx| {
24095            (
24096                editor.buffer().clone(),
24097                editor.buffer().read(cx).snapshot(cx),
24098            )
24099        })
24100        .unwrap();
24101
24102    let edits = edits
24103        .into_iter()
24104        .map(|(range, edit)| {
24105            (
24106                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24107                edit,
24108            )
24109        })
24110        .collect::<Vec<_>>();
24111
24112    let text_anchor_edits = edits
24113        .clone()
24114        .into_iter()
24115        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24116        .collect::<Vec<_>>();
24117
24118    let edit_preview = window
24119        .update(cx, |_, _window, cx| {
24120            buffer
24121                .read(cx)
24122                .as_singleton()
24123                .unwrap()
24124                .read(cx)
24125                .preview_edits(text_anchor_edits.into(), cx)
24126        })
24127        .unwrap()
24128        .await;
24129
24130    cx.update(|_window, cx| {
24131        let highlighted_edits = edit_prediction_edit_text(
24132            snapshot.as_singleton().unwrap().2,
24133            &edits,
24134            &edit_preview,
24135            include_deletions,
24136            cx,
24137        );
24138        assertion_fn(highlighted_edits, cx)
24139    });
24140}
24141
24142#[track_caller]
24143fn assert_breakpoint(
24144    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24145    path: &Arc<Path>,
24146    expected: Vec<(u32, Breakpoint)>,
24147) {
24148    if expected.is_empty() {
24149        assert!(!breakpoints.contains_key(path), "{}", path.display());
24150    } else {
24151        let mut breakpoint = breakpoints
24152            .get(path)
24153            .unwrap()
24154            .iter()
24155            .map(|breakpoint| {
24156                (
24157                    breakpoint.row,
24158                    Breakpoint {
24159                        message: breakpoint.message.clone(),
24160                        state: breakpoint.state,
24161                        condition: breakpoint.condition.clone(),
24162                        hit_condition: breakpoint.hit_condition.clone(),
24163                    },
24164                )
24165            })
24166            .collect::<Vec<_>>();
24167
24168        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24169
24170        assert_eq!(expected, breakpoint);
24171    }
24172}
24173
24174fn add_log_breakpoint_at_cursor(
24175    editor: &mut Editor,
24176    log_message: &str,
24177    window: &mut Window,
24178    cx: &mut Context<Editor>,
24179) {
24180    let (anchor, bp) = editor
24181        .breakpoints_at_cursors(window, cx)
24182        .first()
24183        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24184        .unwrap_or_else(|| {
24185            let snapshot = editor.snapshot(window, cx);
24186            let cursor_position: Point =
24187                editor.selections.newest(&snapshot.display_snapshot).head();
24188
24189            let breakpoint_position = snapshot
24190                .buffer_snapshot()
24191                .anchor_before(Point::new(cursor_position.row, 0));
24192
24193            (breakpoint_position, Breakpoint::new_log(log_message))
24194        });
24195
24196    editor.edit_breakpoint_at_anchor(
24197        anchor,
24198        bp,
24199        BreakpointEditAction::EditLogMessage(log_message.into()),
24200        cx,
24201    );
24202}
24203
24204#[gpui::test]
24205async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24206    init_test(cx, |_| {});
24207
24208    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24209    let fs = FakeFs::new(cx.executor());
24210    fs.insert_tree(
24211        path!("/a"),
24212        json!({
24213            "main.rs": sample_text,
24214        }),
24215    )
24216    .await;
24217    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24218    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24219    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24220
24221    let fs = FakeFs::new(cx.executor());
24222    fs.insert_tree(
24223        path!("/a"),
24224        json!({
24225            "main.rs": sample_text,
24226        }),
24227    )
24228    .await;
24229    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24230    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24231    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24232    let worktree_id = workspace
24233        .update(cx, |workspace, _window, cx| {
24234            workspace.project().update(cx, |project, cx| {
24235                project.worktrees(cx).next().unwrap().read(cx).id()
24236            })
24237        })
24238        .unwrap();
24239
24240    let buffer = project
24241        .update(cx, |project, cx| {
24242            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24243        })
24244        .await
24245        .unwrap();
24246
24247    let (editor, cx) = cx.add_window_view(|window, cx| {
24248        Editor::new(
24249            EditorMode::full(),
24250            MultiBuffer::build_from_buffer(buffer, cx),
24251            Some(project.clone()),
24252            window,
24253            cx,
24254        )
24255    });
24256
24257    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24258    let abs_path = project.read_with(cx, |project, cx| {
24259        project
24260            .absolute_path(&project_path, cx)
24261            .map(Arc::from)
24262            .unwrap()
24263    });
24264
24265    // assert we can add breakpoint on the first line
24266    editor.update_in(cx, |editor, window, cx| {
24267        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24268        editor.move_to_end(&MoveToEnd, window, cx);
24269        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24270    });
24271
24272    let breakpoints = editor.update(cx, |editor, cx| {
24273        editor
24274            .breakpoint_store()
24275            .as_ref()
24276            .unwrap()
24277            .read(cx)
24278            .all_source_breakpoints(cx)
24279    });
24280
24281    assert_eq!(1, breakpoints.len());
24282    assert_breakpoint(
24283        &breakpoints,
24284        &abs_path,
24285        vec![
24286            (0, Breakpoint::new_standard()),
24287            (3, Breakpoint::new_standard()),
24288        ],
24289    );
24290
24291    editor.update_in(cx, |editor, window, cx| {
24292        editor.move_to_beginning(&MoveToBeginning, window, cx);
24293        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24294    });
24295
24296    let breakpoints = editor.update(cx, |editor, cx| {
24297        editor
24298            .breakpoint_store()
24299            .as_ref()
24300            .unwrap()
24301            .read(cx)
24302            .all_source_breakpoints(cx)
24303    });
24304
24305    assert_eq!(1, breakpoints.len());
24306    assert_breakpoint(
24307        &breakpoints,
24308        &abs_path,
24309        vec![(3, Breakpoint::new_standard())],
24310    );
24311
24312    editor.update_in(cx, |editor, window, cx| {
24313        editor.move_to_end(&MoveToEnd, window, cx);
24314        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24315    });
24316
24317    let breakpoints = editor.update(cx, |editor, cx| {
24318        editor
24319            .breakpoint_store()
24320            .as_ref()
24321            .unwrap()
24322            .read(cx)
24323            .all_source_breakpoints(cx)
24324    });
24325
24326    assert_eq!(0, breakpoints.len());
24327    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24328}
24329
24330#[gpui::test]
24331async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24332    init_test(cx, |_| {});
24333
24334    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24335
24336    let fs = FakeFs::new(cx.executor());
24337    fs.insert_tree(
24338        path!("/a"),
24339        json!({
24340            "main.rs": sample_text,
24341        }),
24342    )
24343    .await;
24344    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24345    let (workspace, cx) =
24346        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24347
24348    let worktree_id = workspace.update(cx, |workspace, cx| {
24349        workspace.project().update(cx, |project, cx| {
24350            project.worktrees(cx).next().unwrap().read(cx).id()
24351        })
24352    });
24353
24354    let buffer = project
24355        .update(cx, |project, cx| {
24356            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24357        })
24358        .await
24359        .unwrap();
24360
24361    let (editor, cx) = cx.add_window_view(|window, cx| {
24362        Editor::new(
24363            EditorMode::full(),
24364            MultiBuffer::build_from_buffer(buffer, cx),
24365            Some(project.clone()),
24366            window,
24367            cx,
24368        )
24369    });
24370
24371    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24372    let abs_path = project.read_with(cx, |project, cx| {
24373        project
24374            .absolute_path(&project_path, cx)
24375            .map(Arc::from)
24376            .unwrap()
24377    });
24378
24379    editor.update_in(cx, |editor, window, cx| {
24380        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24381    });
24382
24383    let breakpoints = editor.update(cx, |editor, cx| {
24384        editor
24385            .breakpoint_store()
24386            .as_ref()
24387            .unwrap()
24388            .read(cx)
24389            .all_source_breakpoints(cx)
24390    });
24391
24392    assert_breakpoint(
24393        &breakpoints,
24394        &abs_path,
24395        vec![(0, Breakpoint::new_log("hello world"))],
24396    );
24397
24398    // Removing a log message from a log breakpoint should remove it
24399    editor.update_in(cx, |editor, window, cx| {
24400        add_log_breakpoint_at_cursor(editor, "", window, cx);
24401    });
24402
24403    let breakpoints = editor.update(cx, |editor, cx| {
24404        editor
24405            .breakpoint_store()
24406            .as_ref()
24407            .unwrap()
24408            .read(cx)
24409            .all_source_breakpoints(cx)
24410    });
24411
24412    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24413
24414    editor.update_in(cx, |editor, window, cx| {
24415        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24416        editor.move_to_end(&MoveToEnd, window, cx);
24417        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24418        // Not adding a log message to a standard breakpoint shouldn't remove it
24419        add_log_breakpoint_at_cursor(editor, "", window, cx);
24420    });
24421
24422    let breakpoints = editor.update(cx, |editor, cx| {
24423        editor
24424            .breakpoint_store()
24425            .as_ref()
24426            .unwrap()
24427            .read(cx)
24428            .all_source_breakpoints(cx)
24429    });
24430
24431    assert_breakpoint(
24432        &breakpoints,
24433        &abs_path,
24434        vec![
24435            (0, Breakpoint::new_standard()),
24436            (3, Breakpoint::new_standard()),
24437        ],
24438    );
24439
24440    editor.update_in(cx, |editor, window, cx| {
24441        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24442    });
24443
24444    let breakpoints = editor.update(cx, |editor, cx| {
24445        editor
24446            .breakpoint_store()
24447            .as_ref()
24448            .unwrap()
24449            .read(cx)
24450            .all_source_breakpoints(cx)
24451    });
24452
24453    assert_breakpoint(
24454        &breakpoints,
24455        &abs_path,
24456        vec![
24457            (0, Breakpoint::new_standard()),
24458            (3, Breakpoint::new_log("hello world")),
24459        ],
24460    );
24461
24462    editor.update_in(cx, |editor, window, cx| {
24463        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24464    });
24465
24466    let breakpoints = editor.update(cx, |editor, cx| {
24467        editor
24468            .breakpoint_store()
24469            .as_ref()
24470            .unwrap()
24471            .read(cx)
24472            .all_source_breakpoints(cx)
24473    });
24474
24475    assert_breakpoint(
24476        &breakpoints,
24477        &abs_path,
24478        vec![
24479            (0, Breakpoint::new_standard()),
24480            (3, Breakpoint::new_log("hello Earth!!")),
24481        ],
24482    );
24483}
24484
24485/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24486/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24487/// or when breakpoints were placed out of order. This tests for a regression too
24488#[gpui::test]
24489async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24490    init_test(cx, |_| {});
24491
24492    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24493    let fs = FakeFs::new(cx.executor());
24494    fs.insert_tree(
24495        path!("/a"),
24496        json!({
24497            "main.rs": sample_text,
24498        }),
24499    )
24500    .await;
24501    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24502    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24503    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24504
24505    let fs = FakeFs::new(cx.executor());
24506    fs.insert_tree(
24507        path!("/a"),
24508        json!({
24509            "main.rs": sample_text,
24510        }),
24511    )
24512    .await;
24513    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24514    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24515    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24516    let worktree_id = workspace
24517        .update(cx, |workspace, _window, cx| {
24518            workspace.project().update(cx, |project, cx| {
24519                project.worktrees(cx).next().unwrap().read(cx).id()
24520            })
24521        })
24522        .unwrap();
24523
24524    let buffer = project
24525        .update(cx, |project, cx| {
24526            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24527        })
24528        .await
24529        .unwrap();
24530
24531    let (editor, cx) = cx.add_window_view(|window, cx| {
24532        Editor::new(
24533            EditorMode::full(),
24534            MultiBuffer::build_from_buffer(buffer, cx),
24535            Some(project.clone()),
24536            window,
24537            cx,
24538        )
24539    });
24540
24541    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24542    let abs_path = project.read_with(cx, |project, cx| {
24543        project
24544            .absolute_path(&project_path, cx)
24545            .map(Arc::from)
24546            .unwrap()
24547    });
24548
24549    // assert we can add breakpoint on the first line
24550    editor.update_in(cx, |editor, window, cx| {
24551        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24552        editor.move_to_end(&MoveToEnd, window, cx);
24553        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24554        editor.move_up(&MoveUp, window, cx);
24555        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24556    });
24557
24558    let breakpoints = editor.update(cx, |editor, cx| {
24559        editor
24560            .breakpoint_store()
24561            .as_ref()
24562            .unwrap()
24563            .read(cx)
24564            .all_source_breakpoints(cx)
24565    });
24566
24567    assert_eq!(1, breakpoints.len());
24568    assert_breakpoint(
24569        &breakpoints,
24570        &abs_path,
24571        vec![
24572            (0, Breakpoint::new_standard()),
24573            (2, Breakpoint::new_standard()),
24574            (3, Breakpoint::new_standard()),
24575        ],
24576    );
24577
24578    editor.update_in(cx, |editor, window, cx| {
24579        editor.move_to_beginning(&MoveToBeginning, window, cx);
24580        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24581        editor.move_to_end(&MoveToEnd, window, cx);
24582        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24583        // Disabling a breakpoint that doesn't exist should do nothing
24584        editor.move_up(&MoveUp, window, cx);
24585        editor.move_up(&MoveUp, window, cx);
24586        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24587    });
24588
24589    let breakpoints = editor.update(cx, |editor, cx| {
24590        editor
24591            .breakpoint_store()
24592            .as_ref()
24593            .unwrap()
24594            .read(cx)
24595            .all_source_breakpoints(cx)
24596    });
24597
24598    let disable_breakpoint = {
24599        let mut bp = Breakpoint::new_standard();
24600        bp.state = BreakpointState::Disabled;
24601        bp
24602    };
24603
24604    assert_eq!(1, breakpoints.len());
24605    assert_breakpoint(
24606        &breakpoints,
24607        &abs_path,
24608        vec![
24609            (0, disable_breakpoint.clone()),
24610            (2, Breakpoint::new_standard()),
24611            (3, disable_breakpoint.clone()),
24612        ],
24613    );
24614
24615    editor.update_in(cx, |editor, window, cx| {
24616        editor.move_to_beginning(&MoveToBeginning, window, cx);
24617        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24618        editor.move_to_end(&MoveToEnd, window, cx);
24619        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24620        editor.move_up(&MoveUp, window, cx);
24621        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24622    });
24623
24624    let breakpoints = editor.update(cx, |editor, cx| {
24625        editor
24626            .breakpoint_store()
24627            .as_ref()
24628            .unwrap()
24629            .read(cx)
24630            .all_source_breakpoints(cx)
24631    });
24632
24633    assert_eq!(1, breakpoints.len());
24634    assert_breakpoint(
24635        &breakpoints,
24636        &abs_path,
24637        vec![
24638            (0, Breakpoint::new_standard()),
24639            (2, disable_breakpoint),
24640            (3, Breakpoint::new_standard()),
24641        ],
24642    );
24643}
24644
24645#[gpui::test]
24646async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24647    init_test(cx, |_| {});
24648    let capabilities = lsp::ServerCapabilities {
24649        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24650            prepare_provider: Some(true),
24651            work_done_progress_options: Default::default(),
24652        })),
24653        ..Default::default()
24654    };
24655    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24656
24657    cx.set_state(indoc! {"
24658        struct Fˇoo {}
24659    "});
24660
24661    cx.update_editor(|editor, _, cx| {
24662        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24663        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24664        editor.highlight_background::<DocumentHighlightRead>(
24665            &[highlight_range],
24666            |_, theme| theme.colors().editor_document_highlight_read_background,
24667            cx,
24668        );
24669    });
24670
24671    let mut prepare_rename_handler = cx
24672        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24673            move |_, _, _| async move {
24674                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24675                    start: lsp::Position {
24676                        line: 0,
24677                        character: 7,
24678                    },
24679                    end: lsp::Position {
24680                        line: 0,
24681                        character: 10,
24682                    },
24683                })))
24684            },
24685        );
24686    let prepare_rename_task = cx
24687        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24688        .expect("Prepare rename was not started");
24689    prepare_rename_handler.next().await.unwrap();
24690    prepare_rename_task.await.expect("Prepare rename failed");
24691
24692    let mut rename_handler =
24693        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24694            let edit = lsp::TextEdit {
24695                range: lsp::Range {
24696                    start: lsp::Position {
24697                        line: 0,
24698                        character: 7,
24699                    },
24700                    end: lsp::Position {
24701                        line: 0,
24702                        character: 10,
24703                    },
24704                },
24705                new_text: "FooRenamed".to_string(),
24706            };
24707            Ok(Some(lsp::WorkspaceEdit::new(
24708                // Specify the same edit twice
24709                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24710            )))
24711        });
24712    let rename_task = cx
24713        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24714        .expect("Confirm rename was not started");
24715    rename_handler.next().await.unwrap();
24716    rename_task.await.expect("Confirm rename failed");
24717    cx.run_until_parked();
24718
24719    // Despite two edits, only one is actually applied as those are identical
24720    cx.assert_editor_state(indoc! {"
24721        struct FooRenamedˇ {}
24722    "});
24723}
24724
24725#[gpui::test]
24726async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24727    init_test(cx, |_| {});
24728    // These capabilities indicate that the server does not support prepare rename.
24729    let capabilities = lsp::ServerCapabilities {
24730        rename_provider: Some(lsp::OneOf::Left(true)),
24731        ..Default::default()
24732    };
24733    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24734
24735    cx.set_state(indoc! {"
24736        struct Fˇoo {}
24737    "});
24738
24739    cx.update_editor(|editor, _window, cx| {
24740        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24741        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24742        editor.highlight_background::<DocumentHighlightRead>(
24743            &[highlight_range],
24744            |_, theme| theme.colors().editor_document_highlight_read_background,
24745            cx,
24746        );
24747    });
24748
24749    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24750        .expect("Prepare rename was not started")
24751        .await
24752        .expect("Prepare rename failed");
24753
24754    let mut rename_handler =
24755        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24756            let edit = lsp::TextEdit {
24757                range: lsp::Range {
24758                    start: lsp::Position {
24759                        line: 0,
24760                        character: 7,
24761                    },
24762                    end: lsp::Position {
24763                        line: 0,
24764                        character: 10,
24765                    },
24766                },
24767                new_text: "FooRenamed".to_string(),
24768            };
24769            Ok(Some(lsp::WorkspaceEdit::new(
24770                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24771            )))
24772        });
24773    let rename_task = cx
24774        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24775        .expect("Confirm rename was not started");
24776    rename_handler.next().await.unwrap();
24777    rename_task.await.expect("Confirm rename failed");
24778    cx.run_until_parked();
24779
24780    // Correct range is renamed, as `surrounding_word` is used to find it.
24781    cx.assert_editor_state(indoc! {"
24782        struct FooRenamedˇ {}
24783    "});
24784}
24785
24786#[gpui::test]
24787async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24788    init_test(cx, |_| {});
24789    let mut cx = EditorTestContext::new(cx).await;
24790
24791    let language = Arc::new(
24792        Language::new(
24793            LanguageConfig::default(),
24794            Some(tree_sitter_html::LANGUAGE.into()),
24795        )
24796        .with_brackets_query(
24797            r#"
24798            ("<" @open "/>" @close)
24799            ("</" @open ">" @close)
24800            ("<" @open ">" @close)
24801            ("\"" @open "\"" @close)
24802            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24803        "#,
24804        )
24805        .unwrap(),
24806    );
24807    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24808
24809    cx.set_state(indoc! {"
24810        <span>ˇ</span>
24811    "});
24812    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24813    cx.assert_editor_state(indoc! {"
24814        <span>
24815        ˇ
24816        </span>
24817    "});
24818
24819    cx.set_state(indoc! {"
24820        <span><span></span>ˇ</span>
24821    "});
24822    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24823    cx.assert_editor_state(indoc! {"
24824        <span><span></span>
24825        ˇ</span>
24826    "});
24827
24828    cx.set_state(indoc! {"
24829        <span>ˇ
24830        </span>
24831    "});
24832    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24833    cx.assert_editor_state(indoc! {"
24834        <span>
24835        ˇ
24836        </span>
24837    "});
24838}
24839
24840#[gpui::test(iterations = 10)]
24841async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24842    init_test(cx, |_| {});
24843
24844    let fs = FakeFs::new(cx.executor());
24845    fs.insert_tree(
24846        path!("/dir"),
24847        json!({
24848            "a.ts": "a",
24849        }),
24850    )
24851    .await;
24852
24853    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24854    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24855    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24856
24857    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24858    language_registry.add(Arc::new(Language::new(
24859        LanguageConfig {
24860            name: "TypeScript".into(),
24861            matcher: LanguageMatcher {
24862                path_suffixes: vec!["ts".to_string()],
24863                ..Default::default()
24864            },
24865            ..Default::default()
24866        },
24867        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24868    )));
24869    let mut fake_language_servers = language_registry.register_fake_lsp(
24870        "TypeScript",
24871        FakeLspAdapter {
24872            capabilities: lsp::ServerCapabilities {
24873                code_lens_provider: Some(lsp::CodeLensOptions {
24874                    resolve_provider: Some(true),
24875                }),
24876                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24877                    commands: vec!["_the/command".to_string()],
24878                    ..lsp::ExecuteCommandOptions::default()
24879                }),
24880                ..lsp::ServerCapabilities::default()
24881            },
24882            ..FakeLspAdapter::default()
24883        },
24884    );
24885
24886    let editor = workspace
24887        .update(cx, |workspace, window, cx| {
24888            workspace.open_abs_path(
24889                PathBuf::from(path!("/dir/a.ts")),
24890                OpenOptions::default(),
24891                window,
24892                cx,
24893            )
24894        })
24895        .unwrap()
24896        .await
24897        .unwrap()
24898        .downcast::<Editor>()
24899        .unwrap();
24900    cx.executor().run_until_parked();
24901
24902    let fake_server = fake_language_servers.next().await.unwrap();
24903
24904    let buffer = editor.update(cx, |editor, cx| {
24905        editor
24906            .buffer()
24907            .read(cx)
24908            .as_singleton()
24909            .expect("have opened a single file by path")
24910    });
24911
24912    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24913    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24914    drop(buffer_snapshot);
24915    let actions = cx
24916        .update_window(*workspace, |_, window, cx| {
24917            project.code_actions(&buffer, anchor..anchor, window, cx)
24918        })
24919        .unwrap();
24920
24921    fake_server
24922        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24923            Ok(Some(vec![
24924                lsp::CodeLens {
24925                    range: lsp::Range::default(),
24926                    command: Some(lsp::Command {
24927                        title: "Code lens command".to_owned(),
24928                        command: "_the/command".to_owned(),
24929                        arguments: None,
24930                    }),
24931                    data: None,
24932                },
24933                lsp::CodeLens {
24934                    range: lsp::Range::default(),
24935                    command: Some(lsp::Command {
24936                        title: "Command not in capabilities".to_owned(),
24937                        command: "not in capabilities".to_owned(),
24938                        arguments: None,
24939                    }),
24940                    data: None,
24941                },
24942                lsp::CodeLens {
24943                    range: lsp::Range {
24944                        start: lsp::Position {
24945                            line: 1,
24946                            character: 1,
24947                        },
24948                        end: lsp::Position {
24949                            line: 1,
24950                            character: 1,
24951                        },
24952                    },
24953                    command: Some(lsp::Command {
24954                        title: "Command not in range".to_owned(),
24955                        command: "_the/command".to_owned(),
24956                        arguments: None,
24957                    }),
24958                    data: None,
24959                },
24960            ]))
24961        })
24962        .next()
24963        .await;
24964
24965    let actions = actions.await.unwrap();
24966    assert_eq!(
24967        actions.len(),
24968        1,
24969        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24970    );
24971    let action = actions[0].clone();
24972    let apply = project.update(cx, |project, cx| {
24973        project.apply_code_action(buffer.clone(), action, true, cx)
24974    });
24975
24976    // Resolving the code action does not populate its edits. In absence of
24977    // edits, we must execute the given command.
24978    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24979        |mut lens, _| async move {
24980            let lens_command = lens.command.as_mut().expect("should have a command");
24981            assert_eq!(lens_command.title, "Code lens command");
24982            lens_command.arguments = Some(vec![json!("the-argument")]);
24983            Ok(lens)
24984        },
24985    );
24986
24987    // While executing the command, the language server sends the editor
24988    // a `workspaceEdit` request.
24989    fake_server
24990        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24991            let fake = fake_server.clone();
24992            move |params, _| {
24993                assert_eq!(params.command, "_the/command");
24994                let fake = fake.clone();
24995                async move {
24996                    fake.server
24997                        .request::<lsp::request::ApplyWorkspaceEdit>(
24998                            lsp::ApplyWorkspaceEditParams {
24999                                label: None,
25000                                edit: lsp::WorkspaceEdit {
25001                                    changes: Some(
25002                                        [(
25003                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25004                                            vec![lsp::TextEdit {
25005                                                range: lsp::Range::new(
25006                                                    lsp::Position::new(0, 0),
25007                                                    lsp::Position::new(0, 0),
25008                                                ),
25009                                                new_text: "X".into(),
25010                                            }],
25011                                        )]
25012                                        .into_iter()
25013                                        .collect(),
25014                                    ),
25015                                    ..lsp::WorkspaceEdit::default()
25016                                },
25017                            },
25018                        )
25019                        .await
25020                        .into_response()
25021                        .unwrap();
25022                    Ok(Some(json!(null)))
25023                }
25024            }
25025        })
25026        .next()
25027        .await;
25028
25029    // Applying the code lens command returns a project transaction containing the edits
25030    // sent by the language server in its `workspaceEdit` request.
25031    let transaction = apply.await.unwrap();
25032    assert!(transaction.0.contains_key(&buffer));
25033    buffer.update(cx, |buffer, cx| {
25034        assert_eq!(buffer.text(), "Xa");
25035        buffer.undo(cx);
25036        assert_eq!(buffer.text(), "a");
25037    });
25038
25039    let actions_after_edits = cx
25040        .update_window(*workspace, |_, window, cx| {
25041            project.code_actions(&buffer, anchor..anchor, window, cx)
25042        })
25043        .unwrap()
25044        .await
25045        .unwrap();
25046    assert_eq!(
25047        actions, actions_after_edits,
25048        "For the same selection, same code lens actions should be returned"
25049    );
25050
25051    let _responses =
25052        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25053            panic!("No more code lens requests are expected");
25054        });
25055    editor.update_in(cx, |editor, window, cx| {
25056        editor.select_all(&SelectAll, window, cx);
25057    });
25058    cx.executor().run_until_parked();
25059    let new_actions = cx
25060        .update_window(*workspace, |_, window, cx| {
25061            project.code_actions(&buffer, anchor..anchor, window, cx)
25062        })
25063        .unwrap()
25064        .await
25065        .unwrap();
25066    assert_eq!(
25067        actions, new_actions,
25068        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25069    );
25070}
25071
25072#[gpui::test]
25073async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25074    init_test(cx, |_| {});
25075
25076    let fs = FakeFs::new(cx.executor());
25077    let main_text = r#"fn main() {
25078println!("1");
25079println!("2");
25080println!("3");
25081println!("4");
25082println!("5");
25083}"#;
25084    let lib_text = "mod foo {}";
25085    fs.insert_tree(
25086        path!("/a"),
25087        json!({
25088            "lib.rs": lib_text,
25089            "main.rs": main_text,
25090        }),
25091    )
25092    .await;
25093
25094    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25095    let (workspace, cx) =
25096        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25097    let worktree_id = workspace.update(cx, |workspace, cx| {
25098        workspace.project().update(cx, |project, cx| {
25099            project.worktrees(cx).next().unwrap().read(cx).id()
25100        })
25101    });
25102
25103    let expected_ranges = vec![
25104        Point::new(0, 0)..Point::new(0, 0),
25105        Point::new(1, 0)..Point::new(1, 1),
25106        Point::new(2, 0)..Point::new(2, 2),
25107        Point::new(3, 0)..Point::new(3, 3),
25108    ];
25109
25110    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25111    let editor_1 = workspace
25112        .update_in(cx, |workspace, window, cx| {
25113            workspace.open_path(
25114                (worktree_id, rel_path("main.rs")),
25115                Some(pane_1.downgrade()),
25116                true,
25117                window,
25118                cx,
25119            )
25120        })
25121        .unwrap()
25122        .await
25123        .downcast::<Editor>()
25124        .unwrap();
25125    pane_1.update(cx, |pane, cx| {
25126        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25127        open_editor.update(cx, |editor, cx| {
25128            assert_eq!(
25129                editor.display_text(cx),
25130                main_text,
25131                "Original main.rs text on initial open",
25132            );
25133            assert_eq!(
25134                editor
25135                    .selections
25136                    .all::<Point>(&editor.display_snapshot(cx))
25137                    .into_iter()
25138                    .map(|s| s.range())
25139                    .collect::<Vec<_>>(),
25140                vec![Point::zero()..Point::zero()],
25141                "Default selections on initial open",
25142            );
25143        })
25144    });
25145    editor_1.update_in(cx, |editor, window, cx| {
25146        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25147            s.select_ranges(expected_ranges.clone());
25148        });
25149    });
25150
25151    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25152        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25153    });
25154    let editor_2 = workspace
25155        .update_in(cx, |workspace, window, cx| {
25156            workspace.open_path(
25157                (worktree_id, rel_path("main.rs")),
25158                Some(pane_2.downgrade()),
25159                true,
25160                window,
25161                cx,
25162            )
25163        })
25164        .unwrap()
25165        .await
25166        .downcast::<Editor>()
25167        .unwrap();
25168    pane_2.update(cx, |pane, cx| {
25169        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25170        open_editor.update(cx, |editor, cx| {
25171            assert_eq!(
25172                editor.display_text(cx),
25173                main_text,
25174                "Original main.rs text on initial open in another panel",
25175            );
25176            assert_eq!(
25177                editor
25178                    .selections
25179                    .all::<Point>(&editor.display_snapshot(cx))
25180                    .into_iter()
25181                    .map(|s| s.range())
25182                    .collect::<Vec<_>>(),
25183                vec![Point::zero()..Point::zero()],
25184                "Default selections on initial open in another panel",
25185            );
25186        })
25187    });
25188
25189    editor_2.update_in(cx, |editor, window, cx| {
25190        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25191    });
25192
25193    let _other_editor_1 = workspace
25194        .update_in(cx, |workspace, window, cx| {
25195            workspace.open_path(
25196                (worktree_id, rel_path("lib.rs")),
25197                Some(pane_1.downgrade()),
25198                true,
25199                window,
25200                cx,
25201            )
25202        })
25203        .unwrap()
25204        .await
25205        .downcast::<Editor>()
25206        .unwrap();
25207    pane_1
25208        .update_in(cx, |pane, window, cx| {
25209            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25210        })
25211        .await
25212        .unwrap();
25213    drop(editor_1);
25214    pane_1.update(cx, |pane, cx| {
25215        pane.active_item()
25216            .unwrap()
25217            .downcast::<Editor>()
25218            .unwrap()
25219            .update(cx, |editor, cx| {
25220                assert_eq!(
25221                    editor.display_text(cx),
25222                    lib_text,
25223                    "Other file should be open and active",
25224                );
25225            });
25226        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25227    });
25228
25229    let _other_editor_2 = workspace
25230        .update_in(cx, |workspace, window, cx| {
25231            workspace.open_path(
25232                (worktree_id, rel_path("lib.rs")),
25233                Some(pane_2.downgrade()),
25234                true,
25235                window,
25236                cx,
25237            )
25238        })
25239        .unwrap()
25240        .await
25241        .downcast::<Editor>()
25242        .unwrap();
25243    pane_2
25244        .update_in(cx, |pane, window, cx| {
25245            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25246        })
25247        .await
25248        .unwrap();
25249    drop(editor_2);
25250    pane_2.update(cx, |pane, cx| {
25251        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25252        open_editor.update(cx, |editor, cx| {
25253            assert_eq!(
25254                editor.display_text(cx),
25255                lib_text,
25256                "Other file should be open and active in another panel too",
25257            );
25258        });
25259        assert_eq!(
25260            pane.items().count(),
25261            1,
25262            "No other editors should be open in another pane",
25263        );
25264    });
25265
25266    let _editor_1_reopened = workspace
25267        .update_in(cx, |workspace, window, cx| {
25268            workspace.open_path(
25269                (worktree_id, rel_path("main.rs")),
25270                Some(pane_1.downgrade()),
25271                true,
25272                window,
25273                cx,
25274            )
25275        })
25276        .unwrap()
25277        .await
25278        .downcast::<Editor>()
25279        .unwrap();
25280    let _editor_2_reopened = workspace
25281        .update_in(cx, |workspace, window, cx| {
25282            workspace.open_path(
25283                (worktree_id, rel_path("main.rs")),
25284                Some(pane_2.downgrade()),
25285                true,
25286                window,
25287                cx,
25288            )
25289        })
25290        .unwrap()
25291        .await
25292        .downcast::<Editor>()
25293        .unwrap();
25294    pane_1.update(cx, |pane, cx| {
25295        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25296        open_editor.update(cx, |editor, cx| {
25297            assert_eq!(
25298                editor.display_text(cx),
25299                main_text,
25300                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25301            );
25302            assert_eq!(
25303                editor
25304                    .selections
25305                    .all::<Point>(&editor.display_snapshot(cx))
25306                    .into_iter()
25307                    .map(|s| s.range())
25308                    .collect::<Vec<_>>(),
25309                expected_ranges,
25310                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25311            );
25312        })
25313    });
25314    pane_2.update(cx, |pane, cx| {
25315        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25316        open_editor.update(cx, |editor, cx| {
25317            assert_eq!(
25318                editor.display_text(cx),
25319                r#"fn main() {
25320⋯rintln!("1");
25321⋯intln!("2");
25322⋯ntln!("3");
25323println!("4");
25324println!("5");
25325}"#,
25326                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25327            );
25328            assert_eq!(
25329                editor
25330                    .selections
25331                    .all::<Point>(&editor.display_snapshot(cx))
25332                    .into_iter()
25333                    .map(|s| s.range())
25334                    .collect::<Vec<_>>(),
25335                vec![Point::zero()..Point::zero()],
25336                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25337            );
25338        })
25339    });
25340}
25341
25342#[gpui::test]
25343async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25344    init_test(cx, |_| {});
25345
25346    let fs = FakeFs::new(cx.executor());
25347    let main_text = r#"fn main() {
25348println!("1");
25349println!("2");
25350println!("3");
25351println!("4");
25352println!("5");
25353}"#;
25354    let lib_text = "mod foo {}";
25355    fs.insert_tree(
25356        path!("/a"),
25357        json!({
25358            "lib.rs": lib_text,
25359            "main.rs": main_text,
25360        }),
25361    )
25362    .await;
25363
25364    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25365    let (workspace, cx) =
25366        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25367    let worktree_id = workspace.update(cx, |workspace, cx| {
25368        workspace.project().update(cx, |project, cx| {
25369            project.worktrees(cx).next().unwrap().read(cx).id()
25370        })
25371    });
25372
25373    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25374    let editor = workspace
25375        .update_in(cx, |workspace, window, cx| {
25376            workspace.open_path(
25377                (worktree_id, rel_path("main.rs")),
25378                Some(pane.downgrade()),
25379                true,
25380                window,
25381                cx,
25382            )
25383        })
25384        .unwrap()
25385        .await
25386        .downcast::<Editor>()
25387        .unwrap();
25388    pane.update(cx, |pane, cx| {
25389        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25390        open_editor.update(cx, |editor, cx| {
25391            assert_eq!(
25392                editor.display_text(cx),
25393                main_text,
25394                "Original main.rs text on initial open",
25395            );
25396        })
25397    });
25398    editor.update_in(cx, |editor, window, cx| {
25399        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25400    });
25401
25402    cx.update_global(|store: &mut SettingsStore, cx| {
25403        store.update_user_settings(cx, |s| {
25404            s.workspace.restore_on_file_reopen = Some(false);
25405        });
25406    });
25407    editor.update_in(cx, |editor, window, cx| {
25408        editor.fold_ranges(
25409            vec![
25410                Point::new(1, 0)..Point::new(1, 1),
25411                Point::new(2, 0)..Point::new(2, 2),
25412                Point::new(3, 0)..Point::new(3, 3),
25413            ],
25414            false,
25415            window,
25416            cx,
25417        );
25418    });
25419    pane.update_in(cx, |pane, window, cx| {
25420        pane.close_all_items(&CloseAllItems::default(), window, cx)
25421    })
25422    .await
25423    .unwrap();
25424    pane.update(cx, |pane, _| {
25425        assert!(pane.active_item().is_none());
25426    });
25427    cx.update_global(|store: &mut SettingsStore, cx| {
25428        store.update_user_settings(cx, |s| {
25429            s.workspace.restore_on_file_reopen = Some(true);
25430        });
25431    });
25432
25433    let _editor_reopened = workspace
25434        .update_in(cx, |workspace, window, cx| {
25435            workspace.open_path(
25436                (worktree_id, rel_path("main.rs")),
25437                Some(pane.downgrade()),
25438                true,
25439                window,
25440                cx,
25441            )
25442        })
25443        .unwrap()
25444        .await
25445        .downcast::<Editor>()
25446        .unwrap();
25447    pane.update(cx, |pane, cx| {
25448        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25449        open_editor.update(cx, |editor, cx| {
25450            assert_eq!(
25451                editor.display_text(cx),
25452                main_text,
25453                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25454            );
25455        })
25456    });
25457}
25458
25459#[gpui::test]
25460async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25461    struct EmptyModalView {
25462        focus_handle: gpui::FocusHandle,
25463    }
25464    impl EventEmitter<DismissEvent> for EmptyModalView {}
25465    impl Render for EmptyModalView {
25466        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25467            div()
25468        }
25469    }
25470    impl Focusable for EmptyModalView {
25471        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25472            self.focus_handle.clone()
25473        }
25474    }
25475    impl workspace::ModalView for EmptyModalView {}
25476    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25477        EmptyModalView {
25478            focus_handle: cx.focus_handle(),
25479        }
25480    }
25481
25482    init_test(cx, |_| {});
25483
25484    let fs = FakeFs::new(cx.executor());
25485    let project = Project::test(fs, [], cx).await;
25486    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25487    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25488    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25489    let editor = cx.new_window_entity(|window, cx| {
25490        Editor::new(
25491            EditorMode::full(),
25492            buffer,
25493            Some(project.clone()),
25494            window,
25495            cx,
25496        )
25497    });
25498    workspace
25499        .update(cx, |workspace, window, cx| {
25500            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25501        })
25502        .unwrap();
25503    editor.update_in(cx, |editor, window, cx| {
25504        editor.open_context_menu(&OpenContextMenu, window, cx);
25505        assert!(editor.mouse_context_menu.is_some());
25506    });
25507    workspace
25508        .update(cx, |workspace, window, cx| {
25509            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25510        })
25511        .unwrap();
25512    cx.read(|cx| {
25513        assert!(editor.read(cx).mouse_context_menu.is_none());
25514    });
25515}
25516
25517fn set_linked_edit_ranges(
25518    opening: (Point, Point),
25519    closing: (Point, Point),
25520    editor: &mut Editor,
25521    cx: &mut Context<Editor>,
25522) {
25523    let Some((buffer, _)) = editor
25524        .buffer
25525        .read(cx)
25526        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25527    else {
25528        panic!("Failed to get buffer for selection position");
25529    };
25530    let buffer = buffer.read(cx);
25531    let buffer_id = buffer.remote_id();
25532    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25533    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25534    let mut linked_ranges = HashMap::default();
25535    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25536    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25537}
25538
25539#[gpui::test]
25540async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25541    init_test(cx, |_| {});
25542
25543    let fs = FakeFs::new(cx.executor());
25544    fs.insert_file(path!("/file.html"), Default::default())
25545        .await;
25546
25547    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25548
25549    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25550    let html_language = Arc::new(Language::new(
25551        LanguageConfig {
25552            name: "HTML".into(),
25553            matcher: LanguageMatcher {
25554                path_suffixes: vec!["html".to_string()],
25555                ..LanguageMatcher::default()
25556            },
25557            brackets: BracketPairConfig {
25558                pairs: vec![BracketPair {
25559                    start: "<".into(),
25560                    end: ">".into(),
25561                    close: true,
25562                    ..Default::default()
25563                }],
25564                ..Default::default()
25565            },
25566            ..Default::default()
25567        },
25568        Some(tree_sitter_html::LANGUAGE.into()),
25569    ));
25570    language_registry.add(html_language);
25571    let mut fake_servers = language_registry.register_fake_lsp(
25572        "HTML",
25573        FakeLspAdapter {
25574            capabilities: lsp::ServerCapabilities {
25575                completion_provider: Some(lsp::CompletionOptions {
25576                    resolve_provider: Some(true),
25577                    ..Default::default()
25578                }),
25579                ..Default::default()
25580            },
25581            ..Default::default()
25582        },
25583    );
25584
25585    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25586    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25587
25588    let worktree_id = workspace
25589        .update(cx, |workspace, _window, cx| {
25590            workspace.project().update(cx, |project, cx| {
25591                project.worktrees(cx).next().unwrap().read(cx).id()
25592            })
25593        })
25594        .unwrap();
25595    project
25596        .update(cx, |project, cx| {
25597            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25598        })
25599        .await
25600        .unwrap();
25601    let editor = workspace
25602        .update(cx, |workspace, window, cx| {
25603            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25604        })
25605        .unwrap()
25606        .await
25607        .unwrap()
25608        .downcast::<Editor>()
25609        .unwrap();
25610
25611    let fake_server = fake_servers.next().await.unwrap();
25612    cx.run_until_parked();
25613    editor.update_in(cx, |editor, window, cx| {
25614        editor.set_text("<ad></ad>", window, cx);
25615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25616            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25617        });
25618        set_linked_edit_ranges(
25619            (Point::new(0, 1), Point::new(0, 3)),
25620            (Point::new(0, 6), Point::new(0, 8)),
25621            editor,
25622            cx,
25623        );
25624    });
25625    let mut completion_handle =
25626        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25627            Ok(Some(lsp::CompletionResponse::Array(vec![
25628                lsp::CompletionItem {
25629                    label: "head".to_string(),
25630                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25631                        lsp::InsertReplaceEdit {
25632                            new_text: "head".to_string(),
25633                            insert: lsp::Range::new(
25634                                lsp::Position::new(0, 1),
25635                                lsp::Position::new(0, 3),
25636                            ),
25637                            replace: lsp::Range::new(
25638                                lsp::Position::new(0, 1),
25639                                lsp::Position::new(0, 3),
25640                            ),
25641                        },
25642                    )),
25643                    ..Default::default()
25644                },
25645            ])))
25646        });
25647    editor.update_in(cx, |editor, window, cx| {
25648        editor.show_completions(&ShowCompletions, window, cx);
25649    });
25650    cx.run_until_parked();
25651    completion_handle.next().await.unwrap();
25652    editor.update(cx, |editor, _| {
25653        assert!(
25654            editor.context_menu_visible(),
25655            "Completion menu should be visible"
25656        );
25657    });
25658    editor.update_in(cx, |editor, window, cx| {
25659        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25660    });
25661    cx.executor().run_until_parked();
25662    editor.update(cx, |editor, cx| {
25663        assert_eq!(editor.text(cx), "<head></head>");
25664    });
25665}
25666
25667#[gpui::test]
25668async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25669    init_test(cx, |_| {});
25670
25671    let mut cx = EditorTestContext::new(cx).await;
25672    let language = Arc::new(Language::new(
25673        LanguageConfig {
25674            name: "TSX".into(),
25675            matcher: LanguageMatcher {
25676                path_suffixes: vec!["tsx".to_string()],
25677                ..LanguageMatcher::default()
25678            },
25679            brackets: BracketPairConfig {
25680                pairs: vec![BracketPair {
25681                    start: "<".into(),
25682                    end: ">".into(),
25683                    close: true,
25684                    ..Default::default()
25685                }],
25686                ..Default::default()
25687            },
25688            linked_edit_characters: HashSet::from_iter(['.']),
25689            ..Default::default()
25690        },
25691        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25692    ));
25693    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25694
25695    // Test typing > does not extend linked pair
25696    cx.set_state("<divˇ<div></div>");
25697    cx.update_editor(|editor, _, cx| {
25698        set_linked_edit_ranges(
25699            (Point::new(0, 1), Point::new(0, 4)),
25700            (Point::new(0, 11), Point::new(0, 14)),
25701            editor,
25702            cx,
25703        );
25704    });
25705    cx.update_editor(|editor, window, cx| {
25706        editor.handle_input(">", window, cx);
25707    });
25708    cx.assert_editor_state("<div>ˇ<div></div>");
25709
25710    // Test typing . do extend linked pair
25711    cx.set_state("<Animatedˇ></Animated>");
25712    cx.update_editor(|editor, _, cx| {
25713        set_linked_edit_ranges(
25714            (Point::new(0, 1), Point::new(0, 9)),
25715            (Point::new(0, 12), Point::new(0, 20)),
25716            editor,
25717            cx,
25718        );
25719    });
25720    cx.update_editor(|editor, window, cx| {
25721        editor.handle_input(".", window, cx);
25722    });
25723    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25724    cx.update_editor(|editor, _, cx| {
25725        set_linked_edit_ranges(
25726            (Point::new(0, 1), Point::new(0, 10)),
25727            (Point::new(0, 13), Point::new(0, 21)),
25728            editor,
25729            cx,
25730        );
25731    });
25732    cx.update_editor(|editor, window, cx| {
25733        editor.handle_input("V", window, cx);
25734    });
25735    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25736}
25737
25738#[gpui::test]
25739async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25740    init_test(cx, |_| {});
25741
25742    let fs = FakeFs::new(cx.executor());
25743    fs.insert_tree(
25744        path!("/root"),
25745        json!({
25746            "a": {
25747                "main.rs": "fn main() {}",
25748            },
25749            "foo": {
25750                "bar": {
25751                    "external_file.rs": "pub mod external {}",
25752                }
25753            }
25754        }),
25755    )
25756    .await;
25757
25758    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25759    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25760    language_registry.add(rust_lang());
25761    let _fake_servers = language_registry.register_fake_lsp(
25762        "Rust",
25763        FakeLspAdapter {
25764            ..FakeLspAdapter::default()
25765        },
25766    );
25767    let (workspace, cx) =
25768        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25769    let worktree_id = workspace.update(cx, |workspace, cx| {
25770        workspace.project().update(cx, |project, cx| {
25771            project.worktrees(cx).next().unwrap().read(cx).id()
25772        })
25773    });
25774
25775    let assert_language_servers_count =
25776        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25777            project.update(cx, |project, cx| {
25778                let current = project
25779                    .lsp_store()
25780                    .read(cx)
25781                    .as_local()
25782                    .unwrap()
25783                    .language_servers
25784                    .len();
25785                assert_eq!(expected, current, "{context}");
25786            });
25787        };
25788
25789    assert_language_servers_count(
25790        0,
25791        "No servers should be running before any file is open",
25792        cx,
25793    );
25794    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25795    let main_editor = workspace
25796        .update_in(cx, |workspace, window, cx| {
25797            workspace.open_path(
25798                (worktree_id, rel_path("main.rs")),
25799                Some(pane.downgrade()),
25800                true,
25801                window,
25802                cx,
25803            )
25804        })
25805        .unwrap()
25806        .await
25807        .downcast::<Editor>()
25808        .unwrap();
25809    pane.update(cx, |pane, cx| {
25810        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25811        open_editor.update(cx, |editor, cx| {
25812            assert_eq!(
25813                editor.display_text(cx),
25814                "fn main() {}",
25815                "Original main.rs text on initial open",
25816            );
25817        });
25818        assert_eq!(open_editor, main_editor);
25819    });
25820    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25821
25822    let external_editor = workspace
25823        .update_in(cx, |workspace, window, cx| {
25824            workspace.open_abs_path(
25825                PathBuf::from("/root/foo/bar/external_file.rs"),
25826                OpenOptions::default(),
25827                window,
25828                cx,
25829            )
25830        })
25831        .await
25832        .expect("opening external file")
25833        .downcast::<Editor>()
25834        .expect("downcasted external file's open element to editor");
25835    pane.update(cx, |pane, cx| {
25836        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25837        open_editor.update(cx, |editor, cx| {
25838            assert_eq!(
25839                editor.display_text(cx),
25840                "pub mod external {}",
25841                "External file is open now",
25842            );
25843        });
25844        assert_eq!(open_editor, external_editor);
25845    });
25846    assert_language_servers_count(
25847        1,
25848        "Second, external, *.rs file should join the existing server",
25849        cx,
25850    );
25851
25852    pane.update_in(cx, |pane, window, cx| {
25853        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25854    })
25855    .await
25856    .unwrap();
25857    pane.update_in(cx, |pane, window, cx| {
25858        pane.navigate_backward(&Default::default(), window, cx);
25859    });
25860    cx.run_until_parked();
25861    pane.update(cx, |pane, cx| {
25862        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25863        open_editor.update(cx, |editor, cx| {
25864            assert_eq!(
25865                editor.display_text(cx),
25866                "pub mod external {}",
25867                "External file is open now",
25868            );
25869        });
25870    });
25871    assert_language_servers_count(
25872        1,
25873        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25874        cx,
25875    );
25876
25877    cx.update(|_, cx| {
25878        workspace::reload(cx);
25879    });
25880    assert_language_servers_count(
25881        1,
25882        "After reloading the worktree with local and external files opened, only one project should be started",
25883        cx,
25884    );
25885}
25886
25887#[gpui::test]
25888async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25889    init_test(cx, |_| {});
25890
25891    let mut cx = EditorTestContext::new(cx).await;
25892    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25893    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25894
25895    // test cursor move to start of each line on tab
25896    // for `if`, `elif`, `else`, `while`, `with` and `for`
25897    cx.set_state(indoc! {"
25898        def main():
25899        ˇ    for item in items:
25900        ˇ        while item.active:
25901        ˇ            if item.value > 10:
25902        ˇ                continue
25903        ˇ            elif item.value < 0:
25904        ˇ                break
25905        ˇ            else:
25906        ˇ                with item.context() as ctx:
25907        ˇ                    yield count
25908        ˇ        else:
25909        ˇ            log('while else')
25910        ˇ    else:
25911        ˇ        log('for else')
25912    "});
25913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25914    cx.wait_for_autoindent_applied().await;
25915    cx.assert_editor_state(indoc! {"
25916        def main():
25917            ˇfor item in items:
25918                ˇwhile item.active:
25919                    ˇif item.value > 10:
25920                        ˇcontinue
25921                    ˇelif item.value < 0:
25922                        ˇbreak
25923                    ˇelse:
25924                        ˇwith item.context() as ctx:
25925                            ˇyield count
25926                ˇelse:
25927                    ˇlog('while else')
25928            ˇelse:
25929                ˇlog('for else')
25930    "});
25931    // test relative indent is preserved when tab
25932    // for `if`, `elif`, `else`, `while`, `with` and `for`
25933    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25934    cx.wait_for_autoindent_applied().await;
25935    cx.assert_editor_state(indoc! {"
25936        def main():
25937                ˇfor item in items:
25938                    ˇwhile item.active:
25939                        ˇif item.value > 10:
25940                            ˇcontinue
25941                        ˇelif item.value < 0:
25942                            ˇbreak
25943                        ˇelse:
25944                            ˇwith item.context() as ctx:
25945                                ˇyield count
25946                    ˇelse:
25947                        ˇlog('while else')
25948                ˇelse:
25949                    ˇlog('for else')
25950    "});
25951
25952    // test cursor move to start of each line on tab
25953    // for `try`, `except`, `else`, `finally`, `match` and `def`
25954    cx.set_state(indoc! {"
25955        def main():
25956        ˇ    try:
25957        ˇ        fetch()
25958        ˇ    except ValueError:
25959        ˇ        handle_error()
25960        ˇ    else:
25961        ˇ        match value:
25962        ˇ            case _:
25963        ˇ    finally:
25964        ˇ        def status():
25965        ˇ            return 0
25966    "});
25967    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25968    cx.wait_for_autoindent_applied().await;
25969    cx.assert_editor_state(indoc! {"
25970        def main():
25971            ˇtry:
25972                ˇfetch()
25973            ˇexcept ValueError:
25974                ˇhandle_error()
25975            ˇelse:
25976                ˇmatch value:
25977                    ˇcase _:
25978            ˇfinally:
25979                ˇdef status():
25980                    ˇreturn 0
25981    "});
25982    // test relative indent is preserved when tab
25983    // for `try`, `except`, `else`, `finally`, `match` and `def`
25984    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25985    cx.wait_for_autoindent_applied().await;
25986    cx.assert_editor_state(indoc! {"
25987        def main():
25988                ˇtry:
25989                    ˇfetch()
25990                ˇexcept ValueError:
25991                    ˇhandle_error()
25992                ˇelse:
25993                    ˇmatch value:
25994                        ˇcase _:
25995                ˇfinally:
25996                    ˇdef status():
25997                        ˇreturn 0
25998    "});
25999}
26000
26001#[gpui::test]
26002async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26003    init_test(cx, |_| {});
26004
26005    let mut cx = EditorTestContext::new(cx).await;
26006    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26007    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26008
26009    // test `else` auto outdents when typed inside `if` block
26010    cx.set_state(indoc! {"
26011        def main():
26012            if i == 2:
26013                return
26014                ˇ
26015    "});
26016    cx.update_editor(|editor, window, cx| {
26017        editor.handle_input("else:", window, cx);
26018    });
26019    cx.wait_for_autoindent_applied().await;
26020    cx.assert_editor_state(indoc! {"
26021        def main():
26022            if i == 2:
26023                return
26024            else:ˇ
26025    "});
26026
26027    // test `except` auto outdents when typed inside `try` block
26028    cx.set_state(indoc! {"
26029        def main():
26030            try:
26031                i = 2
26032                ˇ
26033    "});
26034    cx.update_editor(|editor, window, cx| {
26035        editor.handle_input("except:", window, cx);
26036    });
26037    cx.wait_for_autoindent_applied().await;
26038    cx.assert_editor_state(indoc! {"
26039        def main():
26040            try:
26041                i = 2
26042            except:ˇ
26043    "});
26044
26045    // test `else` auto outdents when typed inside `except` block
26046    cx.set_state(indoc! {"
26047        def main():
26048            try:
26049                i = 2
26050            except:
26051                j = 2
26052                ˇ
26053    "});
26054    cx.update_editor(|editor, window, cx| {
26055        editor.handle_input("else:", window, cx);
26056    });
26057    cx.wait_for_autoindent_applied().await;
26058    cx.assert_editor_state(indoc! {"
26059        def main():
26060            try:
26061                i = 2
26062            except:
26063                j = 2
26064            else:ˇ
26065    "});
26066
26067    // test `finally` auto outdents when typed inside `else` block
26068    cx.set_state(indoc! {"
26069        def main():
26070            try:
26071                i = 2
26072            except:
26073                j = 2
26074            else:
26075                k = 2
26076                ˇ
26077    "});
26078    cx.update_editor(|editor, window, cx| {
26079        editor.handle_input("finally:", window, cx);
26080    });
26081    cx.wait_for_autoindent_applied().await;
26082    cx.assert_editor_state(indoc! {"
26083        def main():
26084            try:
26085                i = 2
26086            except:
26087                j = 2
26088            else:
26089                k = 2
26090            finally:ˇ
26091    "});
26092
26093    // test `else` does not outdents when typed inside `except` block right after for block
26094    cx.set_state(indoc! {"
26095        def main():
26096            try:
26097                i = 2
26098            except:
26099                for i in range(n):
26100                    pass
26101                ˇ
26102    "});
26103    cx.update_editor(|editor, window, cx| {
26104        editor.handle_input("else:", window, cx);
26105    });
26106    cx.wait_for_autoindent_applied().await;
26107    cx.assert_editor_state(indoc! {"
26108        def main():
26109            try:
26110                i = 2
26111            except:
26112                for i in range(n):
26113                    pass
26114                else:ˇ
26115    "});
26116
26117    // test `finally` auto outdents when typed inside `else` block right after for block
26118    cx.set_state(indoc! {"
26119        def main():
26120            try:
26121                i = 2
26122            except:
26123                j = 2
26124            else:
26125                for i in range(n):
26126                    pass
26127                ˇ
26128    "});
26129    cx.update_editor(|editor, window, cx| {
26130        editor.handle_input("finally:", window, cx);
26131    });
26132    cx.wait_for_autoindent_applied().await;
26133    cx.assert_editor_state(indoc! {"
26134        def main():
26135            try:
26136                i = 2
26137            except:
26138                j = 2
26139            else:
26140                for i in range(n):
26141                    pass
26142            finally:ˇ
26143    "});
26144
26145    // test `except` outdents to inner "try" block
26146    cx.set_state(indoc! {"
26147        def main():
26148            try:
26149                i = 2
26150                if i == 2:
26151                    try:
26152                        i = 3
26153                        ˇ
26154    "});
26155    cx.update_editor(|editor, window, cx| {
26156        editor.handle_input("except:", window, cx);
26157    });
26158    cx.wait_for_autoindent_applied().await;
26159    cx.assert_editor_state(indoc! {"
26160        def main():
26161            try:
26162                i = 2
26163                if i == 2:
26164                    try:
26165                        i = 3
26166                    except:ˇ
26167    "});
26168
26169    // test `except` outdents to outer "try" block
26170    cx.set_state(indoc! {"
26171        def main():
26172            try:
26173                i = 2
26174                if i == 2:
26175                    try:
26176                        i = 3
26177                ˇ
26178    "});
26179    cx.update_editor(|editor, window, cx| {
26180        editor.handle_input("except:", window, cx);
26181    });
26182    cx.wait_for_autoindent_applied().await;
26183    cx.assert_editor_state(indoc! {"
26184        def main():
26185            try:
26186                i = 2
26187                if i == 2:
26188                    try:
26189                        i = 3
26190            except:ˇ
26191    "});
26192
26193    // test `else` stays at correct indent when typed after `for` block
26194    cx.set_state(indoc! {"
26195        def main():
26196            for i in range(10):
26197                if i == 3:
26198                    break
26199            ˇ
26200    "});
26201    cx.update_editor(|editor, window, cx| {
26202        editor.handle_input("else:", window, cx);
26203    });
26204    cx.wait_for_autoindent_applied().await;
26205    cx.assert_editor_state(indoc! {"
26206        def main():
26207            for i in range(10):
26208                if i == 3:
26209                    break
26210            else:ˇ
26211    "});
26212
26213    // test does not outdent on typing after line with square brackets
26214    cx.set_state(indoc! {"
26215        def f() -> list[str]:
26216            ˇ
26217    "});
26218    cx.update_editor(|editor, window, cx| {
26219        editor.handle_input("a", window, cx);
26220    });
26221    cx.wait_for_autoindent_applied().await;
26222    cx.assert_editor_state(indoc! {"
26223        def f() -> list[str]:
2622426225    "});
26226
26227    // test does not outdent on typing : after case keyword
26228    cx.set_state(indoc! {"
26229        match 1:
26230            caseˇ
26231    "});
26232    cx.update_editor(|editor, window, cx| {
26233        editor.handle_input(":", window, cx);
26234    });
26235    cx.wait_for_autoindent_applied().await;
26236    cx.assert_editor_state(indoc! {"
26237        match 1:
26238            case:ˇ
26239    "});
26240}
26241
26242#[gpui::test]
26243async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26244    init_test(cx, |_| {});
26245    update_test_language_settings(cx, |settings| {
26246        settings.defaults.extend_comment_on_newline = Some(false);
26247    });
26248    let mut cx = EditorTestContext::new(cx).await;
26249    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26250    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26251
26252    // test correct indent after newline on comment
26253    cx.set_state(indoc! {"
26254        # COMMENT:ˇ
26255    "});
26256    cx.update_editor(|editor, window, cx| {
26257        editor.newline(&Newline, window, cx);
26258    });
26259    cx.wait_for_autoindent_applied().await;
26260    cx.assert_editor_state(indoc! {"
26261        # COMMENT:
26262        ˇ
26263    "});
26264
26265    // test correct indent after newline in brackets
26266    cx.set_state(indoc! {"
26267        {ˇ}
26268    "});
26269    cx.update_editor(|editor, window, cx| {
26270        editor.newline(&Newline, window, cx);
26271    });
26272    cx.wait_for_autoindent_applied().await;
26273    cx.assert_editor_state(indoc! {"
26274        {
26275            ˇ
26276        }
26277    "});
26278
26279    cx.set_state(indoc! {"
26280        (ˇ)
26281    "});
26282    cx.update_editor(|editor, window, cx| {
26283        editor.newline(&Newline, window, cx);
26284    });
26285    cx.run_until_parked();
26286    cx.assert_editor_state(indoc! {"
26287        (
26288            ˇ
26289        )
26290    "});
26291
26292    // do not indent after empty lists or dictionaries
26293    cx.set_state(indoc! {"
26294        a = []ˇ
26295    "});
26296    cx.update_editor(|editor, window, cx| {
26297        editor.newline(&Newline, window, cx);
26298    });
26299    cx.run_until_parked();
26300    cx.assert_editor_state(indoc! {"
26301        a = []
26302        ˇ
26303    "});
26304}
26305
26306#[gpui::test]
26307async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26308    init_test(cx, |_| {});
26309
26310    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26311    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26312    language_registry.add(markdown_lang());
26313    language_registry.add(python_lang);
26314
26315    let mut cx = EditorTestContext::new(cx).await;
26316    cx.update_buffer(|buffer, cx| {
26317        buffer.set_language_registry(language_registry);
26318        buffer.set_language(Some(markdown_lang()), cx);
26319    });
26320
26321    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26322    cx.set_state(indoc! {"
26323        # Heading
26324
26325        ```python
26326        def main():
26327            if condition:
26328                pass
26329                ˇ
26330        ```
26331    "});
26332    cx.update_editor(|editor, window, cx| {
26333        editor.handle_input("else:", window, cx);
26334    });
26335    cx.run_until_parked();
26336    cx.assert_editor_state(indoc! {"
26337        # Heading
26338
26339        ```python
26340        def main():
26341            if condition:
26342                pass
26343            else:ˇ
26344        ```
26345    "});
26346}
26347
26348#[gpui::test]
26349async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26350    init_test(cx, |_| {});
26351
26352    let mut cx = EditorTestContext::new(cx).await;
26353    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26354    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26355
26356    // test cursor move to start of each line on tab
26357    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26358    cx.set_state(indoc! {"
26359        function main() {
26360        ˇ    for item in $items; do
26361        ˇ        while [ -n \"$item\" ]; do
26362        ˇ            if [ \"$value\" -gt 10 ]; then
26363        ˇ                continue
26364        ˇ            elif [ \"$value\" -lt 0 ]; then
26365        ˇ                break
26366        ˇ            else
26367        ˇ                echo \"$item\"
26368        ˇ            fi
26369        ˇ        done
26370        ˇ    done
26371        ˇ}
26372    "});
26373    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26374    cx.wait_for_autoindent_applied().await;
26375    cx.assert_editor_state(indoc! {"
26376        function main() {
26377            ˇfor item in $items; do
26378                ˇwhile [ -n \"$item\" ]; do
26379                    ˇif [ \"$value\" -gt 10 ]; then
26380                        ˇcontinue
26381                    ˇelif [ \"$value\" -lt 0 ]; then
26382                        ˇbreak
26383                    ˇelse
26384                        ˇecho \"$item\"
26385                    ˇfi
26386                ˇdone
26387            ˇdone
26388        ˇ}
26389    "});
26390    // test relative indent is preserved when tab
26391    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26392    cx.wait_for_autoindent_applied().await;
26393    cx.assert_editor_state(indoc! {"
26394        function main() {
26395                ˇfor item in $items; do
26396                    ˇwhile [ -n \"$item\" ]; do
26397                        ˇif [ \"$value\" -gt 10 ]; then
26398                            ˇcontinue
26399                        ˇelif [ \"$value\" -lt 0 ]; then
26400                            ˇbreak
26401                        ˇelse
26402                            ˇecho \"$item\"
26403                        ˇfi
26404                    ˇdone
26405                ˇdone
26406            ˇ}
26407    "});
26408
26409    // test cursor move to start of each line on tab
26410    // for `case` statement with patterns
26411    cx.set_state(indoc! {"
26412        function handle() {
26413        ˇ    case \"$1\" in
26414        ˇ        start)
26415        ˇ            echo \"a\"
26416        ˇ            ;;
26417        ˇ        stop)
26418        ˇ            echo \"b\"
26419        ˇ            ;;
26420        ˇ        *)
26421        ˇ            echo \"c\"
26422        ˇ            ;;
26423        ˇ    esac
26424        ˇ}
26425    "});
26426    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26427    cx.wait_for_autoindent_applied().await;
26428    cx.assert_editor_state(indoc! {"
26429        function handle() {
26430            ˇcase \"$1\" in
26431                ˇstart)
26432                    ˇecho \"a\"
26433                    ˇ;;
26434                ˇstop)
26435                    ˇecho \"b\"
26436                    ˇ;;
26437                ˇ*)
26438                    ˇecho \"c\"
26439                    ˇ;;
26440            ˇesac
26441        ˇ}
26442    "});
26443}
26444
26445#[gpui::test]
26446async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26447    init_test(cx, |_| {});
26448
26449    let mut cx = EditorTestContext::new(cx).await;
26450    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26451    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26452
26453    // test indents on comment insert
26454    cx.set_state(indoc! {"
26455        function main() {
26456        ˇ    for item in $items; do
26457        ˇ        while [ -n \"$item\" ]; do
26458        ˇ            if [ \"$value\" -gt 10 ]; then
26459        ˇ                continue
26460        ˇ            elif [ \"$value\" -lt 0 ]; then
26461        ˇ                break
26462        ˇ            else
26463        ˇ                echo \"$item\"
26464        ˇ            fi
26465        ˇ        done
26466        ˇ    done
26467        ˇ}
26468    "});
26469    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26470    cx.wait_for_autoindent_applied().await;
26471    cx.assert_editor_state(indoc! {"
26472        function main() {
26473        #ˇ    for item in $items; do
26474        #ˇ        while [ -n \"$item\" ]; do
26475        #ˇ            if [ \"$value\" -gt 10 ]; then
26476        #ˇ                continue
26477        #ˇ            elif [ \"$value\" -lt 0 ]; then
26478        #ˇ                break
26479        #ˇ            else
26480        #ˇ                echo \"$item\"
26481        #ˇ            fi
26482        #ˇ        done
26483        #ˇ    done
26484        #ˇ}
26485    "});
26486}
26487
26488#[gpui::test]
26489async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26490    init_test(cx, |_| {});
26491
26492    let mut cx = EditorTestContext::new(cx).await;
26493    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26494    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26495
26496    // test `else` auto outdents when typed inside `if` block
26497    cx.set_state(indoc! {"
26498        if [ \"$1\" = \"test\" ]; then
26499            echo \"foo bar\"
26500            ˇ
26501    "});
26502    cx.update_editor(|editor, window, cx| {
26503        editor.handle_input("else", window, cx);
26504    });
26505    cx.wait_for_autoindent_applied().await;
26506    cx.assert_editor_state(indoc! {"
26507        if [ \"$1\" = \"test\" ]; then
26508            echo \"foo bar\"
26509        elseˇ
26510    "});
26511
26512    // test `elif` auto outdents when typed inside `if` block
26513    cx.set_state(indoc! {"
26514        if [ \"$1\" = \"test\" ]; then
26515            echo \"foo bar\"
26516            ˇ
26517    "});
26518    cx.update_editor(|editor, window, cx| {
26519        editor.handle_input("elif", window, cx);
26520    });
26521    cx.wait_for_autoindent_applied().await;
26522    cx.assert_editor_state(indoc! {"
26523        if [ \"$1\" = \"test\" ]; then
26524            echo \"foo bar\"
26525        elifˇ
26526    "});
26527
26528    // test `fi` auto outdents when typed inside `else` block
26529    cx.set_state(indoc! {"
26530        if [ \"$1\" = \"test\" ]; then
26531            echo \"foo bar\"
26532        else
26533            echo \"bar baz\"
26534            ˇ
26535    "});
26536    cx.update_editor(|editor, window, cx| {
26537        editor.handle_input("fi", window, cx);
26538    });
26539    cx.wait_for_autoindent_applied().await;
26540    cx.assert_editor_state(indoc! {"
26541        if [ \"$1\" = \"test\" ]; then
26542            echo \"foo bar\"
26543        else
26544            echo \"bar baz\"
26545        fiˇ
26546    "});
26547
26548    // test `done` auto outdents when typed inside `while` block
26549    cx.set_state(indoc! {"
26550        while read line; do
26551            echo \"$line\"
26552            ˇ
26553    "});
26554    cx.update_editor(|editor, window, cx| {
26555        editor.handle_input("done", window, cx);
26556    });
26557    cx.wait_for_autoindent_applied().await;
26558    cx.assert_editor_state(indoc! {"
26559        while read line; do
26560            echo \"$line\"
26561        doneˇ
26562    "});
26563
26564    // test `done` auto outdents when typed inside `for` block
26565    cx.set_state(indoc! {"
26566        for file in *.txt; do
26567            cat \"$file\"
26568            ˇ
26569    "});
26570    cx.update_editor(|editor, window, cx| {
26571        editor.handle_input("done", window, cx);
26572    });
26573    cx.wait_for_autoindent_applied().await;
26574    cx.assert_editor_state(indoc! {"
26575        for file in *.txt; do
26576            cat \"$file\"
26577        doneˇ
26578    "});
26579
26580    // test `esac` auto outdents when typed inside `case` block
26581    cx.set_state(indoc! {"
26582        case \"$1\" in
26583            start)
26584                echo \"foo bar\"
26585                ;;
26586            stop)
26587                echo \"bar baz\"
26588                ;;
26589            ˇ
26590    "});
26591    cx.update_editor(|editor, window, cx| {
26592        editor.handle_input("esac", window, cx);
26593    });
26594    cx.wait_for_autoindent_applied().await;
26595    cx.assert_editor_state(indoc! {"
26596        case \"$1\" in
26597            start)
26598                echo \"foo bar\"
26599                ;;
26600            stop)
26601                echo \"bar baz\"
26602                ;;
26603        esacˇ
26604    "});
26605
26606    // test `*)` auto outdents when typed inside `case` block
26607    cx.set_state(indoc! {"
26608        case \"$1\" in
26609            start)
26610                echo \"foo bar\"
26611                ;;
26612                ˇ
26613    "});
26614    cx.update_editor(|editor, window, cx| {
26615        editor.handle_input("*)", window, cx);
26616    });
26617    cx.wait_for_autoindent_applied().await;
26618    cx.assert_editor_state(indoc! {"
26619        case \"$1\" in
26620            start)
26621                echo \"foo bar\"
26622                ;;
26623            *)ˇ
26624    "});
26625
26626    // test `fi` outdents to correct level with nested if blocks
26627    cx.set_state(indoc! {"
26628        if [ \"$1\" = \"test\" ]; then
26629            echo \"outer if\"
26630            if [ \"$2\" = \"debug\" ]; then
26631                echo \"inner if\"
26632                ˇ
26633    "});
26634    cx.update_editor(|editor, window, cx| {
26635        editor.handle_input("fi", window, cx);
26636    });
26637    cx.wait_for_autoindent_applied().await;
26638    cx.assert_editor_state(indoc! {"
26639        if [ \"$1\" = \"test\" ]; then
26640            echo \"outer if\"
26641            if [ \"$2\" = \"debug\" ]; then
26642                echo \"inner if\"
26643            fiˇ
26644    "});
26645}
26646
26647#[gpui::test]
26648async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26649    init_test(cx, |_| {});
26650    update_test_language_settings(cx, |settings| {
26651        settings.defaults.extend_comment_on_newline = Some(false);
26652    });
26653    let mut cx = EditorTestContext::new(cx).await;
26654    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26655    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26656
26657    // test correct indent after newline on comment
26658    cx.set_state(indoc! {"
26659        # COMMENT:ˇ
26660    "});
26661    cx.update_editor(|editor, window, cx| {
26662        editor.newline(&Newline, window, cx);
26663    });
26664    cx.wait_for_autoindent_applied().await;
26665    cx.assert_editor_state(indoc! {"
26666        # COMMENT:
26667        ˇ
26668    "});
26669
26670    // test correct indent after newline after `then`
26671    cx.set_state(indoc! {"
26672
26673        if [ \"$1\" = \"test\" ]; thenˇ
26674    "});
26675    cx.update_editor(|editor, window, cx| {
26676        editor.newline(&Newline, window, cx);
26677    });
26678    cx.wait_for_autoindent_applied().await;
26679    cx.assert_editor_state(indoc! {"
26680
26681        if [ \"$1\" = \"test\" ]; then
26682            ˇ
26683    "});
26684
26685    // test correct indent after newline after `else`
26686    cx.set_state(indoc! {"
26687        if [ \"$1\" = \"test\" ]; then
26688        elseˇ
26689    "});
26690    cx.update_editor(|editor, window, cx| {
26691        editor.newline(&Newline, window, cx);
26692    });
26693    cx.wait_for_autoindent_applied().await;
26694    cx.assert_editor_state(indoc! {"
26695        if [ \"$1\" = \"test\" ]; then
26696        else
26697            ˇ
26698    "});
26699
26700    // test correct indent after newline after `elif`
26701    cx.set_state(indoc! {"
26702        if [ \"$1\" = \"test\" ]; then
26703        elifˇ
26704    "});
26705    cx.update_editor(|editor, window, cx| {
26706        editor.newline(&Newline, window, cx);
26707    });
26708    cx.wait_for_autoindent_applied().await;
26709    cx.assert_editor_state(indoc! {"
26710        if [ \"$1\" = \"test\" ]; then
26711        elif
26712            ˇ
26713    "});
26714
26715    // test correct indent after newline after `do`
26716    cx.set_state(indoc! {"
26717        for file in *.txt; doˇ
26718    "});
26719    cx.update_editor(|editor, window, cx| {
26720        editor.newline(&Newline, window, cx);
26721    });
26722    cx.wait_for_autoindent_applied().await;
26723    cx.assert_editor_state(indoc! {"
26724        for file in *.txt; do
26725            ˇ
26726    "});
26727
26728    // test correct indent after newline after case pattern
26729    cx.set_state(indoc! {"
26730        case \"$1\" in
26731            start)ˇ
26732    "});
26733    cx.update_editor(|editor, window, cx| {
26734        editor.newline(&Newline, window, cx);
26735    });
26736    cx.wait_for_autoindent_applied().await;
26737    cx.assert_editor_state(indoc! {"
26738        case \"$1\" in
26739            start)
26740                ˇ
26741    "});
26742
26743    // test correct indent after newline after case pattern
26744    cx.set_state(indoc! {"
26745        case \"$1\" in
26746            start)
26747                ;;
26748            *)ˇ
26749    "});
26750    cx.update_editor(|editor, window, cx| {
26751        editor.newline(&Newline, window, cx);
26752    });
26753    cx.wait_for_autoindent_applied().await;
26754    cx.assert_editor_state(indoc! {"
26755        case \"$1\" in
26756            start)
26757                ;;
26758            *)
26759                ˇ
26760    "});
26761
26762    // test correct indent after newline after function opening brace
26763    cx.set_state(indoc! {"
26764        function test() {ˇ}
26765    "});
26766    cx.update_editor(|editor, window, cx| {
26767        editor.newline(&Newline, window, cx);
26768    });
26769    cx.wait_for_autoindent_applied().await;
26770    cx.assert_editor_state(indoc! {"
26771        function test() {
26772            ˇ
26773        }
26774    "});
26775
26776    // test no extra indent after semicolon on same line
26777    cx.set_state(indoc! {"
26778        echo \"test\"26779    "});
26780    cx.update_editor(|editor, window, cx| {
26781        editor.newline(&Newline, window, cx);
26782    });
26783    cx.wait_for_autoindent_applied().await;
26784    cx.assert_editor_state(indoc! {"
26785        echo \"test\";
26786        ˇ
26787    "});
26788}
26789
26790fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26791    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26792    point..point
26793}
26794
26795#[track_caller]
26796fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26797    let (text, ranges) = marked_text_ranges(marked_text, true);
26798    assert_eq!(editor.text(cx), text);
26799    assert_eq!(
26800        editor.selections.ranges(&editor.display_snapshot(cx)),
26801        ranges
26802            .iter()
26803            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26804            .collect::<Vec<_>>(),
26805        "Assert selections are {}",
26806        marked_text
26807    );
26808}
26809
26810pub fn handle_signature_help_request(
26811    cx: &mut EditorLspTestContext,
26812    mocked_response: lsp::SignatureHelp,
26813) -> impl Future<Output = ()> + use<> {
26814    let mut request =
26815        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26816            let mocked_response = mocked_response.clone();
26817            async move { Ok(Some(mocked_response)) }
26818        });
26819
26820    async move {
26821        request.next().await;
26822    }
26823}
26824
26825#[track_caller]
26826pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26827    cx.update_editor(|editor, _, _| {
26828        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26829            let entries = menu.entries.borrow();
26830            let entries = entries
26831                .iter()
26832                .map(|entry| entry.string.as_str())
26833                .collect::<Vec<_>>();
26834            assert_eq!(entries, expected);
26835        } else {
26836            panic!("Expected completions menu");
26837        }
26838    });
26839}
26840
26841#[gpui::test]
26842async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26843    init_test(cx, |_| {});
26844    let mut cx = EditorLspTestContext::new_rust(
26845        lsp::ServerCapabilities {
26846            completion_provider: Some(lsp::CompletionOptions {
26847                ..Default::default()
26848            }),
26849            ..Default::default()
26850        },
26851        cx,
26852    )
26853    .await;
26854    cx.lsp
26855        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26856            Ok(Some(lsp::CompletionResponse::Array(vec![
26857                lsp::CompletionItem {
26858                    label: "unsafe".into(),
26859                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26860                        range: lsp::Range {
26861                            start: lsp::Position {
26862                                line: 0,
26863                                character: 9,
26864                            },
26865                            end: lsp::Position {
26866                                line: 0,
26867                                character: 11,
26868                            },
26869                        },
26870                        new_text: "unsafe".to_string(),
26871                    })),
26872                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26873                    ..Default::default()
26874                },
26875            ])))
26876        });
26877
26878    cx.update_editor(|editor, _, cx| {
26879        editor.project().unwrap().update(cx, |project, cx| {
26880            project.snippets().update(cx, |snippets, _cx| {
26881                snippets.add_snippet_for_test(
26882                    None,
26883                    PathBuf::from("test_snippets.json"),
26884                    vec![
26885                        Arc::new(project::snippet_provider::Snippet {
26886                            prefix: vec![
26887                                "unlimited word count".to_string(),
26888                                "unlimit word count".to_string(),
26889                                "unlimited unknown".to_string(),
26890                            ],
26891                            body: "this is many words".to_string(),
26892                            description: Some("description".to_string()),
26893                            name: "multi-word snippet test".to_string(),
26894                        }),
26895                        Arc::new(project::snippet_provider::Snippet {
26896                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26897                            body: "fewer words".to_string(),
26898                            description: Some("alt description".to_string()),
26899                            name: "other name".to_string(),
26900                        }),
26901                        Arc::new(project::snippet_provider::Snippet {
26902                            prefix: vec!["ab aa".to_string()],
26903                            body: "abcd".to_string(),
26904                            description: None,
26905                            name: "alphabet".to_string(),
26906                        }),
26907                    ],
26908                );
26909            });
26910        })
26911    });
26912
26913    let get_completions = |cx: &mut EditorLspTestContext| {
26914        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26915            Some(CodeContextMenu::Completions(context_menu)) => {
26916                let entries = context_menu.entries.borrow();
26917                entries
26918                    .iter()
26919                    .map(|entry| entry.string.clone())
26920                    .collect_vec()
26921            }
26922            _ => vec![],
26923        })
26924    };
26925
26926    // snippets:
26927    //  @foo
26928    //  foo bar
26929    //
26930    // when typing:
26931    //
26932    // when typing:
26933    //  - if I type a symbol "open the completions with snippets only"
26934    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26935    //
26936    // stuff we need:
26937    //  - filtering logic change?
26938    //  - remember how far back the completion started.
26939
26940    let test_cases: &[(&str, &[&str])] = &[
26941        (
26942            "un",
26943            &[
26944                "unsafe",
26945                "unlimit word count",
26946                "unlimited unknown",
26947                "unlimited word count",
26948                "unsnip",
26949            ],
26950        ),
26951        (
26952            "u ",
26953            &[
26954                "unlimit word count",
26955                "unlimited unknown",
26956                "unlimited word count",
26957            ],
26958        ),
26959        ("u a", &["ab aa", "unsafe"]), // unsAfe
26960        (
26961            "u u",
26962            &[
26963                "unsafe",
26964                "unlimit word count",
26965                "unlimited unknown", // ranked highest among snippets
26966                "unlimited word count",
26967                "unsnip",
26968            ],
26969        ),
26970        ("uw c", &["unlimit word count", "unlimited word count"]),
26971        (
26972            "u w",
26973            &[
26974                "unlimit word count",
26975                "unlimited word count",
26976                "unlimited unknown",
26977            ],
26978        ),
26979        ("u w ", &["unlimit word count", "unlimited word count"]),
26980        (
26981            "u ",
26982            &[
26983                "unlimit word count",
26984                "unlimited unknown",
26985                "unlimited word count",
26986            ],
26987        ),
26988        ("wor", &[]),
26989        ("uf", &["unsafe"]),
26990        ("af", &["unsafe"]),
26991        ("afu", &[]),
26992        (
26993            "ue",
26994            &["unsafe", "unlimited unknown", "unlimited word count"],
26995        ),
26996        ("@", &["@few"]),
26997        ("@few", &["@few"]),
26998        ("@ ", &[]),
26999        ("a@", &["@few"]),
27000        ("a@f", &["@few", "unsafe"]),
27001        ("a@fw", &["@few"]),
27002        ("a", &["ab aa", "unsafe"]),
27003        ("aa", &["ab aa"]),
27004        ("aaa", &["ab aa"]),
27005        ("ab", &["ab aa"]),
27006        ("ab ", &["ab aa"]),
27007        ("ab a", &["ab aa", "unsafe"]),
27008        ("ab ab", &["ab aa"]),
27009        ("ab ab aa", &["ab aa"]),
27010    ];
27011
27012    for &(input_to_simulate, expected_completions) in test_cases {
27013        cx.set_state("fn a() { ˇ }\n");
27014        for c in input_to_simulate.split("") {
27015            cx.simulate_input(c);
27016            cx.run_until_parked();
27017        }
27018        let expected_completions = expected_completions
27019            .iter()
27020            .map(|s| s.to_string())
27021            .collect_vec();
27022        assert_eq!(
27023            get_completions(&mut cx),
27024            expected_completions,
27025            "< actual / expected >, input = {input_to_simulate:?}",
27026        );
27027    }
27028}
27029
27030/// Handle completion request passing a marked string specifying where the completion
27031/// should be triggered from using '|' character, what range should be replaced, and what completions
27032/// should be returned using '<' and '>' to delimit the range.
27033///
27034/// Also see `handle_completion_request_with_insert_and_replace`.
27035#[track_caller]
27036pub fn handle_completion_request(
27037    marked_string: &str,
27038    completions: Vec<&'static str>,
27039    is_incomplete: bool,
27040    counter: Arc<AtomicUsize>,
27041    cx: &mut EditorLspTestContext,
27042) -> impl Future<Output = ()> {
27043    let complete_from_marker: TextRangeMarker = '|'.into();
27044    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27045    let (_, mut marked_ranges) = marked_text_ranges_by(
27046        marked_string,
27047        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27048    );
27049
27050    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27051        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27052    ));
27053    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27054    let replace_range =
27055        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27056
27057    let mut request =
27058        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27059            let completions = completions.clone();
27060            counter.fetch_add(1, atomic::Ordering::Release);
27061            async move {
27062                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27063                assert_eq!(
27064                    params.text_document_position.position,
27065                    complete_from_position
27066                );
27067                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27068                    is_incomplete,
27069                    item_defaults: None,
27070                    items: completions
27071                        .iter()
27072                        .map(|completion_text| lsp::CompletionItem {
27073                            label: completion_text.to_string(),
27074                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27075                                range: replace_range,
27076                                new_text: completion_text.to_string(),
27077                            })),
27078                            ..Default::default()
27079                        })
27080                        .collect(),
27081                })))
27082            }
27083        });
27084
27085    async move {
27086        request.next().await;
27087    }
27088}
27089
27090/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27091/// given instead, which also contains an `insert` range.
27092///
27093/// This function uses markers to define ranges:
27094/// - `|` marks the cursor position
27095/// - `<>` marks the replace range
27096/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27097pub fn handle_completion_request_with_insert_and_replace(
27098    cx: &mut EditorLspTestContext,
27099    marked_string: &str,
27100    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27101    counter: Arc<AtomicUsize>,
27102) -> impl Future<Output = ()> {
27103    let complete_from_marker: TextRangeMarker = '|'.into();
27104    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27105    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27106
27107    let (_, mut marked_ranges) = marked_text_ranges_by(
27108        marked_string,
27109        vec![
27110            complete_from_marker.clone(),
27111            replace_range_marker.clone(),
27112            insert_range_marker.clone(),
27113        ],
27114    );
27115
27116    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27117        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27118    ));
27119    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27120    let replace_range =
27121        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27122
27123    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27124        Some(ranges) if !ranges.is_empty() => {
27125            let range1 = ranges[0].clone();
27126            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27127        }
27128        _ => lsp::Range {
27129            start: replace_range.start,
27130            end: complete_from_position,
27131        },
27132    };
27133
27134    let mut request =
27135        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27136            let completions = completions.clone();
27137            counter.fetch_add(1, atomic::Ordering::Release);
27138            async move {
27139                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27140                assert_eq!(
27141                    params.text_document_position.position, complete_from_position,
27142                    "marker `|` position doesn't match",
27143                );
27144                Ok(Some(lsp::CompletionResponse::Array(
27145                    completions
27146                        .iter()
27147                        .map(|(label, new_text)| lsp::CompletionItem {
27148                            label: label.to_string(),
27149                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27150                                lsp::InsertReplaceEdit {
27151                                    insert: insert_range,
27152                                    replace: replace_range,
27153                                    new_text: new_text.to_string(),
27154                                },
27155                            )),
27156                            ..Default::default()
27157                        })
27158                        .collect(),
27159                )))
27160            }
27161        });
27162
27163    async move {
27164        request.next().await;
27165    }
27166}
27167
27168fn handle_resolve_completion_request(
27169    cx: &mut EditorLspTestContext,
27170    edits: Option<Vec<(&'static str, &'static str)>>,
27171) -> impl Future<Output = ()> {
27172    let edits = edits.map(|edits| {
27173        edits
27174            .iter()
27175            .map(|(marked_string, new_text)| {
27176                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27177                let replace_range = cx.to_lsp_range(
27178                    MultiBufferOffset(marked_ranges[0].start)
27179                        ..MultiBufferOffset(marked_ranges[0].end),
27180                );
27181                lsp::TextEdit::new(replace_range, new_text.to_string())
27182            })
27183            .collect::<Vec<_>>()
27184    });
27185
27186    let mut request =
27187        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27188            let edits = edits.clone();
27189            async move {
27190                Ok(lsp::CompletionItem {
27191                    additional_text_edits: edits,
27192                    ..Default::default()
27193                })
27194            }
27195        });
27196
27197    async move {
27198        request.next().await;
27199    }
27200}
27201
27202pub(crate) fn update_test_language_settings(
27203    cx: &mut TestAppContext,
27204    f: impl Fn(&mut AllLanguageSettingsContent),
27205) {
27206    cx.update(|cx| {
27207        SettingsStore::update_global(cx, |store, cx| {
27208            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27209        });
27210    });
27211}
27212
27213pub(crate) fn update_test_project_settings(
27214    cx: &mut TestAppContext,
27215    f: impl Fn(&mut ProjectSettingsContent),
27216) {
27217    cx.update(|cx| {
27218        SettingsStore::update_global(cx, |store, cx| {
27219            store.update_user_settings(cx, |settings| f(&mut settings.project));
27220        });
27221    });
27222}
27223
27224pub(crate) fn update_test_editor_settings(
27225    cx: &mut TestAppContext,
27226    f: impl Fn(&mut EditorSettingsContent),
27227) {
27228    cx.update(|cx| {
27229        SettingsStore::update_global(cx, |store, cx| {
27230            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27231        })
27232    })
27233}
27234
27235pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27236    cx.update(|cx| {
27237        assets::Assets.load_test_fonts(cx);
27238        let store = SettingsStore::test(cx);
27239        cx.set_global(store);
27240        theme::init(theme::LoadThemes::JustBase, cx);
27241        release_channel::init(semver::Version::new(0, 0, 0), cx);
27242        crate::init(cx);
27243    });
27244    zlog::init_test();
27245    update_test_language_settings(cx, f);
27246}
27247
27248#[track_caller]
27249fn assert_hunk_revert(
27250    not_reverted_text_with_selections: &str,
27251    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27252    expected_reverted_text_with_selections: &str,
27253    base_text: &str,
27254    cx: &mut EditorLspTestContext,
27255) {
27256    cx.set_state(not_reverted_text_with_selections);
27257    cx.set_head_text(base_text);
27258    cx.executor().run_until_parked();
27259
27260    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27261        let snapshot = editor.snapshot(window, cx);
27262        let reverted_hunk_statuses = snapshot
27263            .buffer_snapshot()
27264            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27265            .map(|hunk| hunk.status().kind)
27266            .collect::<Vec<_>>();
27267
27268        editor.git_restore(&Default::default(), window, cx);
27269        reverted_hunk_statuses
27270    });
27271    cx.executor().run_until_parked();
27272    cx.assert_editor_state(expected_reverted_text_with_selections);
27273    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27274}
27275
27276#[gpui::test(iterations = 10)]
27277async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27278    init_test(cx, |_| {});
27279
27280    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27281    let counter = diagnostic_requests.clone();
27282
27283    let fs = FakeFs::new(cx.executor());
27284    fs.insert_tree(
27285        path!("/a"),
27286        json!({
27287            "first.rs": "fn main() { let a = 5; }",
27288            "second.rs": "// Test file",
27289        }),
27290    )
27291    .await;
27292
27293    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27294    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27295    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27296
27297    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27298    language_registry.add(rust_lang());
27299    let mut fake_servers = language_registry.register_fake_lsp(
27300        "Rust",
27301        FakeLspAdapter {
27302            capabilities: lsp::ServerCapabilities {
27303                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27304                    lsp::DiagnosticOptions {
27305                        identifier: None,
27306                        inter_file_dependencies: true,
27307                        workspace_diagnostics: true,
27308                        work_done_progress_options: Default::default(),
27309                    },
27310                )),
27311                ..Default::default()
27312            },
27313            ..Default::default()
27314        },
27315    );
27316
27317    let editor = workspace
27318        .update(cx, |workspace, window, cx| {
27319            workspace.open_abs_path(
27320                PathBuf::from(path!("/a/first.rs")),
27321                OpenOptions::default(),
27322                window,
27323                cx,
27324            )
27325        })
27326        .unwrap()
27327        .await
27328        .unwrap()
27329        .downcast::<Editor>()
27330        .unwrap();
27331    let fake_server = fake_servers.next().await.unwrap();
27332    let server_id = fake_server.server.server_id();
27333    let mut first_request = fake_server
27334        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27335            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27336            let result_id = Some(new_result_id.to_string());
27337            assert_eq!(
27338                params.text_document.uri,
27339                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27340            );
27341            async move {
27342                Ok(lsp::DocumentDiagnosticReportResult::Report(
27343                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27344                        related_documents: None,
27345                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27346                            items: Vec::new(),
27347                            result_id,
27348                        },
27349                    }),
27350                ))
27351            }
27352        });
27353
27354    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27355        project.update(cx, |project, cx| {
27356            let buffer_id = editor
27357                .read(cx)
27358                .buffer()
27359                .read(cx)
27360                .as_singleton()
27361                .expect("created a singleton buffer")
27362                .read(cx)
27363                .remote_id();
27364            let buffer_result_id = project
27365                .lsp_store()
27366                .read(cx)
27367                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27368            assert_eq!(expected, buffer_result_id);
27369        });
27370    };
27371
27372    ensure_result_id(None, cx);
27373    cx.executor().advance_clock(Duration::from_millis(60));
27374    cx.executor().run_until_parked();
27375    assert_eq!(
27376        diagnostic_requests.load(atomic::Ordering::Acquire),
27377        1,
27378        "Opening file should trigger diagnostic request"
27379    );
27380    first_request
27381        .next()
27382        .await
27383        .expect("should have sent the first diagnostics pull request");
27384    ensure_result_id(Some(SharedString::new("1")), cx);
27385
27386    // Editing should trigger diagnostics
27387    editor.update_in(cx, |editor, window, cx| {
27388        editor.handle_input("2", window, cx)
27389    });
27390    cx.executor().advance_clock(Duration::from_millis(60));
27391    cx.executor().run_until_parked();
27392    assert_eq!(
27393        diagnostic_requests.load(atomic::Ordering::Acquire),
27394        2,
27395        "Editing should trigger diagnostic request"
27396    );
27397    ensure_result_id(Some(SharedString::new("2")), cx);
27398
27399    // Moving cursor should not trigger diagnostic request
27400    editor.update_in(cx, |editor, window, cx| {
27401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27402            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27403        });
27404    });
27405    cx.executor().advance_clock(Duration::from_millis(60));
27406    cx.executor().run_until_parked();
27407    assert_eq!(
27408        diagnostic_requests.load(atomic::Ordering::Acquire),
27409        2,
27410        "Cursor movement should not trigger diagnostic request"
27411    );
27412    ensure_result_id(Some(SharedString::new("2")), cx);
27413    // Multiple rapid edits should be debounced
27414    for _ in 0..5 {
27415        editor.update_in(cx, |editor, window, cx| {
27416            editor.handle_input("x", window, cx)
27417        });
27418    }
27419    cx.executor().advance_clock(Duration::from_millis(60));
27420    cx.executor().run_until_parked();
27421
27422    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27423    assert!(
27424        final_requests <= 4,
27425        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27426    );
27427    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27428}
27429
27430#[gpui::test]
27431async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27432    // Regression test for issue #11671
27433    // Previously, adding a cursor after moving multiple cursors would reset
27434    // the cursor count instead of adding to the existing cursors.
27435    init_test(cx, |_| {});
27436    let mut cx = EditorTestContext::new(cx).await;
27437
27438    // Create a simple buffer with cursor at start
27439    cx.set_state(indoc! {"
27440        ˇaaaa
27441        bbbb
27442        cccc
27443        dddd
27444        eeee
27445        ffff
27446        gggg
27447        hhhh"});
27448
27449    // Add 2 cursors below (so we have 3 total)
27450    cx.update_editor(|editor, window, cx| {
27451        editor.add_selection_below(&Default::default(), window, cx);
27452        editor.add_selection_below(&Default::default(), window, cx);
27453    });
27454
27455    // Verify we have 3 cursors
27456    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27457    assert_eq!(
27458        initial_count, 3,
27459        "Should have 3 cursors after adding 2 below"
27460    );
27461
27462    // Move down one line
27463    cx.update_editor(|editor, window, cx| {
27464        editor.move_down(&MoveDown, window, cx);
27465    });
27466
27467    // Add another cursor below
27468    cx.update_editor(|editor, window, cx| {
27469        editor.add_selection_below(&Default::default(), window, cx);
27470    });
27471
27472    // Should now have 4 cursors (3 original + 1 new)
27473    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27474    assert_eq!(
27475        final_count, 4,
27476        "Should have 4 cursors after moving and adding another"
27477    );
27478}
27479
27480#[gpui::test]
27481async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27482    init_test(cx, |_| {});
27483
27484    let mut cx = EditorTestContext::new(cx).await;
27485
27486    cx.set_state(indoc!(
27487        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27488           Second line here"#
27489    ));
27490
27491    cx.update_editor(|editor, window, cx| {
27492        // Enable soft wrapping with a narrow width to force soft wrapping and
27493        // confirm that more than 2 rows are being displayed.
27494        editor.set_wrap_width(Some(100.0.into()), cx);
27495        assert!(editor.display_text(cx).lines().count() > 2);
27496
27497        editor.add_selection_below(
27498            &AddSelectionBelow {
27499                skip_soft_wrap: true,
27500            },
27501            window,
27502            cx,
27503        );
27504
27505        assert_eq!(
27506            display_ranges(editor, cx),
27507            &[
27508                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27509                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27510            ]
27511        );
27512
27513        editor.add_selection_above(
27514            &AddSelectionAbove {
27515                skip_soft_wrap: true,
27516            },
27517            window,
27518            cx,
27519        );
27520
27521        assert_eq!(
27522            display_ranges(editor, cx),
27523            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27524        );
27525
27526        editor.add_selection_below(
27527            &AddSelectionBelow {
27528                skip_soft_wrap: false,
27529            },
27530            window,
27531            cx,
27532        );
27533
27534        assert_eq!(
27535            display_ranges(editor, cx),
27536            &[
27537                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27538                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27539            ]
27540        );
27541
27542        editor.add_selection_above(
27543            &AddSelectionAbove {
27544                skip_soft_wrap: false,
27545            },
27546            window,
27547            cx,
27548        );
27549
27550        assert_eq!(
27551            display_ranges(editor, cx),
27552            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27553        );
27554    });
27555
27556    // Set up text where selections are in the middle of a soft-wrapped line.
27557    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27558    // selection should be at the same buffer column, not the same pixel
27559    // position.
27560    cx.set_state(indoc!(
27561        r#"1. Very long line to show «howˇ» a wrapped line would look
27562           2. Very long line to show how a wrapped line would look"#
27563    ));
27564
27565    cx.update_editor(|editor, window, cx| {
27566        // Enable soft wrapping with a narrow width to force soft wrapping and
27567        // confirm that more than 2 rows are being displayed.
27568        editor.set_wrap_width(Some(100.0.into()), cx);
27569        assert!(editor.display_text(cx).lines().count() > 2);
27570
27571        editor.add_selection_below(
27572            &AddSelectionBelow {
27573                skip_soft_wrap: true,
27574            },
27575            window,
27576            cx,
27577        );
27578
27579        // Assert that there's now 2 selections, both selecting the same column
27580        // range in the buffer row.
27581        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27582        let selections = editor.selections.all::<Point>(&display_map);
27583        assert_eq!(selections.len(), 2);
27584        assert_eq!(selections[0].start.column, selections[1].start.column);
27585        assert_eq!(selections[0].end.column, selections[1].end.column);
27586    });
27587}
27588
27589#[gpui::test]
27590async fn test_insert_snippet(cx: &mut TestAppContext) {
27591    init_test(cx, |_| {});
27592    let mut cx = EditorTestContext::new(cx).await;
27593
27594    cx.update_editor(|editor, _, cx| {
27595        editor.project().unwrap().update(cx, |project, cx| {
27596            project.snippets().update(cx, |snippets, _cx| {
27597                let snippet = project::snippet_provider::Snippet {
27598                    prefix: vec![], // no prefix needed!
27599                    body: "an Unspecified".to_string(),
27600                    description: Some("shhhh it's a secret".to_string()),
27601                    name: "super secret snippet".to_string(),
27602                };
27603                snippets.add_snippet_for_test(
27604                    None,
27605                    PathBuf::from("test_snippets.json"),
27606                    vec![Arc::new(snippet)],
27607                );
27608
27609                let snippet = project::snippet_provider::Snippet {
27610                    prefix: vec![], // no prefix needed!
27611                    body: " Location".to_string(),
27612                    description: Some("the word 'location'".to_string()),
27613                    name: "location word".to_string(),
27614                };
27615                snippets.add_snippet_for_test(
27616                    Some("Markdown".to_string()),
27617                    PathBuf::from("test_snippets.json"),
27618                    vec![Arc::new(snippet)],
27619                );
27620            });
27621        })
27622    });
27623
27624    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27625
27626    cx.update_editor(|editor, window, cx| {
27627        editor.insert_snippet_at_selections(
27628            &InsertSnippet {
27629                language: None,
27630                name: Some("super secret snippet".to_string()),
27631                snippet: None,
27632            },
27633            window,
27634            cx,
27635        );
27636
27637        // Language is specified in the action,
27638        // so the buffer language does not need to match
27639        editor.insert_snippet_at_selections(
27640            &InsertSnippet {
27641                language: Some("Markdown".to_string()),
27642                name: Some("location word".to_string()),
27643                snippet: None,
27644            },
27645            window,
27646            cx,
27647        );
27648
27649        editor.insert_snippet_at_selections(
27650            &InsertSnippet {
27651                language: None,
27652                name: None,
27653                snippet: Some("$0 after".to_string()),
27654            },
27655            window,
27656            cx,
27657        );
27658    });
27659
27660    cx.assert_editor_state(
27661        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27662    );
27663}
27664
27665#[gpui::test(iterations = 10)]
27666async fn test_document_colors(cx: &mut TestAppContext) {
27667    let expected_color = Rgba {
27668        r: 0.33,
27669        g: 0.33,
27670        b: 0.33,
27671        a: 0.33,
27672    };
27673
27674    init_test(cx, |_| {});
27675
27676    let fs = FakeFs::new(cx.executor());
27677    fs.insert_tree(
27678        path!("/a"),
27679        json!({
27680            "first.rs": "fn main() { let a = 5; }",
27681        }),
27682    )
27683    .await;
27684
27685    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27686    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27687    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27688
27689    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27690    language_registry.add(rust_lang());
27691    let mut fake_servers = language_registry.register_fake_lsp(
27692        "Rust",
27693        FakeLspAdapter {
27694            capabilities: lsp::ServerCapabilities {
27695                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27696                ..lsp::ServerCapabilities::default()
27697            },
27698            name: "rust-analyzer",
27699            ..FakeLspAdapter::default()
27700        },
27701    );
27702    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27703        "Rust",
27704        FakeLspAdapter {
27705            capabilities: lsp::ServerCapabilities {
27706                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27707                ..lsp::ServerCapabilities::default()
27708            },
27709            name: "not-rust-analyzer",
27710            ..FakeLspAdapter::default()
27711        },
27712    );
27713
27714    let editor = workspace
27715        .update(cx, |workspace, window, cx| {
27716            workspace.open_abs_path(
27717                PathBuf::from(path!("/a/first.rs")),
27718                OpenOptions::default(),
27719                window,
27720                cx,
27721            )
27722        })
27723        .unwrap()
27724        .await
27725        .unwrap()
27726        .downcast::<Editor>()
27727        .unwrap();
27728    let fake_language_server = fake_servers.next().await.unwrap();
27729    let fake_language_server_without_capabilities =
27730        fake_servers_without_capabilities.next().await.unwrap();
27731    let requests_made = Arc::new(AtomicUsize::new(0));
27732    let closure_requests_made = Arc::clone(&requests_made);
27733    let mut color_request_handle = fake_language_server
27734        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27735            let requests_made = Arc::clone(&closure_requests_made);
27736            async move {
27737                assert_eq!(
27738                    params.text_document.uri,
27739                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27740                );
27741                requests_made.fetch_add(1, atomic::Ordering::Release);
27742                Ok(vec![
27743                    lsp::ColorInformation {
27744                        range: lsp::Range {
27745                            start: lsp::Position {
27746                                line: 0,
27747                                character: 0,
27748                            },
27749                            end: lsp::Position {
27750                                line: 0,
27751                                character: 1,
27752                            },
27753                        },
27754                        color: lsp::Color {
27755                            red: 0.33,
27756                            green: 0.33,
27757                            blue: 0.33,
27758                            alpha: 0.33,
27759                        },
27760                    },
27761                    lsp::ColorInformation {
27762                        range: lsp::Range {
27763                            start: lsp::Position {
27764                                line: 0,
27765                                character: 0,
27766                            },
27767                            end: lsp::Position {
27768                                line: 0,
27769                                character: 1,
27770                            },
27771                        },
27772                        color: lsp::Color {
27773                            red: 0.33,
27774                            green: 0.33,
27775                            blue: 0.33,
27776                            alpha: 0.33,
27777                        },
27778                    },
27779                ])
27780            }
27781        });
27782
27783    let _handle = fake_language_server_without_capabilities
27784        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27785            panic!("Should not be called");
27786        });
27787    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27788    color_request_handle.next().await.unwrap();
27789    cx.run_until_parked();
27790    assert_eq!(
27791        1,
27792        requests_made.load(atomic::Ordering::Acquire),
27793        "Should query for colors once per editor open"
27794    );
27795    editor.update_in(cx, |editor, _, cx| {
27796        assert_eq!(
27797            vec![expected_color],
27798            extract_color_inlays(editor, cx),
27799            "Should have an initial inlay"
27800        );
27801    });
27802
27803    // opening another file in a split should not influence the LSP query counter
27804    workspace
27805        .update(cx, |workspace, window, cx| {
27806            assert_eq!(
27807                workspace.panes().len(),
27808                1,
27809                "Should have one pane with one editor"
27810            );
27811            workspace.move_item_to_pane_in_direction(
27812                &MoveItemToPaneInDirection {
27813                    direction: SplitDirection::Right,
27814                    focus: false,
27815                    clone: true,
27816                },
27817                window,
27818                cx,
27819            );
27820        })
27821        .unwrap();
27822    cx.run_until_parked();
27823    workspace
27824        .update(cx, |workspace, _, cx| {
27825            let panes = workspace.panes();
27826            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27827            for pane in panes {
27828                let editor = pane
27829                    .read(cx)
27830                    .active_item()
27831                    .and_then(|item| item.downcast::<Editor>())
27832                    .expect("Should have opened an editor in each split");
27833                let editor_file = editor
27834                    .read(cx)
27835                    .buffer()
27836                    .read(cx)
27837                    .as_singleton()
27838                    .expect("test deals with singleton buffers")
27839                    .read(cx)
27840                    .file()
27841                    .expect("test buffese should have a file")
27842                    .path();
27843                assert_eq!(
27844                    editor_file.as_ref(),
27845                    rel_path("first.rs"),
27846                    "Both editors should be opened for the same file"
27847                )
27848            }
27849        })
27850        .unwrap();
27851
27852    cx.executor().advance_clock(Duration::from_millis(500));
27853    let save = editor.update_in(cx, |editor, window, cx| {
27854        editor.move_to_end(&MoveToEnd, window, cx);
27855        editor.handle_input("dirty", window, cx);
27856        editor.save(
27857            SaveOptions {
27858                format: true,
27859                autosave: true,
27860            },
27861            project.clone(),
27862            window,
27863            cx,
27864        )
27865    });
27866    save.await.unwrap();
27867
27868    color_request_handle.next().await.unwrap();
27869    cx.run_until_parked();
27870    assert_eq!(
27871        2,
27872        requests_made.load(atomic::Ordering::Acquire),
27873        "Should query for colors once per save (deduplicated) and once per formatting after save"
27874    );
27875
27876    drop(editor);
27877    let close = workspace
27878        .update(cx, |workspace, window, cx| {
27879            workspace.active_pane().update(cx, |pane, cx| {
27880                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27881            })
27882        })
27883        .unwrap();
27884    close.await.unwrap();
27885    let close = workspace
27886        .update(cx, |workspace, window, cx| {
27887            workspace.active_pane().update(cx, |pane, cx| {
27888                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27889            })
27890        })
27891        .unwrap();
27892    close.await.unwrap();
27893    assert_eq!(
27894        2,
27895        requests_made.load(atomic::Ordering::Acquire),
27896        "After saving and closing all editors, no extra requests should be made"
27897    );
27898    workspace
27899        .update(cx, |workspace, _, cx| {
27900            assert!(
27901                workspace.active_item(cx).is_none(),
27902                "Should close all editors"
27903            )
27904        })
27905        .unwrap();
27906
27907    workspace
27908        .update(cx, |workspace, window, cx| {
27909            workspace.active_pane().update(cx, |pane, cx| {
27910                pane.navigate_backward(&workspace::GoBack, window, cx);
27911            })
27912        })
27913        .unwrap();
27914    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27915    cx.run_until_parked();
27916    let editor = workspace
27917        .update(cx, |workspace, _, cx| {
27918            workspace
27919                .active_item(cx)
27920                .expect("Should have reopened the editor again after navigating back")
27921                .downcast::<Editor>()
27922                .expect("Should be an editor")
27923        })
27924        .unwrap();
27925
27926    assert_eq!(
27927        2,
27928        requests_made.load(atomic::Ordering::Acquire),
27929        "Cache should be reused on buffer close and reopen"
27930    );
27931    editor.update(cx, |editor, cx| {
27932        assert_eq!(
27933            vec![expected_color],
27934            extract_color_inlays(editor, cx),
27935            "Should have an initial inlay"
27936        );
27937    });
27938
27939    drop(color_request_handle);
27940    let closure_requests_made = Arc::clone(&requests_made);
27941    let mut empty_color_request_handle = fake_language_server
27942        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27943            let requests_made = Arc::clone(&closure_requests_made);
27944            async move {
27945                assert_eq!(
27946                    params.text_document.uri,
27947                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27948                );
27949                requests_made.fetch_add(1, atomic::Ordering::Release);
27950                Ok(Vec::new())
27951            }
27952        });
27953    let save = editor.update_in(cx, |editor, window, cx| {
27954        editor.move_to_end(&MoveToEnd, window, cx);
27955        editor.handle_input("dirty_again", window, cx);
27956        editor.save(
27957            SaveOptions {
27958                format: false,
27959                autosave: true,
27960            },
27961            project.clone(),
27962            window,
27963            cx,
27964        )
27965    });
27966    save.await.unwrap();
27967
27968    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27969    empty_color_request_handle.next().await.unwrap();
27970    cx.run_until_parked();
27971    assert_eq!(
27972        3,
27973        requests_made.load(atomic::Ordering::Acquire),
27974        "Should query for colors once per save only, as formatting was not requested"
27975    );
27976    editor.update(cx, |editor, cx| {
27977        assert_eq!(
27978            Vec::<Rgba>::new(),
27979            extract_color_inlays(editor, cx),
27980            "Should clear all colors when the server returns an empty response"
27981        );
27982    });
27983}
27984
27985#[gpui::test]
27986async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27987    init_test(cx, |_| {});
27988    let (editor, cx) = cx.add_window_view(Editor::single_line);
27989    editor.update_in(cx, |editor, window, cx| {
27990        editor.set_text("oops\n\nwow\n", window, cx)
27991    });
27992    cx.run_until_parked();
27993    editor.update(cx, |editor, cx| {
27994        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27995    });
27996    editor.update(cx, |editor, cx| {
27997        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27998    });
27999    cx.run_until_parked();
28000    editor.update(cx, |editor, cx| {
28001        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28002    });
28003}
28004
28005#[gpui::test]
28006async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28007    init_test(cx, |_| {});
28008
28009    cx.update(|cx| {
28010        register_project_item::<Editor>(cx);
28011    });
28012
28013    let fs = FakeFs::new(cx.executor());
28014    fs.insert_tree("/root1", json!({})).await;
28015    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28016        .await;
28017
28018    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28019    let (workspace, cx) =
28020        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
28021
28022    let worktree_id = project.update(cx, |project, cx| {
28023        project.worktrees(cx).next().unwrap().read(cx).id()
28024    });
28025
28026    let handle = workspace
28027        .update_in(cx, |workspace, window, cx| {
28028            let project_path = (worktree_id, rel_path("one.pdf"));
28029            workspace.open_path(project_path, None, true, window, cx)
28030        })
28031        .await
28032        .unwrap();
28033    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28034    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28035    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28036    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28037}
28038
28039#[gpui::test]
28040async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28041    init_test(cx, |_| {});
28042
28043    let language = Arc::new(Language::new(
28044        LanguageConfig::default(),
28045        Some(tree_sitter_rust::LANGUAGE.into()),
28046    ));
28047
28048    // Test hierarchical sibling navigation
28049    let text = r#"
28050        fn outer() {
28051            if condition {
28052                let a = 1;
28053            }
28054            let b = 2;
28055        }
28056
28057        fn another() {
28058            let c = 3;
28059        }
28060    "#;
28061
28062    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28063    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28064    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28065
28066    // Wait for parsing to complete
28067    editor
28068        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28069        .await;
28070
28071    editor.update_in(cx, |editor, window, cx| {
28072        // Start by selecting "let a = 1;" inside the if block
28073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28074            s.select_display_ranges([
28075                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28076            ]);
28077        });
28078
28079        let initial_selection = editor
28080            .selections
28081            .display_ranges(&editor.display_snapshot(cx));
28082        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28083
28084        // Test select next sibling - should move up levels to find the next sibling
28085        // Since "let a = 1;" has no siblings in the if block, it should move up
28086        // to find "let b = 2;" which is a sibling of the if block
28087        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28088        let next_selection = editor
28089            .selections
28090            .display_ranges(&editor.display_snapshot(cx));
28091
28092        // Should have a selection and it should be different from the initial
28093        assert_eq!(
28094            next_selection.len(),
28095            1,
28096            "Should have one selection after next"
28097        );
28098        assert_ne!(
28099            next_selection[0], initial_selection[0],
28100            "Next sibling selection should be different"
28101        );
28102
28103        // Test hierarchical navigation by going to the end of the current function
28104        // and trying to navigate to the next function
28105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28106            s.select_display_ranges([
28107                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28108            ]);
28109        });
28110
28111        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28112        let function_next_selection = editor
28113            .selections
28114            .display_ranges(&editor.display_snapshot(cx));
28115
28116        // Should move to the next function
28117        assert_eq!(
28118            function_next_selection.len(),
28119            1,
28120            "Should have one selection after function next"
28121        );
28122
28123        // Test select previous sibling navigation
28124        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28125        let prev_selection = editor
28126            .selections
28127            .display_ranges(&editor.display_snapshot(cx));
28128
28129        // Should have a selection and it should be different
28130        assert_eq!(
28131            prev_selection.len(),
28132            1,
28133            "Should have one selection after prev"
28134        );
28135        assert_ne!(
28136            prev_selection[0], function_next_selection[0],
28137            "Previous sibling selection should be different from next"
28138        );
28139    });
28140}
28141
28142#[gpui::test]
28143async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28144    init_test(cx, |_| {});
28145
28146    let mut cx = EditorTestContext::new(cx).await;
28147    cx.set_state(
28148        "let ˇvariable = 42;
28149let another = variable + 1;
28150let result = variable * 2;",
28151    );
28152
28153    // Set up document highlights manually (simulating LSP response)
28154    cx.update_editor(|editor, _window, cx| {
28155        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28156
28157        // Create highlights for "variable" occurrences
28158        let highlight_ranges = [
28159            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28160            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28161            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28162        ];
28163
28164        let anchor_ranges: Vec<_> = highlight_ranges
28165            .iter()
28166            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28167            .collect();
28168
28169        editor.highlight_background::<DocumentHighlightRead>(
28170            &anchor_ranges,
28171            |_, theme| theme.colors().editor_document_highlight_read_background,
28172            cx,
28173        );
28174    });
28175
28176    // Go to next highlight - should move to second "variable"
28177    cx.update_editor(|editor, window, cx| {
28178        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28179    });
28180    cx.assert_editor_state(
28181        "let variable = 42;
28182let another = ˇvariable + 1;
28183let result = variable * 2;",
28184    );
28185
28186    // Go to next highlight - should move to third "variable"
28187    cx.update_editor(|editor, window, cx| {
28188        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28189    });
28190    cx.assert_editor_state(
28191        "let variable = 42;
28192let another = variable + 1;
28193let result = ˇvariable * 2;",
28194    );
28195
28196    // Go to next highlight - should stay at third "variable" (no wrap-around)
28197    cx.update_editor(|editor, window, cx| {
28198        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28199    });
28200    cx.assert_editor_state(
28201        "let variable = 42;
28202let another = variable + 1;
28203let result = ˇvariable * 2;",
28204    );
28205
28206    // Now test going backwards from third position
28207    cx.update_editor(|editor, window, cx| {
28208        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28209    });
28210    cx.assert_editor_state(
28211        "let variable = 42;
28212let another = ˇvariable + 1;
28213let result = variable * 2;",
28214    );
28215
28216    // Go to previous highlight - should move to first "variable"
28217    cx.update_editor(|editor, window, cx| {
28218        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28219    });
28220    cx.assert_editor_state(
28221        "let ˇvariable = 42;
28222let another = variable + 1;
28223let result = variable * 2;",
28224    );
28225
28226    // Go to previous highlight - should stay on first "variable"
28227    cx.update_editor(|editor, window, cx| {
28228        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28229    });
28230    cx.assert_editor_state(
28231        "let ˇvariable = 42;
28232let another = variable + 1;
28233let result = variable * 2;",
28234    );
28235}
28236
28237#[gpui::test]
28238async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28239    cx: &mut gpui::TestAppContext,
28240) {
28241    init_test(cx, |_| {});
28242
28243    let url = "https://zed.dev";
28244
28245    let markdown_language = Arc::new(Language::new(
28246        LanguageConfig {
28247            name: "Markdown".into(),
28248            ..LanguageConfig::default()
28249        },
28250        None,
28251    ));
28252
28253    let mut cx = EditorTestContext::new(cx).await;
28254    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28255    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28256
28257    cx.update_editor(|editor, window, cx| {
28258        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28259        editor.paste(&Paste, window, cx);
28260    });
28261
28262    cx.assert_editor_state(&format!(
28263        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28264    ));
28265}
28266
28267#[gpui::test]
28268async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28269    init_test(cx, |_| {});
28270
28271    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28272    let mut cx = EditorTestContext::new(cx).await;
28273
28274    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28275
28276    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28277    cx.set_state(&indoc! {"
28278        - [ ] Item 1
28279            - [ ] Item 1.a
28280        - [ˇ] Item 2
28281            - [ˇ] Item 2.a
28282            - [ˇ] Item 2.b
28283        "
28284    });
28285    cx.update_editor(|editor, window, cx| {
28286        editor.handle_input("x", window, cx);
28287    });
28288    cx.run_until_parked();
28289    cx.assert_editor_state(indoc! {"
28290        - [ ] Item 1
28291            - [ ] Item 1.a
28292        - [xˇ] Item 2
28293            - [xˇ] Item 2.a
28294            - [xˇ] Item 2.b
28295        "
28296    });
28297
28298    // Case 2: Test adding new line after nested list continues the list with unchecked task
28299    cx.set_state(&indoc! {"
28300        - [ ] Item 1
28301            - [ ] Item 1.a
28302        - [x] Item 2
28303            - [x] Item 2.a
28304            - [x] Item 2.bˇ"
28305    });
28306    cx.update_editor(|editor, window, cx| {
28307        editor.newline(&Newline, window, cx);
28308    });
28309    cx.assert_editor_state(indoc! {"
28310        - [ ] Item 1
28311            - [ ] Item 1.a
28312        - [x] Item 2
28313            - [x] Item 2.a
28314            - [x] Item 2.b
28315            - [ ] ˇ"
28316    });
28317
28318    // Case 3: Test adding content to continued list item
28319    cx.update_editor(|editor, window, cx| {
28320        editor.handle_input("Item 2.c", window, cx);
28321    });
28322    cx.run_until_parked();
28323    cx.assert_editor_state(indoc! {"
28324        - [ ] Item 1
28325            - [ ] Item 1.a
28326        - [x] Item 2
28327            - [x] Item 2.a
28328            - [x] Item 2.b
28329            - [ ] Item 2.cˇ"
28330    });
28331
28332    // Case 4: Test adding new line after nested ordered list continues with next number
28333    cx.set_state(indoc! {"
28334        1. Item 1
28335            1. Item 1.a
28336        2. Item 2
28337            1. Item 2.a
28338            2. Item 2.bˇ"
28339    });
28340    cx.update_editor(|editor, window, cx| {
28341        editor.newline(&Newline, window, cx);
28342    });
28343    cx.assert_editor_state(indoc! {"
28344        1. Item 1
28345            1. Item 1.a
28346        2. Item 2
28347            1. Item 2.a
28348            2. Item 2.b
28349            3. ˇ"
28350    });
28351
28352    // Case 5: Adding content to continued ordered list item
28353    cx.update_editor(|editor, window, cx| {
28354        editor.handle_input("Item 2.c", window, cx);
28355    });
28356    cx.run_until_parked();
28357    cx.assert_editor_state(indoc! {"
28358        1. Item 1
28359            1. Item 1.a
28360        2. Item 2
28361            1. Item 2.a
28362            2. Item 2.b
28363            3. Item 2.cˇ"
28364    });
28365
28366    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28367    cx.set_state(indoc! {"
28368        - Item 1
28369            - Item 1.a
28370            - Item 1.a
28371        ˇ"});
28372    cx.update_editor(|editor, window, cx| {
28373        editor.handle_input("-", window, cx);
28374    });
28375    cx.run_until_parked();
28376    cx.assert_editor_state(indoc! {"
28377        - Item 1
28378            - Item 1.a
28379            - Item 1.a
28380"});
28381
28382    // Case 7: Test blockquote newline preserves something
28383    cx.set_state(indoc! {"
28384        > Item 1ˇ"
28385    });
28386    cx.update_editor(|editor, window, cx| {
28387        editor.newline(&Newline, window, cx);
28388    });
28389    cx.assert_editor_state(indoc! {"
28390        > Item 1
28391        ˇ"
28392    });
28393}
28394
28395#[gpui::test]
28396async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28397    cx: &mut gpui::TestAppContext,
28398) {
28399    init_test(cx, |_| {});
28400
28401    let url = "https://zed.dev";
28402
28403    let markdown_language = Arc::new(Language::new(
28404        LanguageConfig {
28405            name: "Markdown".into(),
28406            ..LanguageConfig::default()
28407        },
28408        None,
28409    ));
28410
28411    let mut cx = EditorTestContext::new(cx).await;
28412    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28413    cx.set_state(&format!(
28414        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28415    ));
28416
28417    cx.update_editor(|editor, window, cx| {
28418        editor.copy(&Copy, window, cx);
28419    });
28420
28421    cx.set_state(&format!(
28422        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28423    ));
28424
28425    cx.update_editor(|editor, window, cx| {
28426        editor.paste(&Paste, window, cx);
28427    });
28428
28429    cx.assert_editor_state(&format!(
28430        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28431    ));
28432}
28433
28434#[gpui::test]
28435async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28436    cx: &mut gpui::TestAppContext,
28437) {
28438    init_test(cx, |_| {});
28439
28440    let url = "https://zed.dev";
28441
28442    let markdown_language = Arc::new(Language::new(
28443        LanguageConfig {
28444            name: "Markdown".into(),
28445            ..LanguageConfig::default()
28446        },
28447        None,
28448    ));
28449
28450    let mut cx = EditorTestContext::new(cx).await;
28451    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28452    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28453
28454    cx.update_editor(|editor, window, cx| {
28455        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28456        editor.paste(&Paste, window, cx);
28457    });
28458
28459    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28460}
28461
28462#[gpui::test]
28463async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28464    cx: &mut gpui::TestAppContext,
28465) {
28466    init_test(cx, |_| {});
28467
28468    let text = "Awesome";
28469
28470    let markdown_language = Arc::new(Language::new(
28471        LanguageConfig {
28472            name: "Markdown".into(),
28473            ..LanguageConfig::default()
28474        },
28475        None,
28476    ));
28477
28478    let mut cx = EditorTestContext::new(cx).await;
28479    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28480    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28481
28482    cx.update_editor(|editor, window, cx| {
28483        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28484        editor.paste(&Paste, window, cx);
28485    });
28486
28487    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28488}
28489
28490#[gpui::test]
28491async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28492    cx: &mut gpui::TestAppContext,
28493) {
28494    init_test(cx, |_| {});
28495
28496    let url = "https://zed.dev";
28497
28498    let markdown_language = Arc::new(Language::new(
28499        LanguageConfig {
28500            name: "Rust".into(),
28501            ..LanguageConfig::default()
28502        },
28503        None,
28504    ));
28505
28506    let mut cx = EditorTestContext::new(cx).await;
28507    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28508    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28509
28510    cx.update_editor(|editor, window, cx| {
28511        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28512        editor.paste(&Paste, window, cx);
28513    });
28514
28515    cx.assert_editor_state(&format!(
28516        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28517    ));
28518}
28519
28520#[gpui::test]
28521async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28522    cx: &mut TestAppContext,
28523) {
28524    init_test(cx, |_| {});
28525
28526    let url = "https://zed.dev";
28527
28528    let markdown_language = Arc::new(Language::new(
28529        LanguageConfig {
28530            name: "Markdown".into(),
28531            ..LanguageConfig::default()
28532        },
28533        None,
28534    ));
28535
28536    let (editor, cx) = cx.add_window_view(|window, cx| {
28537        let multi_buffer = MultiBuffer::build_multi(
28538            [
28539                ("this will embed -> link", vec![Point::row_range(0..1)]),
28540                ("this will replace -> link", vec![Point::row_range(0..1)]),
28541            ],
28542            cx,
28543        );
28544        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28546            s.select_ranges(vec![
28547                Point::new(0, 19)..Point::new(0, 23),
28548                Point::new(1, 21)..Point::new(1, 25),
28549            ])
28550        });
28551        let first_buffer_id = multi_buffer
28552            .read(cx)
28553            .excerpt_buffer_ids()
28554            .into_iter()
28555            .next()
28556            .unwrap();
28557        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28558        first_buffer.update(cx, |buffer, cx| {
28559            buffer.set_language(Some(markdown_language.clone()), cx);
28560        });
28561
28562        editor
28563    });
28564    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28565
28566    cx.update_editor(|editor, window, cx| {
28567        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28568        editor.paste(&Paste, window, cx);
28569    });
28570
28571    cx.assert_editor_state(&format!(
28572        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28573    ));
28574}
28575
28576#[gpui::test]
28577async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28578    init_test(cx, |_| {});
28579
28580    let fs = FakeFs::new(cx.executor());
28581    fs.insert_tree(
28582        path!("/project"),
28583        json!({
28584            "first.rs": "# First Document\nSome content here.",
28585            "second.rs": "Plain text content for second file.",
28586        }),
28587    )
28588    .await;
28589
28590    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28591    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28592    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28593
28594    let language = rust_lang();
28595    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28596    language_registry.add(language.clone());
28597    let mut fake_servers = language_registry.register_fake_lsp(
28598        "Rust",
28599        FakeLspAdapter {
28600            ..FakeLspAdapter::default()
28601        },
28602    );
28603
28604    let buffer1 = project
28605        .update(cx, |project, cx| {
28606            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28607        })
28608        .await
28609        .unwrap();
28610    let buffer2 = project
28611        .update(cx, |project, cx| {
28612            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28613        })
28614        .await
28615        .unwrap();
28616
28617    let multi_buffer = cx.new(|cx| {
28618        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28619        multi_buffer.set_excerpts_for_path(
28620            PathKey::for_buffer(&buffer1, cx),
28621            buffer1.clone(),
28622            [Point::zero()..buffer1.read(cx).max_point()],
28623            3,
28624            cx,
28625        );
28626        multi_buffer.set_excerpts_for_path(
28627            PathKey::for_buffer(&buffer2, cx),
28628            buffer2.clone(),
28629            [Point::zero()..buffer1.read(cx).max_point()],
28630            3,
28631            cx,
28632        );
28633        multi_buffer
28634    });
28635
28636    let (editor, cx) = cx.add_window_view(|window, cx| {
28637        Editor::new(
28638            EditorMode::full(),
28639            multi_buffer,
28640            Some(project.clone()),
28641            window,
28642            cx,
28643        )
28644    });
28645
28646    let fake_language_server = fake_servers.next().await.unwrap();
28647
28648    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28649
28650    let save = editor.update_in(cx, |editor, window, cx| {
28651        assert!(editor.is_dirty(cx));
28652
28653        editor.save(
28654            SaveOptions {
28655                format: true,
28656                autosave: true,
28657            },
28658            project,
28659            window,
28660            cx,
28661        )
28662    });
28663    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28664    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28665    let mut done_edit_rx = Some(done_edit_rx);
28666    let mut start_edit_tx = Some(start_edit_tx);
28667
28668    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28669        start_edit_tx.take().unwrap().send(()).unwrap();
28670        let done_edit_rx = done_edit_rx.take().unwrap();
28671        async move {
28672            done_edit_rx.await.unwrap();
28673            Ok(None)
28674        }
28675    });
28676
28677    start_edit_rx.await.unwrap();
28678    buffer2
28679        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28680        .unwrap();
28681
28682    done_edit_tx.send(()).unwrap();
28683
28684    save.await.unwrap();
28685    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28686}
28687
28688#[track_caller]
28689fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28690    editor
28691        .all_inlays(cx)
28692        .into_iter()
28693        .filter_map(|inlay| inlay.get_color())
28694        .map(Rgba::from)
28695        .collect()
28696}
28697
28698#[gpui::test]
28699fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28700    init_test(cx, |_| {});
28701
28702    let editor = cx.add_window(|window, cx| {
28703        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28704        build_editor(buffer, window, cx)
28705    });
28706
28707    editor
28708        .update(cx, |editor, window, cx| {
28709            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28710                s.select_display_ranges([
28711                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28712                ])
28713            });
28714
28715            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28716
28717            assert_eq!(
28718                editor.display_text(cx),
28719                "line1\nline2\nline2",
28720                "Duplicating last line upward should create duplicate above, not on same line"
28721            );
28722
28723            assert_eq!(
28724                editor
28725                    .selections
28726                    .display_ranges(&editor.display_snapshot(cx)),
28727                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28728                "Selection should move to the duplicated line"
28729            );
28730        })
28731        .unwrap();
28732}
28733
28734#[gpui::test]
28735async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28736    init_test(cx, |_| {});
28737
28738    let mut cx = EditorTestContext::new(cx).await;
28739
28740    cx.set_state("line1\nline2ˇ");
28741
28742    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28743
28744    let clipboard_text = cx
28745        .read_from_clipboard()
28746        .and_then(|item| item.text().as_deref().map(str::to_string));
28747
28748    assert_eq!(
28749        clipboard_text,
28750        Some("line2\n".to_string()),
28751        "Copying a line without trailing newline should include a newline"
28752    );
28753
28754    cx.set_state("line1\nˇ");
28755
28756    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28757
28758    cx.assert_editor_state("line1\nline2\nˇ");
28759}
28760
28761#[gpui::test]
28762async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28763    init_test(cx, |_| {});
28764
28765    let mut cx = EditorTestContext::new(cx).await;
28766
28767    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28768
28769    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28770
28771    let clipboard_text = cx
28772        .read_from_clipboard()
28773        .and_then(|item| item.text().as_deref().map(str::to_string));
28774
28775    assert_eq!(
28776        clipboard_text,
28777        Some("line1\nline2\nline3\n".to_string()),
28778        "Copying multiple lines should include a single newline between lines"
28779    );
28780
28781    cx.set_state("lineA\nˇ");
28782
28783    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28784
28785    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28786}
28787
28788#[gpui::test]
28789async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28790    init_test(cx, |_| {});
28791
28792    let mut cx = EditorTestContext::new(cx).await;
28793
28794    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28795
28796    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28797
28798    let clipboard_text = cx
28799        .read_from_clipboard()
28800        .and_then(|item| item.text().as_deref().map(str::to_string));
28801
28802    assert_eq!(
28803        clipboard_text,
28804        Some("line1\nline2\nline3\n".to_string()),
28805        "Copying multiple lines should include a single newline between lines"
28806    );
28807
28808    cx.set_state("lineA\nˇ");
28809
28810    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28811
28812    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28813}
28814
28815#[gpui::test]
28816async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28817    init_test(cx, |_| {});
28818
28819    let mut cx = EditorTestContext::new(cx).await;
28820
28821    cx.set_state("line1\nline2ˇ");
28822    cx.update_editor(|e, window, cx| {
28823        e.set_mode(EditorMode::SingleLine);
28824        assert!(e.key_context(window, cx).contains("end_of_input"));
28825    });
28826    cx.set_state("ˇline1\nline2");
28827    cx.update_editor(|e, window, cx| {
28828        assert!(!e.key_context(window, cx).contains("end_of_input"));
28829    });
28830    cx.set_state("line1ˇ\nline2");
28831    cx.update_editor(|e, window, cx| {
28832        assert!(!e.key_context(window, cx).contains("end_of_input"));
28833    });
28834}
28835
28836#[gpui::test]
28837async fn test_sticky_scroll(cx: &mut TestAppContext) {
28838    init_test(cx, |_| {});
28839    let mut cx = EditorTestContext::new(cx).await;
28840
28841    let buffer = indoc! {"
28842            ˇfn foo() {
28843                let abc = 123;
28844            }
28845            struct Bar;
28846            impl Bar {
28847                fn new() -> Self {
28848                    Self
28849                }
28850            }
28851            fn baz() {
28852            }
28853        "};
28854    cx.set_state(&buffer);
28855
28856    cx.update_editor(|e, _, cx| {
28857        e.buffer()
28858            .read(cx)
28859            .as_singleton()
28860            .unwrap()
28861            .update(cx, |buffer, cx| {
28862                buffer.set_language(Some(rust_lang()), cx);
28863            })
28864    });
28865
28866    let mut sticky_headers = |offset: ScrollOffset| {
28867        cx.update_editor(|e, window, cx| {
28868            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28869            let style = e.style(cx).clone();
28870            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28871                .into_iter()
28872                .map(
28873                    |StickyHeader {
28874                         start_point,
28875                         offset,
28876                         ..
28877                     }| { (start_point, offset) },
28878                )
28879                .collect::<Vec<_>>()
28880        })
28881    };
28882
28883    let fn_foo = Point { row: 0, column: 0 };
28884    let impl_bar = Point { row: 4, column: 0 };
28885    let fn_new = Point { row: 5, column: 4 };
28886
28887    assert_eq!(sticky_headers(0.0), vec![]);
28888    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28889    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28890    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28891    assert_eq!(sticky_headers(2.0), vec![]);
28892    assert_eq!(sticky_headers(2.5), vec![]);
28893    assert_eq!(sticky_headers(3.0), vec![]);
28894    assert_eq!(sticky_headers(3.5), vec![]);
28895    assert_eq!(sticky_headers(4.0), vec![]);
28896    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28897    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28898    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28899    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28900    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28901    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28902    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28903    assert_eq!(sticky_headers(8.0), vec![]);
28904    assert_eq!(sticky_headers(8.5), vec![]);
28905    assert_eq!(sticky_headers(9.0), vec![]);
28906    assert_eq!(sticky_headers(9.5), vec![]);
28907    assert_eq!(sticky_headers(10.0), vec![]);
28908}
28909
28910#[gpui::test]
28911fn test_relative_line_numbers(cx: &mut TestAppContext) {
28912    init_test(cx, |_| {});
28913
28914    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
28915    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
28916    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
28917
28918    let multibuffer = cx.new(|cx| {
28919        let mut multibuffer = MultiBuffer::new(ReadWrite);
28920        multibuffer.push_excerpts(
28921            buffer_1.clone(),
28922            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28923            cx,
28924        );
28925        multibuffer.push_excerpts(
28926            buffer_2.clone(),
28927            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28928            cx,
28929        );
28930        multibuffer.push_excerpts(
28931            buffer_3.clone(),
28932            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28933            cx,
28934        );
28935        multibuffer
28936    });
28937
28938    // wrapped contents of multibuffer:
28939    //    aaa
28940    //    aaa
28941    //    aaa
28942    //    a
28943    //    bbb
28944    //
28945    //    ccc
28946    //    ccc
28947    //    ccc
28948    //    c
28949    //    ddd
28950    //
28951    //    eee
28952    //    fff
28953    //    fff
28954    //    fff
28955    //    f
28956
28957    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
28958    _ = editor.update(cx, |editor, window, cx| {
28959        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
28960
28961        // includes trailing newlines.
28962        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
28963        let expected_wrapped_line_numbers = [
28964            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
28965        ];
28966
28967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28968            s.select_ranges([
28969                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
28970            ]);
28971        });
28972
28973        let snapshot = editor.snapshot(window, cx);
28974
28975        // these are all 0-indexed
28976        let base_display_row = DisplayRow(11);
28977        let base_row = 3;
28978        let wrapped_base_row = 7;
28979
28980        // test not counting wrapped lines
28981        let expected_relative_numbers = expected_line_numbers
28982            .into_iter()
28983            .enumerate()
28984            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
28985            .collect_vec();
28986        let actual_relative_numbers = snapshot
28987            .calculate_relative_line_numbers(
28988                &(DisplayRow(0)..DisplayRow(24)),
28989                base_display_row,
28990                false,
28991            )
28992            .into_iter()
28993            .sorted()
28994            .collect_vec();
28995        assert_eq!(expected_relative_numbers, actual_relative_numbers);
28996        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
28997        for (display_row, relative_number) in expected_relative_numbers {
28998            assert_eq!(
28999                relative_number,
29000                snapshot
29001                    .relative_line_delta(display_row, base_display_row, false)
29002                    .unsigned_abs() as u32,
29003            );
29004        }
29005
29006        // test counting wrapped lines
29007        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29008            .into_iter()
29009            .enumerate()
29010            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29011            .filter(|(row, _)| *row != base_display_row)
29012            .collect_vec();
29013        let actual_relative_numbers = snapshot
29014            .calculate_relative_line_numbers(
29015                &(DisplayRow(0)..DisplayRow(24)),
29016                base_display_row,
29017                true,
29018            )
29019            .into_iter()
29020            .sorted()
29021            .collect_vec();
29022        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29023        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29024        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29025            assert_eq!(
29026                relative_number,
29027                snapshot
29028                    .relative_line_delta(display_row, base_display_row, true)
29029                    .unsigned_abs() as u32,
29030            );
29031        }
29032    });
29033}
29034
29035#[gpui::test]
29036async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29037    init_test(cx, |_| {});
29038    cx.update(|cx| {
29039        SettingsStore::update_global(cx, |store, cx| {
29040            store.update_user_settings(cx, |settings| {
29041                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29042                    enabled: Some(true),
29043                })
29044            });
29045        });
29046    });
29047    let mut cx = EditorTestContext::new(cx).await;
29048
29049    let line_height = cx.update_editor(|editor, window, cx| {
29050        editor
29051            .style(cx)
29052            .text
29053            .line_height_in_pixels(window.rem_size())
29054    });
29055
29056    let buffer = indoc! {"
29057            ˇfn foo() {
29058                let abc = 123;
29059            }
29060            struct Bar;
29061            impl Bar {
29062                fn new() -> Self {
29063                    Self
29064                }
29065            }
29066            fn baz() {
29067            }
29068        "};
29069    cx.set_state(&buffer);
29070
29071    cx.update_editor(|e, _, cx| {
29072        e.buffer()
29073            .read(cx)
29074            .as_singleton()
29075            .unwrap()
29076            .update(cx, |buffer, cx| {
29077                buffer.set_language(Some(rust_lang()), cx);
29078            })
29079    });
29080
29081    let fn_foo = || empty_range(0, 0);
29082    let impl_bar = || empty_range(4, 0);
29083    let fn_new = || empty_range(5, 4);
29084
29085    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29086        cx.update_editor(|e, window, cx| {
29087            e.scroll(
29088                gpui::Point {
29089                    x: 0.,
29090                    y: scroll_offset,
29091                },
29092                None,
29093                window,
29094                cx,
29095            );
29096        });
29097        cx.simulate_click(
29098            gpui::Point {
29099                x: px(0.),
29100                y: click_offset as f32 * line_height,
29101            },
29102            Modifiers::none(),
29103        );
29104        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29105    };
29106
29107    assert_eq!(
29108        scroll_and_click(
29109            4.5, // impl Bar is halfway off the screen
29110            0.0  // click top of screen
29111        ),
29112        // scrolled to impl Bar
29113        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29114    );
29115
29116    assert_eq!(
29117        scroll_and_click(
29118            4.5,  // impl Bar is halfway off the screen
29119            0.25  // click middle of impl Bar
29120        ),
29121        // scrolled to impl Bar
29122        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29123    );
29124
29125    assert_eq!(
29126        scroll_and_click(
29127            4.5, // impl Bar is halfway off the screen
29128            1.5  // click below impl Bar (e.g. fn new())
29129        ),
29130        // scrolled to fn new() - this is below the impl Bar header which has persisted
29131        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29132    );
29133
29134    assert_eq!(
29135        scroll_and_click(
29136            5.5,  // fn new is halfway underneath impl Bar
29137            0.75  // click on the overlap of impl Bar and fn new()
29138        ),
29139        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29140    );
29141
29142    assert_eq!(
29143        scroll_and_click(
29144            5.5,  // fn new is halfway underneath impl Bar
29145            1.25  // click on the visible part of fn new()
29146        ),
29147        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29148    );
29149
29150    assert_eq!(
29151        scroll_and_click(
29152            1.5, // fn foo is halfway off the screen
29153            0.0  // click top of screen
29154        ),
29155        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29156    );
29157
29158    assert_eq!(
29159        scroll_and_click(
29160            1.5,  // fn foo is halfway off the screen
29161            0.75  // click visible part of let abc...
29162        )
29163        .0,
29164        // no change in scroll
29165        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29166        (gpui::Point { x: 0., y: 1.5 })
29167    );
29168}
29169
29170#[gpui::test]
29171async fn test_next_prev_reference(cx: &mut TestAppContext) {
29172    const CYCLE_POSITIONS: &[&'static str] = &[
29173        indoc! {"
29174            fn foo() {
29175                let ˇabc = 123;
29176                let x = abc + 1;
29177                let y = abc + 2;
29178                let z = abc + 2;
29179            }
29180        "},
29181        indoc! {"
29182            fn foo() {
29183                let abc = 123;
29184                let x = ˇabc + 1;
29185                let y = abc + 2;
29186                let z = abc + 2;
29187            }
29188        "},
29189        indoc! {"
29190            fn foo() {
29191                let abc = 123;
29192                let x = abc + 1;
29193                let y = ˇabc + 2;
29194                let z = abc + 2;
29195            }
29196        "},
29197        indoc! {"
29198            fn foo() {
29199                let abc = 123;
29200                let x = abc + 1;
29201                let y = abc + 2;
29202                let z = ˇabc + 2;
29203            }
29204        "},
29205    ];
29206
29207    init_test(cx, |_| {});
29208
29209    let mut cx = EditorLspTestContext::new_rust(
29210        lsp::ServerCapabilities {
29211            references_provider: Some(lsp::OneOf::Left(true)),
29212            ..Default::default()
29213        },
29214        cx,
29215    )
29216    .await;
29217
29218    // importantly, the cursor is in the middle
29219    cx.set_state(indoc! {"
29220        fn foo() {
29221            let aˇbc = 123;
29222            let x = abc + 1;
29223            let y = abc + 2;
29224            let z = abc + 2;
29225        }
29226    "});
29227
29228    let reference_ranges = [
29229        lsp::Position::new(1, 8),
29230        lsp::Position::new(2, 12),
29231        lsp::Position::new(3, 12),
29232        lsp::Position::new(4, 12),
29233    ]
29234    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29235
29236    cx.lsp
29237        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29238            Ok(Some(
29239                reference_ranges
29240                    .map(|range| lsp::Location {
29241                        uri: params.text_document_position.text_document.uri.clone(),
29242                        range,
29243                    })
29244                    .to_vec(),
29245            ))
29246        });
29247
29248    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29249        cx.update_editor(|editor, window, cx| {
29250            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29251        })
29252        .unwrap()
29253        .await
29254        .unwrap()
29255    };
29256
29257    _move(Direction::Next, 1, &mut cx).await;
29258    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29259
29260    _move(Direction::Next, 1, &mut cx).await;
29261    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29262
29263    _move(Direction::Next, 1, &mut cx).await;
29264    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29265
29266    // loops back to the start
29267    _move(Direction::Next, 1, &mut cx).await;
29268    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29269
29270    // loops back to the end
29271    _move(Direction::Prev, 1, &mut cx).await;
29272    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29273
29274    _move(Direction::Prev, 1, &mut cx).await;
29275    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29276
29277    _move(Direction::Prev, 1, &mut cx).await;
29278    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29279
29280    _move(Direction::Prev, 1, &mut cx).await;
29281    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29282
29283    _move(Direction::Next, 3, &mut cx).await;
29284    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29285
29286    _move(Direction::Prev, 2, &mut cx).await;
29287    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29288}
29289
29290#[gpui::test]
29291async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29292    init_test(cx, |_| {});
29293
29294    let (editor, cx) = cx.add_window_view(|window, cx| {
29295        let multi_buffer = MultiBuffer::build_multi(
29296            [
29297                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29298                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29299            ],
29300            cx,
29301        );
29302        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29303    });
29304
29305    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29306    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29307
29308    cx.assert_excerpts_with_selections(indoc! {"
29309        [EXCERPT]
29310        ˇ1
29311        2
29312        3
29313        [EXCERPT]
29314        1
29315        2
29316        3
29317        "});
29318
29319    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29320    cx.update_editor(|editor, window, cx| {
29321        editor.change_selections(None.into(), window, cx, |s| {
29322            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29323        });
29324    });
29325    cx.assert_excerpts_with_selections(indoc! {"
29326        [EXCERPT]
29327        1
2932829329        3
29330        [EXCERPT]
29331        1
29332        2
29333        3
29334        "});
29335
29336    cx.update_editor(|editor, window, cx| {
29337        editor
29338            .select_all_matches(&SelectAllMatches, window, cx)
29339            .unwrap();
29340    });
29341    cx.assert_excerpts_with_selections(indoc! {"
29342        [EXCERPT]
29343        1
2934429345        3
29346        [EXCERPT]
29347        1
2934829349        3
29350        "});
29351
29352    cx.update_editor(|editor, window, cx| {
29353        editor.handle_input("X", window, cx);
29354    });
29355    cx.assert_excerpts_with_selections(indoc! {"
29356        [EXCERPT]
29357        1
2935829359        3
29360        [EXCERPT]
29361        1
2936229363        3
29364        "});
29365
29366    // Scenario 2: Select "2", then fold second buffer before insertion
29367    cx.update_multibuffer(|mb, cx| {
29368        for buffer_id in buffer_ids.iter() {
29369            let buffer = mb.buffer(*buffer_id).unwrap();
29370            buffer.update(cx, |buffer, cx| {
29371                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29372            });
29373        }
29374    });
29375
29376    // Select "2" and select all matches
29377    cx.update_editor(|editor, window, cx| {
29378        editor.change_selections(None.into(), window, cx, |s| {
29379            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29380        });
29381        editor
29382            .select_all_matches(&SelectAllMatches, window, cx)
29383            .unwrap();
29384    });
29385
29386    // Fold second buffer - should remove selections from folded buffer
29387    cx.update_editor(|editor, _, cx| {
29388        editor.fold_buffer(buffer_ids[1], cx);
29389    });
29390    cx.assert_excerpts_with_selections(indoc! {"
29391        [EXCERPT]
29392        1
2939329394        3
29395        [EXCERPT]
29396        [FOLDED]
29397        "});
29398
29399    // Insert text - should only affect first buffer
29400    cx.update_editor(|editor, window, cx| {
29401        editor.handle_input("Y", window, cx);
29402    });
29403    cx.update_editor(|editor, _, cx| {
29404        editor.unfold_buffer(buffer_ids[1], cx);
29405    });
29406    cx.assert_excerpts_with_selections(indoc! {"
29407        [EXCERPT]
29408        1
2940929410        3
29411        [EXCERPT]
29412        1
29413        2
29414        3
29415        "});
29416
29417    // Scenario 3: Select "2", then fold first buffer before insertion
29418    cx.update_multibuffer(|mb, cx| {
29419        for buffer_id in buffer_ids.iter() {
29420            let buffer = mb.buffer(*buffer_id).unwrap();
29421            buffer.update(cx, |buffer, cx| {
29422                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29423            });
29424        }
29425    });
29426
29427    // Select "2" and select all matches
29428    cx.update_editor(|editor, window, cx| {
29429        editor.change_selections(None.into(), window, cx, |s| {
29430            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29431        });
29432        editor
29433            .select_all_matches(&SelectAllMatches, window, cx)
29434            .unwrap();
29435    });
29436
29437    // Fold first buffer - should remove selections from folded buffer
29438    cx.update_editor(|editor, _, cx| {
29439        editor.fold_buffer(buffer_ids[0], cx);
29440    });
29441    cx.assert_excerpts_with_selections(indoc! {"
29442        [EXCERPT]
29443        [FOLDED]
29444        [EXCERPT]
29445        1
2944629447        3
29448        "});
29449
29450    // Insert text - should only affect second buffer
29451    cx.update_editor(|editor, window, cx| {
29452        editor.handle_input("Z", window, cx);
29453    });
29454    cx.update_editor(|editor, _, cx| {
29455        editor.unfold_buffer(buffer_ids[0], cx);
29456    });
29457    cx.assert_excerpts_with_selections(indoc! {"
29458        [EXCERPT]
29459        1
29460        2
29461        3
29462        [EXCERPT]
29463        1
2946429465        3
29466        "});
29467
29468    // Test correct folded header is selected upon fold
29469    cx.update_editor(|editor, _, cx| {
29470        editor.fold_buffer(buffer_ids[0], cx);
29471        editor.fold_buffer(buffer_ids[1], cx);
29472    });
29473    cx.assert_excerpts_with_selections(indoc! {"
29474        [EXCERPT]
29475        [FOLDED]
29476        [EXCERPT]
29477        ˇ[FOLDED]
29478        "});
29479
29480    // Test selection inside folded buffer unfolds it on type
29481    cx.update_editor(|editor, window, cx| {
29482        editor.handle_input("W", window, cx);
29483    });
29484    cx.update_editor(|editor, _, cx| {
29485        editor.unfold_buffer(buffer_ids[0], cx);
29486    });
29487    cx.assert_excerpts_with_selections(indoc! {"
29488        [EXCERPT]
29489        1
29490        2
29491        3
29492        [EXCERPT]
29493        Wˇ1
29494        Z
29495        3
29496        "});
29497}
29498
29499#[gpui::test]
29500async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29501    init_test(cx, |_| {});
29502
29503    let (editor, cx) = cx.add_window_view(|window, cx| {
29504        let multi_buffer = MultiBuffer::build_multi(
29505            [
29506                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29507                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29508            ],
29509            cx,
29510        );
29511        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29512    });
29513
29514    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29515
29516    cx.assert_excerpts_with_selections(indoc! {"
29517        [EXCERPT]
29518        ˇ1
29519        2
29520        3
29521        [EXCERPT]
29522        1
29523        2
29524        3
29525        4
29526        5
29527        6
29528        7
29529        8
29530        9
29531        "});
29532
29533    cx.update_editor(|editor, window, cx| {
29534        editor.change_selections(None.into(), window, cx, |s| {
29535            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29536        });
29537    });
29538
29539    cx.assert_excerpts_with_selections(indoc! {"
29540        [EXCERPT]
29541        1
29542        2
29543        3
29544        [EXCERPT]
29545        1
29546        2
29547        3
29548        4
29549        5
29550        6
29551        ˇ7
29552        8
29553        9
29554        "});
29555
29556    cx.update_editor(|editor, _window, cx| {
29557        editor.set_vertical_scroll_margin(0, cx);
29558    });
29559
29560    cx.update_editor(|editor, window, cx| {
29561        assert_eq!(editor.vertical_scroll_margin(), 0);
29562        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29563        assert_eq!(
29564            editor.snapshot(window, cx).scroll_position(),
29565            gpui::Point::new(0., 12.0)
29566        );
29567    });
29568
29569    cx.update_editor(|editor, _window, cx| {
29570        editor.set_vertical_scroll_margin(3, cx);
29571    });
29572
29573    cx.update_editor(|editor, window, cx| {
29574        assert_eq!(editor.vertical_scroll_margin(), 3);
29575        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29576        assert_eq!(
29577            editor.snapshot(window, cx).scroll_position(),
29578            gpui::Point::new(0., 9.0)
29579        );
29580    });
29581}
29582
29583#[gpui::test]
29584async fn test_find_references_single_case(cx: &mut TestAppContext) {
29585    init_test(cx, |_| {});
29586    let mut cx = EditorLspTestContext::new_rust(
29587        lsp::ServerCapabilities {
29588            references_provider: Some(lsp::OneOf::Left(true)),
29589            ..lsp::ServerCapabilities::default()
29590        },
29591        cx,
29592    )
29593    .await;
29594
29595    let before = indoc!(
29596        r#"
29597        fn main() {
29598            let aˇbc = 123;
29599            let xyz = abc;
29600        }
29601        "#
29602    );
29603    let after = indoc!(
29604        r#"
29605        fn main() {
29606            let abc = 123;
29607            let xyz = ˇabc;
29608        }
29609        "#
29610    );
29611
29612    cx.lsp
29613        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29614            Ok(Some(vec![
29615                lsp::Location {
29616                    uri: params.text_document_position.text_document.uri.clone(),
29617                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29618                },
29619                lsp::Location {
29620                    uri: params.text_document_position.text_document.uri,
29621                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29622                },
29623            ]))
29624        });
29625
29626    cx.set_state(before);
29627
29628    let action = FindAllReferences {
29629        always_open_multibuffer: false,
29630    };
29631
29632    let navigated = cx
29633        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29634        .expect("should have spawned a task")
29635        .await
29636        .unwrap();
29637
29638    assert_eq!(navigated, Navigated::No);
29639
29640    cx.run_until_parked();
29641
29642    cx.assert_editor_state(after);
29643}
29644
29645#[gpui::test]
29646async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29647    init_test(cx, |settings| {
29648        settings.defaults.tab_size = Some(2.try_into().unwrap());
29649    });
29650
29651    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29652    let mut cx = EditorTestContext::new(cx).await;
29653    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29654
29655    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29656    cx.set_state(indoc! {"
29657        - [ ] taskˇ
29658    "});
29659    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29660    cx.wait_for_autoindent_applied().await;
29661    cx.assert_editor_state(indoc! {"
29662        - [ ] task
29663        - [ ] ˇ
29664    "});
29665
29666    // Case 2: Works with checked task items too
29667    cx.set_state(indoc! {"
29668        - [x] completed taskˇ
29669    "});
29670    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29671    cx.wait_for_autoindent_applied().await;
29672    cx.assert_editor_state(indoc! {"
29673        - [x] completed task
29674        - [ ] ˇ
29675    "});
29676
29677    // Case 2.1: Works with uppercase checked marker too
29678    cx.set_state(indoc! {"
29679        - [X] completed taskˇ
29680    "});
29681    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29682    cx.wait_for_autoindent_applied().await;
29683    cx.assert_editor_state(indoc! {"
29684        - [X] completed task
29685        - [ ] ˇ
29686    "});
29687
29688    // Case 3: Cursor position doesn't matter - content after marker is what counts
29689    cx.set_state(indoc! {"
29690        - [ ] taˇsk
29691    "});
29692    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29693    cx.wait_for_autoindent_applied().await;
29694    cx.assert_editor_state(indoc! {"
29695        - [ ] ta
29696        - [ ] ˇsk
29697    "});
29698
29699    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29700    cx.set_state(indoc! {"
29701        - [ ]  ˇ
29702    "});
29703    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29704    cx.wait_for_autoindent_applied().await;
29705    cx.assert_editor_state(
29706        indoc! {"
29707        - [ ]$$
29708        ˇ
29709    "}
29710        .replace("$", " ")
29711        .as_str(),
29712    );
29713
29714    // Case 5: Adding newline with content adds marker preserving indentation
29715    cx.set_state(indoc! {"
29716        - [ ] task
29717          - [ ] indentedˇ
29718    "});
29719    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29720    cx.wait_for_autoindent_applied().await;
29721    cx.assert_editor_state(indoc! {"
29722        - [ ] task
29723          - [ ] indented
29724          - [ ] ˇ
29725    "});
29726
29727    // Case 6: Adding newline with cursor right after prefix, unindents
29728    cx.set_state(indoc! {"
29729        - [ ] task
29730          - [ ] sub task
29731            - [ ] ˇ
29732    "});
29733    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29734    cx.wait_for_autoindent_applied().await;
29735    cx.assert_editor_state(indoc! {"
29736        - [ ] task
29737          - [ ] sub task
29738          - [ ] ˇ
29739    "});
29740    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29741    cx.wait_for_autoindent_applied().await;
29742
29743    // Case 7: Adding newline with cursor right after prefix, removes marker
29744    cx.assert_editor_state(indoc! {"
29745        - [ ] task
29746          - [ ] sub task
29747        - [ ] ˇ
29748    "});
29749    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29750    cx.wait_for_autoindent_applied().await;
29751    cx.assert_editor_state(indoc! {"
29752        - [ ] task
29753          - [ ] sub task
29754        ˇ
29755    "});
29756
29757    // Case 8: Cursor before or inside prefix does not add marker
29758    cx.set_state(indoc! {"
29759        ˇ- [ ] task
29760    "});
29761    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29762    cx.wait_for_autoindent_applied().await;
29763    cx.assert_editor_state(indoc! {"
29764
29765        ˇ- [ ] task
29766    "});
29767
29768    cx.set_state(indoc! {"
29769        - [ˇ ] task
29770    "});
29771    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29772    cx.wait_for_autoindent_applied().await;
29773    cx.assert_editor_state(indoc! {"
29774        - [
29775        ˇ
29776        ] task
29777    "});
29778}
29779
29780#[gpui::test]
29781async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29782    init_test(cx, |settings| {
29783        settings.defaults.tab_size = Some(2.try_into().unwrap());
29784    });
29785
29786    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29787    let mut cx = EditorTestContext::new(cx).await;
29788    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29789
29790    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29791    cx.set_state(indoc! {"
29792        - itemˇ
29793    "});
29794    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29795    cx.wait_for_autoindent_applied().await;
29796    cx.assert_editor_state(indoc! {"
29797        - item
29798        - ˇ
29799    "});
29800
29801    // Case 2: Works with different markers
29802    cx.set_state(indoc! {"
29803        * starred itemˇ
29804    "});
29805    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29806    cx.wait_for_autoindent_applied().await;
29807    cx.assert_editor_state(indoc! {"
29808        * starred item
29809        * ˇ
29810    "});
29811
29812    cx.set_state(indoc! {"
29813        + plus itemˇ
29814    "});
29815    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29816    cx.wait_for_autoindent_applied().await;
29817    cx.assert_editor_state(indoc! {"
29818        + plus item
29819        + ˇ
29820    "});
29821
29822    // Case 3: Cursor position doesn't matter - content after marker is what counts
29823    cx.set_state(indoc! {"
29824        - itˇem
29825    "});
29826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29827    cx.wait_for_autoindent_applied().await;
29828    cx.assert_editor_state(indoc! {"
29829        - it
29830        - ˇem
29831    "});
29832
29833    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29834    cx.set_state(indoc! {"
29835        -  ˇ
29836    "});
29837    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29838    cx.wait_for_autoindent_applied().await;
29839    cx.assert_editor_state(
29840        indoc! {"
29841        - $
29842        ˇ
29843    "}
29844        .replace("$", " ")
29845        .as_str(),
29846    );
29847
29848    // Case 5: Adding newline with content adds marker preserving indentation
29849    cx.set_state(indoc! {"
29850        - item
29851          - indentedˇ
29852    "});
29853    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29854    cx.wait_for_autoindent_applied().await;
29855    cx.assert_editor_state(indoc! {"
29856        - item
29857          - indented
29858          - ˇ
29859    "});
29860
29861    // Case 6: Adding newline with cursor right after marker, unindents
29862    cx.set_state(indoc! {"
29863        - item
29864          - sub item
29865            - ˇ
29866    "});
29867    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29868    cx.wait_for_autoindent_applied().await;
29869    cx.assert_editor_state(indoc! {"
29870        - item
29871          - sub item
29872          - ˇ
29873    "});
29874    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29875    cx.wait_for_autoindent_applied().await;
29876
29877    // Case 7: Adding newline with cursor right after marker, removes marker
29878    cx.assert_editor_state(indoc! {"
29879        - item
29880          - sub item
29881        - ˇ
29882    "});
29883    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29884    cx.wait_for_autoindent_applied().await;
29885    cx.assert_editor_state(indoc! {"
29886        - item
29887          - sub item
29888        ˇ
29889    "});
29890
29891    // Case 8: Cursor before or inside prefix does not add marker
29892    cx.set_state(indoc! {"
29893        ˇ- item
29894    "});
29895    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29896    cx.wait_for_autoindent_applied().await;
29897    cx.assert_editor_state(indoc! {"
29898
29899        ˇ- item
29900    "});
29901
29902    cx.set_state(indoc! {"
29903        -ˇ item
29904    "});
29905    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29906    cx.wait_for_autoindent_applied().await;
29907    cx.assert_editor_state(indoc! {"
29908        -
29909        ˇitem
29910    "});
29911}
29912
29913#[gpui::test]
29914async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
29915    init_test(cx, |settings| {
29916        settings.defaults.tab_size = Some(2.try_into().unwrap());
29917    });
29918
29919    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29920    let mut cx = EditorTestContext::new(cx).await;
29921    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29922
29923    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29924    cx.set_state(indoc! {"
29925        1. first itemˇ
29926    "});
29927    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29928    cx.wait_for_autoindent_applied().await;
29929    cx.assert_editor_state(indoc! {"
29930        1. first item
29931        2. ˇ
29932    "});
29933
29934    // Case 2: Works with larger numbers
29935    cx.set_state(indoc! {"
29936        10. tenth itemˇ
29937    "});
29938    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29939    cx.wait_for_autoindent_applied().await;
29940    cx.assert_editor_state(indoc! {"
29941        10. tenth item
29942        11. ˇ
29943    "});
29944
29945    // Case 3: Cursor position doesn't matter - content after marker is what counts
29946    cx.set_state(indoc! {"
29947        1. itˇem
29948    "});
29949    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29950    cx.wait_for_autoindent_applied().await;
29951    cx.assert_editor_state(indoc! {"
29952        1. it
29953        2. ˇem
29954    "});
29955
29956    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29957    cx.set_state(indoc! {"
29958        1.  ˇ
29959    "});
29960    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29961    cx.wait_for_autoindent_applied().await;
29962    cx.assert_editor_state(
29963        indoc! {"
29964        1. $
29965        ˇ
29966    "}
29967        .replace("$", " ")
29968        .as_str(),
29969    );
29970
29971    // Case 5: Adding newline with content adds marker preserving indentation
29972    cx.set_state(indoc! {"
29973        1. item
29974          2. indentedˇ
29975    "});
29976    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29977    cx.wait_for_autoindent_applied().await;
29978    cx.assert_editor_state(indoc! {"
29979        1. item
29980          2. indented
29981          3. ˇ
29982    "});
29983
29984    // Case 6: Adding newline with cursor right after marker, unindents
29985    cx.set_state(indoc! {"
29986        1. item
29987          2. sub item
29988            3. ˇ
29989    "});
29990    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29991    cx.wait_for_autoindent_applied().await;
29992    cx.assert_editor_state(indoc! {"
29993        1. item
29994          2. sub item
29995          1. ˇ
29996    "});
29997    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29998    cx.wait_for_autoindent_applied().await;
29999
30000    // Case 7: Adding newline with cursor right after marker, removes marker
30001    cx.assert_editor_state(indoc! {"
30002        1. item
30003          2. sub item
30004        1. ˇ
30005    "});
30006    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30007    cx.wait_for_autoindent_applied().await;
30008    cx.assert_editor_state(indoc! {"
30009        1. item
30010          2. sub item
30011        ˇ
30012    "});
30013
30014    // Case 8: Cursor before or inside prefix does not add marker
30015    cx.set_state(indoc! {"
30016        ˇ1. item
30017    "});
30018    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30019    cx.wait_for_autoindent_applied().await;
30020    cx.assert_editor_state(indoc! {"
30021
30022        ˇ1. item
30023    "});
30024
30025    cx.set_state(indoc! {"
30026        1ˇ. item
30027    "});
30028    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30029    cx.wait_for_autoindent_applied().await;
30030    cx.assert_editor_state(indoc! {"
30031        1
30032        ˇ. item
30033    "});
30034}
30035
30036#[gpui::test]
30037async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30038    init_test(cx, |settings| {
30039        settings.defaults.tab_size = Some(2.try_into().unwrap());
30040    });
30041
30042    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30043    let mut cx = EditorTestContext::new(cx).await;
30044    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30045
30046    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30047    cx.set_state(indoc! {"
30048        1. first item
30049          1. sub first item
30050          2. sub second item
30051          3. ˇ
30052    "});
30053    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30054    cx.wait_for_autoindent_applied().await;
30055    cx.assert_editor_state(indoc! {"
30056        1. first item
30057          1. sub first item
30058          2. sub second item
30059        1. ˇ
30060    "});
30061}
30062
30063#[gpui::test]
30064async fn test_tab_list_indent(cx: &mut TestAppContext) {
30065    init_test(cx, |settings| {
30066        settings.defaults.tab_size = Some(2.try_into().unwrap());
30067    });
30068
30069    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30070    let mut cx = EditorTestContext::new(cx).await;
30071    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30072
30073    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30074    cx.set_state(indoc! {"
30075        - ˇitem
30076    "});
30077    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30078    cx.wait_for_autoindent_applied().await;
30079    let expected = indoc! {"
30080        $$- ˇitem
30081    "};
30082    cx.assert_editor_state(expected.replace("$", " ").as_str());
30083
30084    // Case 2: Task list - cursor after prefix
30085    cx.set_state(indoc! {"
30086        - [ ] ˇtask
30087    "});
30088    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30089    cx.wait_for_autoindent_applied().await;
30090    let expected = indoc! {"
30091        $$- [ ] ˇtask
30092    "};
30093    cx.assert_editor_state(expected.replace("$", " ").as_str());
30094
30095    // Case 3: Ordered list - cursor after prefix
30096    cx.set_state(indoc! {"
30097        1. ˇfirst
30098    "});
30099    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30100    cx.wait_for_autoindent_applied().await;
30101    let expected = indoc! {"
30102        $$1. ˇfirst
30103    "};
30104    cx.assert_editor_state(expected.replace("$", " ").as_str());
30105
30106    // Case 4: With existing indentation - adds more indent
30107    let initial = indoc! {"
30108        $$- ˇitem
30109    "};
30110    cx.set_state(initial.replace("$", " ").as_str());
30111    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30112    cx.wait_for_autoindent_applied().await;
30113    let expected = indoc! {"
30114        $$$$- ˇitem
30115    "};
30116    cx.assert_editor_state(expected.replace("$", " ").as_str());
30117
30118    // Case 5: Empty list item
30119    cx.set_state(indoc! {"
30120        - ˇ
30121    "});
30122    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30123    cx.wait_for_autoindent_applied().await;
30124    let expected = indoc! {"
30125        $$- ˇ
30126    "};
30127    cx.assert_editor_state(expected.replace("$", " ").as_str());
30128
30129    // Case 6: Cursor at end of line with content
30130    cx.set_state(indoc! {"
30131        - itemˇ
30132    "});
30133    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30134    cx.wait_for_autoindent_applied().await;
30135    let expected = indoc! {"
30136        $$- itemˇ
30137    "};
30138    cx.assert_editor_state(expected.replace("$", " ").as_str());
30139
30140    // Case 7: Cursor at start of list item, indents it
30141    cx.set_state(indoc! {"
30142        - item
30143        ˇ  - sub item
30144    "});
30145    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30146    cx.wait_for_autoindent_applied().await;
30147    let expected = indoc! {"
30148        - item
30149          ˇ  - sub item
30150    "};
30151    cx.assert_editor_state(expected);
30152
30153    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30154    cx.update_editor(|_, _, cx| {
30155        SettingsStore::update_global(cx, |store, cx| {
30156            store.update_user_settings(cx, |settings| {
30157                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30158            });
30159        });
30160    });
30161    cx.set_state(indoc! {"
30162        - item
30163        ˇ  - sub item
30164    "});
30165    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30166    cx.wait_for_autoindent_applied().await;
30167    let expected = indoc! {"
30168        - item
30169          ˇ- sub item
30170    "};
30171    cx.assert_editor_state(expected);
30172}
30173
30174#[gpui::test]
30175async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30176    init_test(cx, |_| {});
30177    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30178
30179    cx.update(|cx| {
30180        SettingsStore::update_global(cx, |store, cx| {
30181            store.update_user_settings(cx, |settings| {
30182                settings.project.all_languages.defaults.inlay_hints =
30183                    Some(InlayHintSettingsContent {
30184                        enabled: Some(true),
30185                        ..InlayHintSettingsContent::default()
30186                    });
30187            });
30188        });
30189    });
30190
30191    let fs = FakeFs::new(cx.executor());
30192    fs.insert_tree(
30193        path!("/project"),
30194        json!({
30195            ".zed": {
30196                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30197            },
30198            "main.rs": "fn main() {}"
30199        }),
30200    )
30201    .await;
30202
30203    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30204    let server_name = "override-rust-analyzer";
30205    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30206
30207    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30208    language_registry.add(rust_lang());
30209
30210    let capabilities = lsp::ServerCapabilities {
30211        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30212        ..lsp::ServerCapabilities::default()
30213    };
30214    let mut fake_language_servers = language_registry.register_fake_lsp(
30215        "Rust",
30216        FakeLspAdapter {
30217            name: server_name,
30218            capabilities,
30219            initializer: Some(Box::new({
30220                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30221                move |fake_server| {
30222                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30223                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30224                        move |_params, _| {
30225                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30226                            async move {
30227                                Ok(Some(vec![lsp::InlayHint {
30228                                    position: lsp::Position::new(0, 0),
30229                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30230                                    kind: None,
30231                                    text_edits: None,
30232                                    tooltip: None,
30233                                    padding_left: None,
30234                                    padding_right: None,
30235                                    data: None,
30236                                }]))
30237                            }
30238                        },
30239                    );
30240                }
30241            })),
30242            ..FakeLspAdapter::default()
30243        },
30244    );
30245
30246    cx.run_until_parked();
30247
30248    let worktree_id = project.read_with(cx, |project, cx| {
30249        project
30250            .worktrees(cx)
30251            .next()
30252            .map(|wt| wt.read(cx).id())
30253            .expect("should have a worktree")
30254    });
30255    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30256
30257    let trusted_worktrees =
30258        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30259
30260    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30261        store.can_trust(&worktree_store, worktree_id, cx)
30262    });
30263    assert!(!can_trust, "worktree should be restricted initially");
30264
30265    let buffer_before_approval = project
30266        .update(cx, |project, cx| {
30267            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30268        })
30269        .await
30270        .unwrap();
30271
30272    let (editor, cx) = cx.add_window_view(|window, cx| {
30273        Editor::new(
30274            EditorMode::full(),
30275            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30276            Some(project.clone()),
30277            window,
30278            cx,
30279        )
30280    });
30281    cx.run_until_parked();
30282    let fake_language_server = fake_language_servers.next();
30283
30284    cx.read(|cx| {
30285        let file = buffer_before_approval.read(cx).file();
30286        assert_eq!(
30287            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30288                .language_servers,
30289            ["...".to_string()],
30290            "local .zed/settings.json must not apply before trust approval"
30291        )
30292    });
30293
30294    editor.update_in(cx, |editor, window, cx| {
30295        editor.handle_input("1", window, cx);
30296    });
30297    cx.run_until_parked();
30298    cx.executor()
30299        .advance_clock(std::time::Duration::from_secs(1));
30300    assert_eq!(
30301        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30302        0,
30303        "inlay hints must not be queried before trust approval"
30304    );
30305
30306    trusted_worktrees.update(cx, |store, cx| {
30307        store.trust(
30308            &worktree_store,
30309            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30310            cx,
30311        );
30312    });
30313    cx.run_until_parked();
30314
30315    cx.read(|cx| {
30316        let file = buffer_before_approval.read(cx).file();
30317        assert_eq!(
30318            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30319                .language_servers,
30320            ["override-rust-analyzer".to_string()],
30321            "local .zed/settings.json should apply after trust approval"
30322        )
30323    });
30324    let _fake_language_server = fake_language_server.await.unwrap();
30325    editor.update_in(cx, |editor, window, cx| {
30326        editor.handle_input("1", window, cx);
30327    });
30328    cx.run_until_parked();
30329    cx.executor()
30330        .advance_clock(std::time::Duration::from_secs(1));
30331    assert!(
30332        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30333        "inlay hints should be queried after trust approval"
30334    );
30335
30336    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30337        store.can_trust(&worktree_store, worktree_id, cx)
30338    });
30339    assert!(can_trust_after, "worktree should be trusted after trust()");
30340}
30341
30342#[gpui::test]
30343fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30344    // This test reproduces a bug where drawing an editor at a position above the viewport
30345    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30346    // causes an infinite loop in blocks_in_range.
30347    //
30348    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30349    // the content mask intersection produces visible_bounds with origin at the viewport top.
30350    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30351    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30352    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30353    init_test(cx, |_| {});
30354
30355    let window = cx.add_window(|_, _| gpui::Empty);
30356    let mut cx = VisualTestContext::from_window(*window, cx);
30357
30358    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30359    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30360
30361    // Simulate a small viewport (500x500 pixels at origin 0,0)
30362    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30363
30364    // Draw the editor at a very negative Y position, simulating an editor that's been
30365    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30366    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30367    // This should NOT hang - it should just render nothing.
30368    cx.draw(
30369        gpui::point(px(0.), px(-10000.)),
30370        gpui::size(px(500.), px(3000.)),
30371        |_, _| editor.clone(),
30372    );
30373
30374    // If we get here without hanging, the test passes
30375}
30376
30377#[gpui::test]
30378async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30379    init_test(cx, |_| {});
30380
30381    let fs = FakeFs::new(cx.executor());
30382    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30383        .await;
30384
30385    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30386    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30387    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30388
30389    let editor = workspace
30390        .update(cx, |workspace, window, cx| {
30391            workspace.open_abs_path(
30392                PathBuf::from(path!("/root/file.txt")),
30393                OpenOptions::default(),
30394                window,
30395                cx,
30396            )
30397        })
30398        .unwrap()
30399        .await
30400        .unwrap()
30401        .downcast::<Editor>()
30402        .unwrap();
30403
30404    // Enable diff review button mode
30405    editor.update(cx, |editor, cx| {
30406        editor.set_show_diff_review_button(true, cx);
30407    });
30408
30409    // Initially, no indicator should be present
30410    editor.update(cx, |editor, _cx| {
30411        assert!(
30412            editor.gutter_diff_review_indicator.0.is_none(),
30413            "Indicator should be None initially"
30414        );
30415    });
30416}
30417
30418#[gpui::test]
30419async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30420    init_test(cx, |_| {});
30421
30422    // Register DisableAiSettings and set disable_ai to true
30423    cx.update(|cx| {
30424        project::DisableAiSettings::register(cx);
30425        project::DisableAiSettings::override_global(
30426            project::DisableAiSettings { disable_ai: true },
30427            cx,
30428        );
30429    });
30430
30431    let fs = FakeFs::new(cx.executor());
30432    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30433        .await;
30434
30435    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30436    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30437    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30438
30439    let editor = workspace
30440        .update(cx, |workspace, window, cx| {
30441            workspace.open_abs_path(
30442                PathBuf::from(path!("/root/file.txt")),
30443                OpenOptions::default(),
30444                window,
30445                cx,
30446            )
30447        })
30448        .unwrap()
30449        .await
30450        .unwrap()
30451        .downcast::<Editor>()
30452        .unwrap();
30453
30454    // Enable diff review button mode
30455    editor.update(cx, |editor, cx| {
30456        editor.set_show_diff_review_button(true, cx);
30457    });
30458
30459    // Verify AI is disabled
30460    cx.read(|cx| {
30461        assert!(
30462            project::DisableAiSettings::get_global(cx).disable_ai,
30463            "AI should be disabled"
30464        );
30465    });
30466
30467    // The indicator should not be created when AI is disabled
30468    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30469    editor.update(cx, |editor, _cx| {
30470        assert!(
30471            editor.gutter_diff_review_indicator.0.is_none(),
30472            "Indicator should be None when AI is disabled"
30473        );
30474    });
30475}
30476
30477#[gpui::test]
30478async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30479    init_test(cx, |_| {});
30480
30481    // Register DisableAiSettings and set disable_ai to false
30482    cx.update(|cx| {
30483        project::DisableAiSettings::register(cx);
30484        project::DisableAiSettings::override_global(
30485            project::DisableAiSettings { disable_ai: false },
30486            cx,
30487        );
30488    });
30489
30490    let fs = FakeFs::new(cx.executor());
30491    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30492        .await;
30493
30494    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30495    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30496    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30497
30498    let editor = workspace
30499        .update(cx, |workspace, window, cx| {
30500            workspace.open_abs_path(
30501                PathBuf::from(path!("/root/file.txt")),
30502                OpenOptions::default(),
30503                window,
30504                cx,
30505            )
30506        })
30507        .unwrap()
30508        .await
30509        .unwrap()
30510        .downcast::<Editor>()
30511        .unwrap();
30512
30513    // Enable diff review button mode
30514    editor.update(cx, |editor, cx| {
30515        editor.set_show_diff_review_button(true, cx);
30516    });
30517
30518    // Verify AI is enabled
30519    cx.read(|cx| {
30520        assert!(
30521            !project::DisableAiSettings::get_global(cx).disable_ai,
30522            "AI should be enabled"
30523        );
30524    });
30525
30526    // The show_diff_review_button flag should be true
30527    editor.update(cx, |editor, _cx| {
30528        assert!(
30529            editor.show_diff_review_button(),
30530            "show_diff_review_button should be true"
30531        );
30532    });
30533}
30534
30535/// Helper function to create a DiffHunkKey for testing.
30536/// Uses Anchor::min() as a placeholder anchor since these tests don't need
30537/// real buffer positioning.
30538fn test_hunk_key(file_path: &str) -> DiffHunkKey {
30539    DiffHunkKey {
30540        file_path: if file_path.is_empty() {
30541            Arc::from(util::rel_path::RelPath::empty())
30542        } else {
30543            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30544        },
30545        hunk_start_anchor: Anchor::min(),
30546    }
30547}
30548
30549/// Helper function to create a DiffHunkKey with a specific anchor for testing.
30550fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
30551    DiffHunkKey {
30552        file_path: if file_path.is_empty() {
30553            Arc::from(util::rel_path::RelPath::empty())
30554        } else {
30555            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30556        },
30557        hunk_start_anchor: anchor,
30558    }
30559}
30560
30561/// Helper function to add a review comment with default anchors for testing.
30562fn add_test_comment(
30563    editor: &mut Editor,
30564    key: DiffHunkKey,
30565    comment: &str,
30566    display_row: u32,
30567    cx: &mut Context<Editor>,
30568) -> usize {
30569    editor.add_review_comment(
30570        key,
30571        comment.to_string(),
30572        DisplayRow(display_row),
30573        Anchor::min()..Anchor::max(),
30574        cx,
30575    )
30576}
30577
30578#[gpui::test]
30579fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
30580    init_test(cx, |_| {});
30581
30582    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30583
30584    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30585        let key = test_hunk_key("");
30586
30587        let id = add_test_comment(editor, key.clone(), "Test comment", 0, cx);
30588
30589        let snapshot = editor.buffer().read(cx).snapshot(cx);
30590        assert_eq!(editor.total_review_comment_count(), 1);
30591        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
30592
30593        let comments = editor.comments_for_hunk(&key, &snapshot);
30594        assert_eq!(comments.len(), 1);
30595        assert_eq!(comments[0].comment, "Test comment");
30596        assert_eq!(comments[0].id, id);
30597    });
30598}
30599
30600#[gpui::test]
30601fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
30602    init_test(cx, |_| {});
30603
30604    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30605
30606    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30607        let snapshot = editor.buffer().read(cx).snapshot(cx);
30608        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30609        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30610        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30611        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30612
30613        add_test_comment(editor, key1.clone(), "Comment for file1", 0, cx);
30614        add_test_comment(editor, key2.clone(), "Comment for file2", 10, cx);
30615
30616        let snapshot = editor.buffer().read(cx).snapshot(cx);
30617        assert_eq!(editor.total_review_comment_count(), 2);
30618        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
30619        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
30620
30621        assert_eq!(
30622            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
30623            "Comment for file1"
30624        );
30625        assert_eq!(
30626            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
30627            "Comment for file2"
30628        );
30629    });
30630}
30631
30632#[gpui::test]
30633fn test_review_comment_remove(cx: &mut TestAppContext) {
30634    init_test(cx, |_| {});
30635
30636    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30637
30638    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30639        let key = test_hunk_key("");
30640
30641        let id = add_test_comment(editor, key, "To be removed", 0, cx);
30642
30643        assert_eq!(editor.total_review_comment_count(), 1);
30644
30645        let removed = editor.remove_review_comment(id, cx);
30646        assert!(removed);
30647        assert_eq!(editor.total_review_comment_count(), 0);
30648
30649        // Try to remove again
30650        let removed_again = editor.remove_review_comment(id, cx);
30651        assert!(!removed_again);
30652    });
30653}
30654
30655#[gpui::test]
30656fn test_review_comment_update(cx: &mut TestAppContext) {
30657    init_test(cx, |_| {});
30658
30659    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30660
30661    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30662        let key = test_hunk_key("");
30663
30664        let id = add_test_comment(editor, key.clone(), "Original text", 0, cx);
30665
30666        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
30667        assert!(updated);
30668
30669        let snapshot = editor.buffer().read(cx).snapshot(cx);
30670        let comments = editor.comments_for_hunk(&key, &snapshot);
30671        assert_eq!(comments[0].comment, "Updated text");
30672        assert!(!comments[0].is_editing); // Should clear editing flag
30673    });
30674}
30675
30676#[gpui::test]
30677fn test_review_comment_take_all(cx: &mut TestAppContext) {
30678    init_test(cx, |_| {});
30679
30680    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30681
30682    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30683        let snapshot = editor.buffer().read(cx).snapshot(cx);
30684        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30685        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30686        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30687        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30688
30689        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", 0, cx);
30690        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", 1, cx);
30691        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", 10, cx);
30692
30693        // IDs should be sequential starting from 0
30694        assert_eq!(id1, 0);
30695        assert_eq!(id2, 1);
30696        assert_eq!(id3, 2);
30697
30698        assert_eq!(editor.total_review_comment_count(), 3);
30699
30700        let taken = editor.take_all_review_comments(cx);
30701
30702        // Should have 2 entries (one per hunk)
30703        assert_eq!(taken.len(), 2);
30704
30705        // Total comments should be 3
30706        let total: usize = taken
30707            .iter()
30708            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
30709            .sum();
30710        assert_eq!(total, 3);
30711
30712        // Storage should be empty
30713        assert_eq!(editor.total_review_comment_count(), 0);
30714
30715        // After taking all comments, ID counter should reset
30716        // New comments should get IDs starting from 0 again
30717        let new_id1 = add_test_comment(editor, key1, "New Comment 1", 0, cx);
30718        let new_id2 = add_test_comment(editor, key2, "New Comment 2", 0, cx);
30719
30720        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
30721        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
30722    });
30723}
30724
30725#[gpui::test]
30726fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
30727    init_test(cx, |_| {});
30728
30729    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30730
30731    // Show overlay
30732    editor
30733        .update(cx, |editor, window, cx| {
30734            editor.show_diff_review_overlay(DisplayRow(0), window, cx);
30735        })
30736        .unwrap();
30737
30738    // Verify overlay is shown
30739    editor
30740        .update(cx, |editor, _window, _cx| {
30741            assert!(!editor.diff_review_overlays.is_empty());
30742            assert_eq!(editor.diff_review_display_row(), Some(DisplayRow(0)));
30743            assert!(editor.diff_review_prompt_editor().is_some());
30744        })
30745        .unwrap();
30746
30747    // Dismiss overlay
30748    editor
30749        .update(cx, |editor, _window, cx| {
30750            editor.dismiss_all_diff_review_overlays(cx);
30751        })
30752        .unwrap();
30753
30754    // Verify overlay is dismissed
30755    editor
30756        .update(cx, |editor, _window, _cx| {
30757            assert!(editor.diff_review_overlays.is_empty());
30758            assert_eq!(editor.diff_review_display_row(), None);
30759            assert!(editor.diff_review_prompt_editor().is_none());
30760        })
30761        .unwrap();
30762}
30763
30764#[gpui::test]
30765fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
30766    init_test(cx, |_| {});
30767
30768    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30769
30770    // Show overlay
30771    editor
30772        .update(cx, |editor, window, cx| {
30773            editor.show_diff_review_overlay(DisplayRow(0), window, cx);
30774        })
30775        .unwrap();
30776
30777    // Verify overlay is shown
30778    editor
30779        .update(cx, |editor, _window, _cx| {
30780            assert!(!editor.diff_review_overlays.is_empty());
30781        })
30782        .unwrap();
30783
30784    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
30785    editor
30786        .update(cx, |editor, window, cx| {
30787            editor.dismiss_menus_and_popups(true, window, cx);
30788        })
30789        .unwrap();
30790
30791    // Verify overlay is dismissed
30792    editor
30793        .update(cx, |editor, _window, _cx| {
30794            assert!(editor.diff_review_overlays.is_empty());
30795        })
30796        .unwrap();
30797}
30798
30799#[gpui::test]
30800fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
30801    init_test(cx, |_| {});
30802
30803    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30804
30805    // Show overlay
30806    editor
30807        .update(cx, |editor, window, cx| {
30808            editor.show_diff_review_overlay(DisplayRow(0), window, cx);
30809        })
30810        .unwrap();
30811
30812    // Try to submit without typing anything (empty comment)
30813    editor
30814        .update(cx, |editor, window, cx| {
30815            editor.submit_diff_review_comment(window, cx);
30816        })
30817        .unwrap();
30818
30819    // Verify no comment was added
30820    editor
30821        .update(cx, |editor, _window, _cx| {
30822            assert_eq!(editor.total_review_comment_count(), 0);
30823        })
30824        .unwrap();
30825
30826    // Try to submit with whitespace-only comment
30827    editor
30828        .update(cx, |editor, window, cx| {
30829            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
30830                prompt_editor.update(cx, |pe, cx| {
30831                    pe.insert("   \n\t  ", window, cx);
30832                });
30833            }
30834            editor.submit_diff_review_comment(window, cx);
30835        })
30836        .unwrap();
30837
30838    // Verify still no comment was added
30839    editor
30840        .update(cx, |editor, _window, _cx| {
30841            assert_eq!(editor.total_review_comment_count(), 0);
30842        })
30843        .unwrap();
30844}
30845
30846#[gpui::test]
30847fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
30848    init_test(cx, |_| {});
30849
30850    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30851
30852    // Add a comment directly
30853    let comment_id = editor
30854        .update(cx, |editor, _window, cx| {
30855            let key = test_hunk_key("");
30856            add_test_comment(editor, key, "Original comment", 0, cx)
30857        })
30858        .unwrap();
30859
30860    // Set comment to editing mode
30861    editor
30862        .update(cx, |editor, _window, cx| {
30863            editor.set_comment_editing(comment_id, true, cx);
30864        })
30865        .unwrap();
30866
30867    // Verify editing flag is set
30868    editor
30869        .update(cx, |editor, _window, cx| {
30870            let key = test_hunk_key("");
30871            let snapshot = editor.buffer().read(cx).snapshot(cx);
30872            let comments = editor.comments_for_hunk(&key, &snapshot);
30873            assert_eq!(comments.len(), 1);
30874            assert!(comments[0].is_editing);
30875        })
30876        .unwrap();
30877
30878    // Update the comment
30879    editor
30880        .update(cx, |editor, _window, cx| {
30881            let updated =
30882                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
30883            assert!(updated);
30884        })
30885        .unwrap();
30886
30887    // Verify comment was updated and editing flag is cleared
30888    editor
30889        .update(cx, |editor, _window, cx| {
30890            let key = test_hunk_key("");
30891            let snapshot = editor.buffer().read(cx).snapshot(cx);
30892            let comments = editor.comments_for_hunk(&key, &snapshot);
30893            assert_eq!(comments[0].comment, "Updated comment");
30894            assert!(!comments[0].is_editing);
30895        })
30896        .unwrap();
30897}
30898
30899#[gpui::test]
30900fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
30901    init_test(cx, |_| {});
30902
30903    // Create an editor with some text
30904    let editor = cx.add_window(|window, cx| {
30905        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
30906        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30907        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30908    });
30909
30910    // Add a comment with an anchor on line 2
30911    editor
30912        .update(cx, |editor, _window, cx| {
30913            let snapshot = editor.buffer().read(cx).snapshot(cx);
30914            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
30915            let key = DiffHunkKey {
30916                file_path: Arc::from(util::rel_path::RelPath::empty()),
30917                hunk_start_anchor: anchor,
30918            };
30919            editor.add_review_comment(
30920                key,
30921                "Comment on line 2".to_string(),
30922                DisplayRow(1),
30923                anchor..anchor,
30924                cx,
30925            );
30926            assert_eq!(editor.total_review_comment_count(), 1);
30927        })
30928        .unwrap();
30929
30930    // Delete all content (this should orphan the comment's anchor)
30931    editor
30932        .update(cx, |editor, window, cx| {
30933            editor.select_all(&SelectAll, window, cx);
30934            editor.insert("completely new content", window, cx);
30935        })
30936        .unwrap();
30937
30938    // Trigger cleanup
30939    editor
30940        .update(cx, |editor, _window, cx| {
30941            editor.cleanup_orphaned_review_comments(cx);
30942            // Comment should be removed because its anchor is invalid
30943            assert_eq!(editor.total_review_comment_count(), 0);
30944        })
30945        .unwrap();
30946}
30947
30948#[gpui::test]
30949fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
30950    init_test(cx, |_| {});
30951
30952    // Create an editor with some text
30953    let editor = cx.add_window(|window, cx| {
30954        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
30955        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30956        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30957    });
30958
30959    // Add a comment with an anchor on line 2
30960    editor
30961        .update(cx, |editor, _window, cx| {
30962            let snapshot = editor.buffer().read(cx).snapshot(cx);
30963            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
30964            let key = DiffHunkKey {
30965                file_path: Arc::from(util::rel_path::RelPath::empty()),
30966                hunk_start_anchor: anchor,
30967            };
30968            editor.add_review_comment(
30969                key,
30970                "Comment on line 2".to_string(),
30971                DisplayRow(1),
30972                anchor..anchor,
30973                cx,
30974            );
30975            assert_eq!(editor.total_review_comment_count(), 1);
30976        })
30977        .unwrap();
30978
30979    // Edit the buffer - this should trigger cleanup via on_buffer_event
30980    // Delete all content which orphans the anchor
30981    editor
30982        .update(cx, |editor, window, cx| {
30983            editor.select_all(&SelectAll, window, cx);
30984            editor.insert("completely new content", window, cx);
30985            // The cleanup is called automatically in on_buffer_event when Edited fires
30986        })
30987        .unwrap();
30988
30989    // Verify cleanup happened automatically (not manually triggered)
30990    editor
30991        .update(cx, |editor, _window, _cx| {
30992            // Comment should be removed because its anchor became invalid
30993            // and cleanup was called automatically on buffer edit
30994            assert_eq!(editor.total_review_comment_count(), 0);
30995        })
30996        .unwrap();
30997}
30998
30999#[gpui::test]
31000fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
31001    init_test(cx, |_| {});
31002
31003    // This test verifies that comments can be stored for multiple different hunks
31004    // and that hunk_comment_count correctly identifies comments per hunk.
31005    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31006
31007    _ = editor.update(cx, |editor, _window, cx| {
31008        let snapshot = editor.buffer().read(cx).snapshot(cx);
31009
31010        // Create two different hunk keys (simulating two different files)
31011        let anchor = snapshot.anchor_before(Point::new(0, 0));
31012        let key1 = DiffHunkKey {
31013            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
31014            hunk_start_anchor: anchor,
31015        };
31016        let key2 = DiffHunkKey {
31017            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31018            hunk_start_anchor: anchor,
31019        };
31020
31021        // Add comments to first hunk
31022        editor.add_review_comment(
31023            key1.clone(),
31024            "Comment 1 for file1".to_string(),
31025            DisplayRow(0),
31026            anchor..anchor,
31027            cx,
31028        );
31029        editor.add_review_comment(
31030            key1.clone(),
31031            "Comment 2 for file1".to_string(),
31032            DisplayRow(1),
31033            anchor..anchor,
31034            cx,
31035        );
31036
31037        // Add comment to second hunk
31038        editor.add_review_comment(
31039            key2.clone(),
31040            "Comment for file2".to_string(),
31041            DisplayRow(0),
31042            anchor..anchor,
31043            cx,
31044        );
31045
31046        // Verify total count
31047        assert_eq!(editor.total_review_comment_count(), 3);
31048
31049        // Verify per-hunk counts
31050        let snapshot = editor.buffer().read(cx).snapshot(cx);
31051        assert_eq!(
31052            editor.hunk_comment_count(&key1, &snapshot),
31053            2,
31054            "file1 should have 2 comments"
31055        );
31056        assert_eq!(
31057            editor.hunk_comment_count(&key2, &snapshot),
31058            1,
31059            "file2 should have 1 comment"
31060        );
31061
31062        // Verify comments_for_hunk returns correct comments
31063        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31064        assert_eq!(file1_comments.len(), 2);
31065        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31066        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31067
31068        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31069        assert_eq!(file2_comments.len(), 1);
31070        assert_eq!(file2_comments[0].comment, "Comment for file2");
31071    });
31072}
31073
31074#[gpui::test]
31075fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31076    init_test(cx, |_| {});
31077
31078    // This test verifies that hunk_keys_match correctly identifies when two
31079    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31080    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31081
31082    _ = editor.update(cx, |editor, _window, cx| {
31083        let snapshot = editor.buffer().read(cx).snapshot(cx);
31084        let anchor = snapshot.anchor_before(Point::new(0, 0));
31085
31086        // Create two keys with the same file path and anchor
31087        let key1 = DiffHunkKey {
31088            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31089            hunk_start_anchor: anchor,
31090        };
31091        let key2 = DiffHunkKey {
31092            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31093            hunk_start_anchor: anchor,
31094        };
31095
31096        // Add comment to first key
31097        editor.add_review_comment(
31098            key1,
31099            "Test comment".to_string(),
31100            DisplayRow(0),
31101            anchor..anchor,
31102            cx,
31103        );
31104
31105        // Verify second key (same hunk) finds the comment
31106        let snapshot = editor.buffer().read(cx).snapshot(cx);
31107        assert_eq!(
31108            editor.hunk_comment_count(&key2, &snapshot),
31109            1,
31110            "Same hunk should find the comment"
31111        );
31112
31113        // Create a key with different file path
31114        let different_file_key = DiffHunkKey {
31115            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31116            hunk_start_anchor: anchor,
31117        };
31118
31119        // Different file should not find the comment
31120        assert_eq!(
31121            editor.hunk_comment_count(&different_file_key, &snapshot),
31122            0,
31123            "Different file should not find the comment"
31124        );
31125    });
31126}
31127
31128#[gpui::test]
31129fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31130    init_test(cx, |_| {});
31131
31132    // This test verifies that set_diff_review_comments_expanded correctly
31133    // updates the expanded state of overlays.
31134    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31135
31136    // Show overlay
31137    editor
31138        .update(cx, |editor, window, cx| {
31139            editor.show_diff_review_overlay(DisplayRow(0), window, cx);
31140        })
31141        .unwrap();
31142
31143    // Verify initially expanded (default)
31144    editor
31145        .update(cx, |editor, _window, _cx| {
31146            assert!(
31147                editor.diff_review_overlays[0].comments_expanded,
31148                "Should be expanded by default"
31149            );
31150        })
31151        .unwrap();
31152
31153    // Set to collapsed using the public method
31154    editor
31155        .update(cx, |editor, _window, cx| {
31156            editor.set_diff_review_comments_expanded(false, cx);
31157        })
31158        .unwrap();
31159
31160    // Verify collapsed
31161    editor
31162        .update(cx, |editor, _window, _cx| {
31163            assert!(
31164                !editor.diff_review_overlays[0].comments_expanded,
31165                "Should be collapsed after setting to false"
31166            );
31167        })
31168        .unwrap();
31169
31170    // Set back to expanded
31171    editor
31172        .update(cx, |editor, _window, cx| {
31173            editor.set_diff_review_comments_expanded(true, cx);
31174        })
31175        .unwrap();
31176
31177    // Verify expanded again
31178    editor
31179        .update(cx, |editor, _window, _cx| {
31180            assert!(
31181                editor.diff_review_overlays[0].comments_expanded,
31182                "Should be expanded after setting to true"
31183            );
31184        })
31185        .unwrap();
31186}
31187
31188#[gpui::test]
31189fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31190    init_test(cx, |_| {});
31191
31192    // This test verifies that calculate_overlay_height returns correct heights
31193    // based on comment count and expanded state.
31194    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31195
31196    _ = editor.update(cx, |editor, _window, cx| {
31197        let snapshot = editor.buffer().read(cx).snapshot(cx);
31198        let anchor = snapshot.anchor_before(Point::new(0, 0));
31199        let key = DiffHunkKey {
31200            file_path: Arc::from(util::rel_path::RelPath::empty()),
31201            hunk_start_anchor: anchor,
31202        };
31203
31204        // No comments: base height of 2
31205        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31206        assert_eq!(
31207            height_no_comments, 2,
31208            "Base height should be 2 with no comments"
31209        );
31210
31211        // Add one comment
31212        editor.add_review_comment(
31213            key.clone(),
31214            "Comment 1".to_string(),
31215            DisplayRow(0),
31216            anchor..anchor,
31217            cx,
31218        );
31219
31220        let snapshot = editor.buffer().read(cx).snapshot(cx);
31221
31222        // With comments expanded: base (2) + header (1) + 2 per comment
31223        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31224        assert_eq!(
31225            height_expanded,
31226            2 + 1 + 2, // base + header + 1 comment * 2
31227            "Height with 1 comment expanded"
31228        );
31229
31230        // With comments collapsed: base (2) + header (1)
31231        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31232        assert_eq!(
31233            height_collapsed,
31234            2 + 1, // base + header only
31235            "Height with comments collapsed"
31236        );
31237
31238        // Add more comments
31239        editor.add_review_comment(
31240            key.clone(),
31241            "Comment 2".to_string(),
31242            DisplayRow(0),
31243            anchor..anchor,
31244            cx,
31245        );
31246        editor.add_review_comment(
31247            key.clone(),
31248            "Comment 3".to_string(),
31249            DisplayRow(0),
31250            anchor..anchor,
31251            cx,
31252        );
31253
31254        let snapshot = editor.buffer().read(cx).snapshot(cx);
31255
31256        // With 3 comments expanded
31257        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31258        assert_eq!(
31259            height_3_expanded,
31260            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31261            "Height with 3 comments expanded"
31262        );
31263
31264        // Collapsed height stays the same regardless of comment count
31265        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31266        assert_eq!(
31267            height_3_collapsed,
31268            2 + 1, // base + header only
31269            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31270        );
31271    });
31272}
31273
31274#[gpui::test]
31275async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31276    init_test(cx, |_| {});
31277
31278    let language = Arc::new(Language::new(
31279        LanguageConfig::default(),
31280        Some(tree_sitter_rust::LANGUAGE.into()),
31281    ));
31282
31283    let text = r#"
31284        fn main() {
31285            let x = foo(1, 2);
31286        }
31287    "#
31288    .unindent();
31289
31290    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31291    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31292    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31293
31294    editor
31295        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31296        .await;
31297
31298    // Test case 1: Move to end of syntax nodes
31299    editor.update_in(cx, |editor, window, cx| {
31300        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31301            s.select_display_ranges([
31302                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
31303            ]);
31304        });
31305    });
31306    editor.update(cx, |editor, cx| {
31307        assert_text_with_selections(
31308            editor,
31309            indoc! {r#"
31310                fn main() {
31311                    let x = foo(ˇ1, 2);
31312                }
31313            "#},
31314            cx,
31315        );
31316    });
31317    editor.update_in(cx, |editor, window, cx| {
31318        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31319    });
31320    editor.update(cx, |editor, cx| {
31321        assert_text_with_selections(
31322            editor,
31323            indoc! {r#"
31324                fn main() {
31325                    let x = foo(1ˇ, 2);
31326                }
31327            "#},
31328            cx,
31329        );
31330    });
31331    editor.update_in(cx, |editor, window, cx| {
31332        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31333    });
31334    editor.update(cx, |editor, cx| {
31335        assert_text_with_selections(
31336            editor,
31337            indoc! {r#"
31338                fn main() {
31339                    let x = foo(1, 2)ˇ;
31340                }
31341            "#},
31342            cx,
31343        );
31344    });
31345    editor.update_in(cx, |editor, window, cx| {
31346        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31347    });
31348    editor.update(cx, |editor, cx| {
31349        assert_text_with_selections(
31350            editor,
31351            indoc! {r#"
31352                fn main() {
31353                    let x = foo(1, 2);ˇ
31354                }
31355            "#},
31356            cx,
31357        );
31358    });
31359    editor.update_in(cx, |editor, window, cx| {
31360        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31361    });
31362    editor.update(cx, |editor, cx| {
31363        assert_text_with_selections(
31364            editor,
31365            indoc! {r#"
31366                fn main() {
31367                    let x = foo(1, 2);
3136831369            "#},
31370            cx,
31371        );
31372    });
31373
31374    // Test case 2: Move to start of syntax nodes
31375    editor.update_in(cx, |editor, window, cx| {
31376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31377            s.select_display_ranges([
31378                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
31379            ]);
31380        });
31381    });
31382    editor.update(cx, |editor, cx| {
31383        assert_text_with_selections(
31384            editor,
31385            indoc! {r#"
31386                fn main() {
31387                    let x = foo(1, 2ˇ);
31388                }
31389            "#},
31390            cx,
31391        );
31392    });
31393    editor.update_in(cx, |editor, window, cx| {
31394        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31395    });
31396    editor.update(cx, |editor, cx| {
31397        assert_text_with_selections(
31398            editor,
31399            indoc! {r#"
31400                fn main() {
31401                    let x = fooˇ(1, 2);
31402                }
31403            "#},
31404            cx,
31405        );
31406    });
31407    editor.update_in(cx, |editor, window, cx| {
31408        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31409    });
31410    editor.update(cx, |editor, cx| {
31411        assert_text_with_selections(
31412            editor,
31413            indoc! {r#"
31414                fn main() {
31415                    let x = ˇfoo(1, 2);
31416                }
31417            "#},
31418            cx,
31419        );
31420    });
31421    editor.update_in(cx, |editor, window, cx| {
31422        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31423    });
31424    editor.update(cx, |editor, cx| {
31425        assert_text_with_selections(
31426            editor,
31427            indoc! {r#"
31428                fn main() {
31429                    ˇlet x = foo(1, 2);
31430                }
31431            "#},
31432            cx,
31433        );
31434    });
31435    editor.update_in(cx, |editor, window, cx| {
31436        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31437    });
31438    editor.update(cx, |editor, cx| {
31439        assert_text_with_selections(
31440            editor,
31441            indoc! {r#"
31442                fn main() ˇ{
31443                    let x = foo(1, 2);
31444                }
31445            "#},
31446            cx,
31447        );
31448    });
31449    editor.update_in(cx, |editor, window, cx| {
31450        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31451    });
31452    editor.update(cx, |editor, cx| {
31453        assert_text_with_selections(
31454            editor,
31455            indoc! {r#"
31456                ˇfn main() {
31457                    let x = foo(1, 2);
31458                }
31459            "#},
31460            cx,
31461        );
31462    });
31463}
31464
31465#[gpui::test]
31466async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
31467    init_test(cx, |_| {});
31468
31469    let language = Arc::new(Language::new(
31470        LanguageConfig::default(),
31471        Some(tree_sitter_rust::LANGUAGE.into()),
31472    ));
31473
31474    let text = r#"
31475        fn main() {
31476            let x = foo(1, 2);
31477            let y = bar(3, 4);
31478        }
31479    "#
31480    .unindent();
31481
31482    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31483    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31484    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31485
31486    editor
31487        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31488        .await;
31489
31490    // Test case 1: Move to end of syntax nodes with two cursors
31491    editor.update_in(cx, |editor, window, cx| {
31492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31493            s.select_display_ranges([
31494                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
31495                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
31496            ]);
31497        });
31498    });
31499    editor.update(cx, |editor, cx| {
31500        assert_text_with_selections(
31501            editor,
31502            indoc! {r#"
31503                fn main() {
31504                    let x = foo(1, 2ˇ);
31505                    let y = bar(3, 4ˇ);
31506                }
31507            "#},
31508            cx,
31509        );
31510    });
31511    editor.update_in(cx, |editor, window, cx| {
31512        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31513    });
31514    editor.update(cx, |editor, cx| {
31515        assert_text_with_selections(
31516            editor,
31517            indoc! {r#"
31518                fn main() {
31519                    let x = foo(1, 2)ˇ;
31520                    let y = bar(3, 4)ˇ;
31521                }
31522            "#},
31523            cx,
31524        );
31525    });
31526    editor.update_in(cx, |editor, window, cx| {
31527        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31528    });
31529    editor.update(cx, |editor, cx| {
31530        assert_text_with_selections(
31531            editor,
31532            indoc! {r#"
31533                fn main() {
31534                    let x = foo(1, 2);ˇ
31535                    let y = bar(3, 4);ˇ
31536                }
31537            "#},
31538            cx,
31539        );
31540    });
31541
31542    // Test case 2: Move to start of syntax nodes with two cursors
31543    editor.update_in(cx, |editor, window, cx| {
31544        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31545            s.select_display_ranges([
31546                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
31547                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
31548            ]);
31549        });
31550    });
31551    editor.update(cx, |editor, cx| {
31552        assert_text_with_selections(
31553            editor,
31554            indoc! {r#"
31555                fn main() {
31556                    let x = foo(1, ˇ2);
31557                    let y = bar(3, ˇ4);
31558                }
31559            "#},
31560            cx,
31561        );
31562    });
31563    editor.update_in(cx, |editor, window, cx| {
31564        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31565    });
31566    editor.update(cx, |editor, cx| {
31567        assert_text_with_selections(
31568            editor,
31569            indoc! {r#"
31570                fn main() {
31571                    let x = fooˇ(1, 2);
31572                    let y = barˇ(3, 4);
31573                }
31574            "#},
31575            cx,
31576        );
31577    });
31578    editor.update_in(cx, |editor, window, cx| {
31579        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31580    });
31581    editor.update(cx, |editor, cx| {
31582        assert_text_with_selections(
31583            editor,
31584            indoc! {r#"
31585                fn main() {
31586                    let x = ˇfoo(1, 2);
31587                    let y = ˇbar(3, 4);
31588                }
31589            "#},
31590            cx,
31591        );
31592    });
31593    editor.update_in(cx, |editor, window, cx| {
31594        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31595    });
31596    editor.update(cx, |editor, cx| {
31597        assert_text_with_selections(
31598            editor,
31599            indoc! {r#"
31600                fn main() {
31601                    ˇlet x = foo(1, 2);
31602                    ˇlet y = bar(3, 4);
31603                }
31604            "#},
31605            cx,
31606        );
31607    });
31608}
31609
31610#[gpui::test]
31611async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
31612    cx: &mut TestAppContext,
31613) {
31614    init_test(cx, |_| {});
31615
31616    let language = Arc::new(Language::new(
31617        LanguageConfig::default(),
31618        Some(tree_sitter_rust::LANGUAGE.into()),
31619    ));
31620
31621    let text = r#"
31622        fn main() {
31623            let x = foo(1, 2);
31624            let msg = "hello world";
31625        }
31626    "#
31627    .unindent();
31628
31629    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31630    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31631    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31632
31633    editor
31634        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31635        .await;
31636
31637    // Test case 1: With existing selection, move_to_end keeps selection
31638    editor.update_in(cx, |editor, window, cx| {
31639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31640            s.select_display_ranges([
31641                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
31642            ]);
31643        });
31644    });
31645    editor.update(cx, |editor, cx| {
31646        assert_text_with_selections(
31647            editor,
31648            indoc! {r#"
31649                fn main() {
31650                    let x = «foo(1, 2)ˇ»;
31651                    let msg = "hello world";
31652                }
31653            "#},
31654            cx,
31655        );
31656    });
31657    editor.update_in(cx, |editor, window, cx| {
31658        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31659    });
31660    editor.update(cx, |editor, cx| {
31661        assert_text_with_selections(
31662            editor,
31663            indoc! {r#"
31664                fn main() {
31665                    let x = «foo(1, 2)ˇ»;
31666                    let msg = "hello world";
31667                }
31668            "#},
31669            cx,
31670        );
31671    });
31672
31673    // Test case 2: Move to end within a string
31674    editor.update_in(cx, |editor, window, cx| {
31675        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31676            s.select_display_ranges([
31677                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
31678            ]);
31679        });
31680    });
31681    editor.update(cx, |editor, cx| {
31682        assert_text_with_selections(
31683            editor,
31684            indoc! {r#"
31685                fn main() {
31686                    let x = foo(1, 2);
31687                    let msg = "ˇhello world";
31688                }
31689            "#},
31690            cx,
31691        );
31692    });
31693    editor.update_in(cx, |editor, window, cx| {
31694        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31695    });
31696    editor.update(cx, |editor, cx| {
31697        assert_text_with_selections(
31698            editor,
31699            indoc! {r#"
31700                fn main() {
31701                    let x = foo(1, 2);
31702                    let msg = "hello worldˇ";
31703                }
31704            "#},
31705            cx,
31706        );
31707    });
31708
31709    // Test case 3: Move to start within a string
31710    editor.update_in(cx, |editor, window, cx| {
31711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31712            s.select_display_ranges([
31713                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
31714            ]);
31715        });
31716    });
31717    editor.update(cx, |editor, cx| {
31718        assert_text_with_selections(
31719            editor,
31720            indoc! {r#"
31721                fn main() {
31722                    let x = foo(1, 2);
31723                    let msg = "hello ˇworld";
31724                }
31725            "#},
31726            cx,
31727        );
31728    });
31729    editor.update_in(cx, |editor, window, cx| {
31730        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31731    });
31732    editor.update(cx, |editor, cx| {
31733        assert_text_with_selections(
31734            editor,
31735            indoc! {r#"
31736                fn main() {
31737                    let x = foo(1, 2);
31738                    let msg = "ˇhello world";
31739                }
31740            "#},
31741            cx,
31742        );
31743    });
31744}