editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::build_editor_with_project;
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_item_view::InvalidItemView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, 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    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, 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    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(
  224            editor.selections.ranges(&editor.display_snapshot(cx)),
  225            vec![4..4]
  226        );
  227
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([4..5])
  231        });
  232        editor.insert("e", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![5..5]
  238        );
  239
  240        now += group_interval + Duration::from_millis(1);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([2..2])
  243        });
  244
  245        // Simulate an edit in another editor
  246        buffer.update(cx, |buffer, cx| {
  247            buffer.start_transaction_at(now, cx);
  248            buffer.edit([(0..1, "a")], None, cx);
  249            buffer.edit([(1..1, "b")], None, cx);
  250            buffer.end_transaction_at(now, cx);
  251        });
  252
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(
  255            editor.selections.ranges(&editor.display_snapshot(cx)),
  256            vec![3..3]
  257        );
  258
  259        // Last transaction happened past the group interval in a different editor.
  260        // Undo it individually and don't restore selections.
  261        editor.undo(&Undo, window, cx);
  262        assert_eq!(editor.text(cx), "12cde6");
  263        assert_eq!(
  264            editor.selections.ranges(&editor.display_snapshot(cx)),
  265            vec![2..2]
  266        );
  267
  268        // First two transactions happened within the group interval in this editor.
  269        // Undo them together and restore selections.
  270        editor.undo(&Undo, window, cx);
  271        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  272        assert_eq!(editor.text(cx), "123456");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![0..0]
  276        );
  277
  278        // Redo the first two transactions together.
  279        editor.redo(&Redo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![5..5]
  284        );
  285
  286        // Redo the last transaction on its own.
  287        editor.redo(&Redo, window, cx);
  288        assert_eq!(editor.text(cx), "ab2cde6");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![6..6]
  292        );
  293
  294        // Test empty transactions.
  295        editor.start_transaction_at(now, window, cx);
  296        editor.end_transaction_at(now, cx);
  297        editor.undo(&Undo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299    });
  300}
  301
  302#[gpui::test]
  303fn test_ime_composition(cx: &mut TestAppContext) {
  304    init_test(cx, |_| {});
  305
  306    let buffer = cx.new(|cx| {
  307        let mut buffer = language::Buffer::local("abcde", cx);
  308        // Ensure automatic grouping doesn't occur.
  309        buffer.set_group_interval(Duration::ZERO);
  310        buffer
  311    });
  312
  313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  314    cx.add_window(|window, cx| {
  315        let mut editor = build_editor(buffer.clone(), window, cx);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  320        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  321        assert_eq!(editor.text(cx), "äbcde");
  322        assert_eq!(
  323            editor.marked_text_ranges(cx),
  324            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  325        );
  326
  327        // Finalize IME composition.
  328        editor.replace_text_in_range(None, "ā", window, cx);
  329        assert_eq!(editor.text(cx), "ābcde");
  330        assert_eq!(editor.marked_text_ranges(cx), None);
  331
  332        // IME composition edits are grouped and are undone/redone at once.
  333        editor.undo(&Default::default(), window, cx);
  334        assert_eq!(editor.text(cx), "abcde");
  335        assert_eq!(editor.marked_text_ranges(cx), None);
  336        editor.redo(&Default::default(), window, cx);
  337        assert_eq!(editor.text(cx), "ābcde");
  338        assert_eq!(editor.marked_text_ranges(cx), None);
  339
  340        // Start a new IME composition.
  341        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  345        );
  346
  347        // Undoing during an IME composition cancels it.
  348        editor.undo(&Default::default(), window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  353        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  354        assert_eq!(editor.text(cx), "ābcdè");
  355        assert_eq!(
  356            editor.marked_text_ranges(cx),
  357            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  358        );
  359
  360        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  361        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  362        assert_eq!(editor.text(cx), "ābcdę");
  363        assert_eq!(editor.marked_text_ranges(cx), None);
  364
  365        // Start a new IME composition with multiple cursors.
  366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  367            s.select_ranges([
  368                OffsetUtf16(1)..OffsetUtf16(1),
  369                OffsetUtf16(3)..OffsetUtf16(3),
  370                OffsetUtf16(5)..OffsetUtf16(5),
  371            ])
  372        });
  373        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  374        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                OffsetUtf16(0)..OffsetUtf16(3),
  379                OffsetUtf16(4)..OffsetUtf16(7),
  380                OffsetUtf16(8)..OffsetUtf16(11)
  381            ])
  382        );
  383
  384        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  385        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  386        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  387        assert_eq!(
  388            editor.marked_text_ranges(cx),
  389            Some(vec![
  390                OffsetUtf16(1)..OffsetUtf16(2),
  391                OffsetUtf16(5)..OffsetUtf16(6),
  392                OffsetUtf16(9)..OffsetUtf16(10)
  393            ])
  394        );
  395
  396        // Finalize IME composition with multiple cursors.
  397        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  398        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  399        assert_eq!(editor.marked_text_ranges(cx), None);
  400
  401        editor
  402    });
  403}
  404
  405#[gpui::test]
  406fn test_selection_with_mouse(cx: &mut TestAppContext) {
  407    init_test(cx, |_| {});
  408
  409    let editor = cx.add_window(|window, cx| {
  410        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  411        build_editor(buffer, window, cx)
  412    });
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  416    });
  417    assert_eq!(
  418        editor
  419            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  420            .unwrap(),
  421        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  422    );
  423
  424    _ = editor.update(cx, |editor, window, cx| {
  425        editor.update_selection(
  426            DisplayPoint::new(DisplayRow(3), 3),
  427            0,
  428            gpui::Point::<f32>::default(),
  429            window,
  430            cx,
  431        );
  432    });
  433
  434    assert_eq!(
  435        editor
  436            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  437            .unwrap(),
  438        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  439    );
  440
  441    _ = editor.update(cx, |editor, window, cx| {
  442        editor.update_selection(
  443            DisplayPoint::new(DisplayRow(1), 1),
  444            0,
  445            gpui::Point::<f32>::default(),
  446            window,
  447            cx,
  448        );
  449    });
  450
  451    assert_eq!(
  452        editor
  453            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  454            .unwrap(),
  455        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460        editor.update_selection(
  461            DisplayPoint::new(DisplayRow(3), 3),
  462            0,
  463            gpui::Point::<f32>::default(),
  464            window,
  465            cx,
  466        );
  467    });
  468
  469    assert_eq!(
  470        editor
  471            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  472            .unwrap(),
  473        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  478        editor.update_selection(
  479            DisplayPoint::new(DisplayRow(0), 0),
  480            0,
  481            gpui::Point::<f32>::default(),
  482            window,
  483            cx,
  484        );
  485    });
  486
  487    assert_eq!(
  488        editor
  489            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  490            .unwrap(),
  491        [
  492            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  493            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  494        ]
  495    );
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.end_selection(window, cx);
  499    });
  500
  501    assert_eq!(
  502        editor
  503            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  504            .unwrap(),
  505        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  506    );
  507}
  508
  509#[gpui::test]
  510fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  511    init_test(cx, |_| {});
  512
  513    let editor = cx.add_window(|window, cx| {
  514        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  515        build_editor(buffer, window, cx)
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.end_selection(window, cx);
  532    });
  533
  534    assert_eq!(
  535        editor
  536            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  537            .unwrap(),
  538        [
  539            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  540            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  541        ]
  542    );
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    assert_eq!(
  553        editor
  554            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  555            .unwrap(),
  556        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  557    );
  558}
  559
  560#[gpui::test]
  561fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  562    init_test(cx, |_| {});
  563
  564    let editor = cx.add_window(|window, cx| {
  565        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  566        build_editor(buffer, window, cx)
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  574        );
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.update_selection(
  579            DisplayPoint::new(DisplayRow(3), 3),
  580            0,
  581            gpui::Point::<f32>::default(),
  582            window,
  583            cx,
  584        );
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  588        );
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.cancel(&Cancel, window, cx);
  593        editor.update_selection(
  594            DisplayPoint::new(DisplayRow(1), 1),
  595            0,
  596            gpui::Point::<f32>::default(),
  597            window,
  598            cx,
  599        );
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  603        );
  604    });
  605}
  606
  607#[gpui::test]
  608fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  609    init_test(cx, |_| {});
  610
  611    let editor = cx.add_window(|window, cx| {
  612        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  613        build_editor(buffer, window, cx)
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  618        assert_eq!(
  619            editor.selections.display_ranges(cx),
  620            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  621        );
  622
  623        editor.move_down(&Default::default(), window, cx);
  624        assert_eq!(
  625            editor.selections.display_ranges(cx),
  626            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  627        );
  628
  629        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  630        assert_eq!(
  631            editor.selections.display_ranges(cx),
  632            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  633        );
  634
  635        editor.move_up(&Default::default(), window, cx);
  636        assert_eq!(
  637            editor.selections.display_ranges(cx),
  638            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  639        );
  640    });
  641}
  642
  643#[gpui::test]
  644fn test_extending_selection(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    let editor = cx.add_window(|window, cx| {
  648        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  649        build_editor(buffer, window, cx)
  650    });
  651
  652    _ = editor.update(cx, |editor, window, cx| {
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  654        editor.end_selection(window, cx);
  655        assert_eq!(
  656            editor.selections.display_ranges(cx),
  657            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  658        );
  659
  660        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            editor.selections.display_ranges(cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  673        );
  674
  675        editor.update_selection(
  676            DisplayPoint::new(DisplayRow(0), 1),
  677            0,
  678            gpui::Point::<f32>::default(),
  679            window,
  680            cx,
  681        );
  682        editor.end_selection(window, cx);
  683        assert_eq!(
  684            editor.selections.display_ranges(cx),
  685            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  686        );
  687
  688        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  689        editor.end_selection(window, cx);
  690        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  691        editor.end_selection(window, cx);
  692        assert_eq!(
  693            editor.selections.display_ranges(cx),
  694            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  695        );
  696
  697        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  698        assert_eq!(
  699            editor.selections.display_ranges(cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  701        );
  702
  703        editor.update_selection(
  704            DisplayPoint::new(DisplayRow(0), 6),
  705            0,
  706            gpui::Point::<f32>::default(),
  707            window,
  708            cx,
  709        );
  710        assert_eq!(
  711            editor.selections.display_ranges(cx),
  712            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  713        );
  714
  715        editor.update_selection(
  716            DisplayPoint::new(DisplayRow(0), 1),
  717            0,
  718            gpui::Point::<f32>::default(),
  719            window,
  720            cx,
  721        );
  722        editor.end_selection(window, cx);
  723        assert_eq!(
  724            editor.selections.display_ranges(cx),
  725            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  726        );
  727    });
  728}
  729
  730#[gpui::test]
  731fn test_clone(cx: &mut TestAppContext) {
  732    init_test(cx, |_| {});
  733
  734    let (text, selection_ranges) = marked_text_ranges(
  735        indoc! {"
  736            one
  737            two
  738            threeˇ
  739            four
  740            fiveˇ
  741        "},
  742        true,
  743    );
  744
  745    let editor = cx.add_window(|window, cx| {
  746        let buffer = MultiBuffer::build_simple(&text, cx);
  747        build_editor(buffer, window, cx)
  748    });
  749
  750    _ = editor.update(cx, |editor, window, cx| {
  751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  752            s.select_ranges(selection_ranges.clone())
  753        });
  754        editor.fold_creases(
  755            vec![
  756                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  757                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  758            ],
  759            true,
  760            window,
  761            cx,
  762        );
  763    });
  764
  765    let cloned_editor = editor
  766        .update(cx, |editor, _, cx| {
  767            cx.open_window(Default::default(), |window, cx| {
  768                cx.new(|cx| editor.clone(window, cx))
  769            })
  770        })
  771        .unwrap()
  772        .unwrap();
  773
  774    let snapshot = editor
  775        .update(cx, |e, window, cx| e.snapshot(window, cx))
  776        .unwrap();
  777    let cloned_snapshot = cloned_editor
  778        .update(cx, |e, window, cx| e.snapshot(window, cx))
  779        .unwrap();
  780
  781    assert_eq!(
  782        cloned_editor
  783            .update(cx, |e, _, cx| e.display_text(cx))
  784            .unwrap(),
  785        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  786    );
  787    assert_eq!(
  788        cloned_snapshot
  789            .folds_in_range(0..text.len())
  790            .collect::<Vec<_>>(),
  791        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  792    );
  793    assert_set_eq!(
  794        cloned_editor
  795            .update(cx, |editor, _, cx| editor
  796                .selections
  797                .ranges::<Point>(&editor.display_snapshot(cx)))
  798            .unwrap(),
  799        editor
  800            .update(cx, |editor, _, cx| editor
  801                .selections
  802                .ranges(&editor.display_snapshot(cx)))
  803            .unwrap()
  804    );
  805    assert_set_eq!(
  806        cloned_editor
  807            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  808            .unwrap(),
  809        editor
  810            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  811            .unwrap()
  812    );
  813}
  814
  815#[gpui::test]
  816async fn test_navigation_history(cx: &mut TestAppContext) {
  817    init_test(cx, |_| {});
  818
  819    use workspace::item::Item;
  820
  821    let fs = FakeFs::new(cx.executor());
  822    let project = Project::test(fs, [], cx).await;
  823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  824    let pane = workspace
  825        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  826        .unwrap();
  827
  828    _ = workspace.update(cx, |_v, window, cx| {
  829        cx.new(|cx| {
  830            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  831            let mut editor = build_editor(buffer, window, cx);
  832            let handle = cx.entity();
  833            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  834
  835            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  836                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  837            }
  838
  839            // Move the cursor a small distance.
  840            // Nothing is added to the navigation history.
  841            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  842                s.select_display_ranges([
  843                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  844                ])
  845            });
  846            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  847                s.select_display_ranges([
  848                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  849                ])
  850            });
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance.
  854            // The history can jump back to the previous position.
  855            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  856                s.select_display_ranges([
  857                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  858                ])
  859            });
  860            let nav_entry = pop_history(&mut editor, cx).unwrap();
  861            editor.navigate(nav_entry.data.unwrap(), window, cx);
  862            assert_eq!(nav_entry.item.id(), cx.entity_id());
  863            assert_eq!(
  864                editor.selections.display_ranges(cx),
  865                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  866            );
  867            assert!(pop_history(&mut editor, cx).is_none());
  868
  869            // Move the cursor a small distance via the mouse.
  870            // Nothing is added to the navigation history.
  871            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  872            editor.end_selection(window, cx);
  873            assert_eq!(
  874                editor.selections.display_ranges(cx),
  875                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  876            );
  877            assert!(pop_history(&mut editor, cx).is_none());
  878
  879            // Move the cursor a large distance via the mouse.
  880            // The history can jump back to the previous position.
  881            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  882            editor.end_selection(window, cx);
  883            assert_eq!(
  884                editor.selections.display_ranges(cx),
  885                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  886            );
  887            let nav_entry = pop_history(&mut editor, cx).unwrap();
  888            editor.navigate(nav_entry.data.unwrap(), window, cx);
  889            assert_eq!(nav_entry.item.id(), cx.entity_id());
  890            assert_eq!(
  891                editor.selections.display_ranges(cx),
  892                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  893            );
  894            assert!(pop_history(&mut editor, cx).is_none());
  895
  896            // Set scroll position to check later
  897            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  898            let original_scroll_position = editor.scroll_manager.anchor();
  899
  900            // Jump to the end of the document and adjust scroll
  901            editor.move_to_end(&MoveToEnd, window, cx);
  902            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  903            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  904
  905            let nav_entry = pop_history(&mut editor, cx).unwrap();
  906            editor.navigate(nav_entry.data.unwrap(), window, cx);
  907            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  908
  909            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  910            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  911            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  912            let invalid_point = Point::new(9999, 0);
  913            editor.navigate(
  914                Box::new(NavigationData {
  915                    cursor_anchor: invalid_anchor,
  916                    cursor_position: invalid_point,
  917                    scroll_anchor: ScrollAnchor {
  918                        anchor: invalid_anchor,
  919                        offset: Default::default(),
  920                    },
  921                    scroll_top_row: invalid_point.row,
  922                }),
  923                window,
  924                cx,
  925            );
  926            assert_eq!(
  927                editor.selections.display_ranges(cx),
  928                &[editor.max_point(cx)..editor.max_point(cx)]
  929            );
  930            assert_eq!(
  931                editor.scroll_position(cx),
  932                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  933            );
  934
  935            editor
  936        })
  937    });
  938}
  939
  940#[gpui::test]
  941fn test_cancel(cx: &mut TestAppContext) {
  942    init_test(cx, |_| {});
  943
  944    let editor = cx.add_window(|window, cx| {
  945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  946        build_editor(buffer, window, cx)
  947    });
  948
  949    _ = editor.update(cx, |editor, window, cx| {
  950        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  951        editor.update_selection(
  952            DisplayPoint::new(DisplayRow(1), 1),
  953            0,
  954            gpui::Point::<f32>::default(),
  955            window,
  956            cx,
  957        );
  958        editor.end_selection(window, cx);
  959
  960        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  961        editor.update_selection(
  962            DisplayPoint::new(DisplayRow(0), 3),
  963            0,
  964            gpui::Point::<f32>::default(),
  965            window,
  966            cx,
  967        );
  968        editor.end_selection(window, cx);
  969        assert_eq!(
  970            editor.selections.display_ranges(cx),
  971            [
  972                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  973                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  974            ]
  975        );
  976    });
  977
  978    _ = editor.update(cx, |editor, window, cx| {
  979        editor.cancel(&Cancel, window, cx);
  980        assert_eq!(
  981            editor.selections.display_ranges(cx),
  982            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  983        );
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.cancel(&Cancel, window, cx);
  988        assert_eq!(
  989            editor.selections.display_ranges(cx),
  990            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_fold_action(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let editor = cx.add_window(|window, cx| {
 1000        let buffer = MultiBuffer::build_simple(
 1001            &"
 1002                impl Foo {
 1003                    // Hello!
 1004
 1005                    fn a() {
 1006                        1
 1007                    }
 1008
 1009                    fn b() {
 1010                        2
 1011                    }
 1012
 1013                    fn c() {
 1014                        3
 1015                    }
 1016                }
 1017            "
 1018            .unindent(),
 1019            cx,
 1020        );
 1021        build_editor(buffer, window, cx)
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1026            s.select_display_ranges([
 1027                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1028            ]);
 1029        });
 1030        editor.fold(&Fold, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                impl Foo {
 1035                    // Hello!
 1036
 1037                    fn a() {
 1038                        1
 1039                    }
 1040
 1041                    fn b() {⋯
 1042                    }
 1043
 1044                    fn c() {⋯
 1045                    }
 1046                }
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {⋯
 1056                }
 1057            "
 1058            .unindent(),
 1059        );
 1060
 1061        editor.unfold_lines(&UnfoldLines, window, cx);
 1062        assert_eq!(
 1063            editor.display_text(cx),
 1064            "
 1065                impl Foo {
 1066                    // Hello!
 1067
 1068                    fn a() {
 1069                        1
 1070                    }
 1071
 1072                    fn b() {⋯
 1073                    }
 1074
 1075                    fn c() {⋯
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            editor.buffer.read(cx).read(cx).text()
 1086        );
 1087    });
 1088}
 1089
 1090#[gpui::test]
 1091fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1092    init_test(cx, |_| {});
 1093
 1094    let editor = cx.add_window(|window, cx| {
 1095        let buffer = MultiBuffer::build_simple(
 1096            &"
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():
 1104                        print(2)
 1105
 1106                    def c():
 1107                        print(3)
 1108            "
 1109            .unindent(),
 1110            cx,
 1111        );
 1112        build_editor(buffer, window, cx)
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1117            s.select_display_ranges([
 1118                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1119            ]);
 1120        });
 1121        editor.fold(&Fold, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.fold(&Fold, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            "
 1142                class Foo:⋯
 1143            "
 1144            .unindent(),
 1145        );
 1146
 1147        editor.unfold_lines(&UnfoldLines, window, cx);
 1148        assert_eq!(
 1149            editor.display_text(cx),
 1150            "
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():⋯
 1158
 1159                    def c():⋯
 1160            "
 1161            .unindent(),
 1162        );
 1163
 1164        editor.unfold_lines(&UnfoldLines, window, cx);
 1165        assert_eq!(
 1166            editor.display_text(cx),
 1167            editor.buffer.read(cx).read(cx).text()
 1168        );
 1169    });
 1170}
 1171
 1172#[gpui::test]
 1173fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1174    init_test(cx, |_| {});
 1175
 1176    let editor = cx.add_window(|window, cx| {
 1177        let buffer = MultiBuffer::build_simple(
 1178            &"
 1179                class Foo:
 1180                    # Hello!
 1181
 1182                    def a():
 1183                        print(1)
 1184
 1185                    def b():
 1186                        print(2)
 1187
 1188
 1189                    def c():
 1190                        print(3)
 1191
 1192
 1193            "
 1194            .unindent(),
 1195            cx,
 1196        );
 1197        build_editor(buffer, window, cx)
 1198    });
 1199
 1200    _ = editor.update(cx, |editor, window, cx| {
 1201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1202            s.select_display_ranges([
 1203                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1204            ]);
 1205        });
 1206        editor.fold(&Fold, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218
 1219                    def c():⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:⋯
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        editor.unfold_lines(&UnfoldLines, window, cx);
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            "
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():⋯
 1248
 1249
 1250                    def c():⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            editor.buffer.read(cx).read(cx).text()
 1261        );
 1262    });
 1263}
 1264
 1265#[gpui::test]
 1266fn test_fold_at_level(cx: &mut TestAppContext) {
 1267    init_test(cx, |_| {});
 1268
 1269    let editor = cx.add_window(|window, cx| {
 1270        let buffer = MultiBuffer::build_simple(
 1271            &"
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():
 1279                        print(2)
 1280
 1281
 1282                class Bar:
 1283                    # World!
 1284
 1285                    def a():
 1286                        print(1)
 1287
 1288                    def b():
 1289                        print(2)
 1290
 1291
 1292            "
 1293            .unindent(),
 1294            cx,
 1295        );
 1296        build_editor(buffer, window, cx)
 1297    });
 1298
 1299    _ = editor.update(cx, |editor, window, cx| {
 1300        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1301        assert_eq!(
 1302            editor.display_text(cx),
 1303            "
 1304                class Foo:
 1305                    # Hello!
 1306
 1307                    def a():⋯
 1308
 1309                    def b():⋯
 1310
 1311
 1312                class Bar:
 1313                    # World!
 1314
 1315                    def a():⋯
 1316
 1317                    def b():⋯
 1318
 1319
 1320            "
 1321            .unindent(),
 1322        );
 1323
 1324        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1325        assert_eq!(
 1326            editor.display_text(cx),
 1327            "
 1328                class Foo:⋯
 1329
 1330
 1331                class Bar:⋯
 1332
 1333
 1334            "
 1335            .unindent(),
 1336        );
 1337
 1338        editor.unfold_all(&UnfoldAll, window, cx);
 1339        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1340        assert_eq!(
 1341            editor.display_text(cx),
 1342            "
 1343                class Foo:
 1344                    # Hello!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353                class Bar:
 1354                    # World!
 1355
 1356                    def a():
 1357                        print(1)
 1358
 1359                    def b():
 1360                        print(2)
 1361
 1362
 1363            "
 1364            .unindent(),
 1365        );
 1366
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            editor.buffer.read(cx).read(cx).text()
 1370        );
 1371        let (_, positions) = marked_text_ranges(
 1372            &"
 1373                       class Foo:
 1374                           # Hello!
 1375
 1376                           def a():
 1377                              print(1)
 1378
 1379                           def b():
 1380                               p«riˇ»nt(2)
 1381
 1382
 1383                       class Bar:
 1384                           # World!
 1385
 1386                           def a():
 1387                               «ˇprint(1)
 1388
 1389                           def b():
 1390                               print(2)»
 1391
 1392
 1393                   "
 1394            .unindent(),
 1395            true,
 1396        );
 1397
 1398        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1399            s.select_ranges(positions)
 1400        });
 1401
 1402        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1403        assert_eq!(
 1404            editor.display_text(cx),
 1405            "
 1406                class Foo:
 1407                    # Hello!
 1408
 1409                    def a():⋯
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428    });
 1429}
 1430
 1431#[gpui::test]
 1432fn test_move_cursor(cx: &mut TestAppContext) {
 1433    init_test(cx, |_| {});
 1434
 1435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1436    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1437
 1438    buffer.update(cx, |buffer, cx| {
 1439        buffer.edit(
 1440            vec![
 1441                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1442                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1443            ],
 1444            None,
 1445            cx,
 1446        );
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1452        );
 1453
 1454        editor.move_down(&MoveDown, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1458        );
 1459
 1460        editor.move_right(&MoveRight, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1464        );
 1465
 1466        editor.move_left(&MoveLeft, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1470        );
 1471
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1476        );
 1477
 1478        editor.move_to_end(&MoveToEnd, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1482        );
 1483
 1484        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1488        );
 1489
 1490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1491            s.select_display_ranges([
 1492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1493            ]);
 1494        });
 1495        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1499        );
 1500
 1501        editor.select_to_end(&SelectToEnd, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1505        );
 1506    });
 1507}
 1508
 1509#[gpui::test]
 1510fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1511    init_test(cx, |_| {});
 1512
 1513    let editor = cx.add_window(|window, cx| {
 1514        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1515        build_editor(buffer, window, cx)
 1516    });
 1517
 1518    assert_eq!('🟥'.len_utf8(), 4);
 1519    assert_eq!('α'.len_utf8(), 2);
 1520
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.fold_creases(
 1523            vec![
 1524                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1525                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1526                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1527            ],
 1528            true,
 1529            window,
 1530            cx,
 1531        );
 1532        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1533
 1534        editor.move_right(&MoveRight, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(0, "🟥".len())]
 1538        );
 1539        editor.move_right(&MoveRight, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(0, "🟥🟧".len())]
 1543        );
 1544        editor.move_right(&MoveRight, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[empty_range(0, "🟥🟧⋯".len())]
 1548        );
 1549
 1550        editor.move_down(&MoveDown, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(1, "ab⋯e".len())]
 1554        );
 1555        editor.move_left(&MoveLeft, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(1, "ab⋯".len())]
 1559        );
 1560        editor.move_left(&MoveLeft, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(1, "ab".len())]
 1564        );
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(1, "a".len())]
 1569        );
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "α".len())]
 1575        );
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(2, "αβ".len())]
 1580        );
 1581        editor.move_right(&MoveRight, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[empty_range(2, "αβ⋯".len())]
 1585        );
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[empty_range(2, "αβ⋯ε".len())]
 1590        );
 1591
 1592        editor.move_up(&MoveUp, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(1, "ab⋯e".len())]
 1596        );
 1597        editor.move_down(&MoveDown, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[empty_range(2, "αβ⋯ε".len())]
 1601        );
 1602        editor.move_up(&MoveUp, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[empty_range(1, "ab⋯e".len())]
 1606        );
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[empty_range(0, "🟥🟧".len())]
 1612        );
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[empty_range(0, "🟥".len())]
 1617        );
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(
 1620            editor.selections.display_ranges(cx),
 1621            &[empty_range(0, "".len())]
 1622        );
 1623    });
 1624}
 1625
 1626#[gpui::test]
 1627fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1628    init_test(cx, |_| {});
 1629
 1630    let editor = cx.add_window(|window, cx| {
 1631        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1632        build_editor(buffer, window, cx)
 1633    });
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1636            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1637        });
 1638
 1639        // moving above start of document should move selection to start of document,
 1640        // but the next move down should still be at the original goal_x
 1641        editor.move_up(&MoveUp, window, cx);
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[empty_range(0, "".len())]
 1645        );
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(1, "abcd".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(2, "αβγ".len())]
 1657        );
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(3, "abcd".len())]
 1663        );
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1669        );
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(
 1674            editor.selections.display_ranges(cx),
 1675            &[empty_range(5, "".len())]
 1676        );
 1677
 1678        editor.move_down(&MoveDown, window, cx);
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[empty_range(5, "".len())]
 1682        );
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(
 1686            editor.selections.display_ranges(cx),
 1687            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1688        );
 1689
 1690        editor.move_up(&MoveUp, window, cx);
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[empty_range(3, "abcd".len())]
 1694        );
 1695
 1696        editor.move_up(&MoveUp, window, cx);
 1697        assert_eq!(
 1698            editor.selections.display_ranges(cx),
 1699            &[empty_range(2, "αβγ".len())]
 1700        );
 1701    });
 1702}
 1703
 1704#[gpui::test]
 1705fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1706    init_test(cx, |_| {});
 1707    let move_to_beg = MoveToBeginningOfLine {
 1708        stop_at_soft_wraps: true,
 1709        stop_at_indent: true,
 1710    };
 1711
 1712    let delete_to_beg = DeleteToBeginningOfLine {
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: true,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1726            s.select_display_ranges([
 1727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1728                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1729            ]);
 1730        });
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1735        assert_eq!(
 1736            editor.selections.display_ranges(cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1740            ]
 1741        );
 1742    });
 1743
 1744    _ = editor.update(cx, |editor, window, cx| {
 1745        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1746        assert_eq!(
 1747            editor.selections.display_ranges(cx),
 1748            &[
 1749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1751            ]
 1752        );
 1753    });
 1754
 1755    _ = editor.update(cx, |editor, window, cx| {
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            editor.selections.display_ranges(cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.move_to_end_of_line(&move_to_end, window, cx);
 1768        assert_eq!(
 1769            editor.selections.display_ranges(cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1772                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1773            ]
 1774        );
 1775    });
 1776
 1777    // Moving to the end of line again is a no-op.
 1778    _ = editor.update(cx, |editor, window, cx| {
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            editor.selections.display_ranges(cx),
 1782            &[
 1783                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1784                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1785            ]
 1786        );
 1787    });
 1788
 1789    _ = editor.update(cx, |editor, window, cx| {
 1790        editor.move_left(&MoveLeft, window, cx);
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            editor.selections.display_ranges(cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            editor.selections.display_ranges(cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_beginning_of_line(
 1828            &SelectToBeginningOfLine {
 1829                stop_at_soft_wraps: true,
 1830                stop_at_indent: true,
 1831            },
 1832            window,
 1833            cx,
 1834        );
 1835        assert_eq!(
 1836            editor.selections.display_ranges(cx),
 1837            &[
 1838                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1839                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1840            ]
 1841        );
 1842    });
 1843
 1844    _ = editor.update(cx, |editor, window, cx| {
 1845        editor.select_to_end_of_line(
 1846            &SelectToEndOfLine {
 1847                stop_at_soft_wraps: true,
 1848            },
 1849            window,
 1850            cx,
 1851        );
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1856                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1857            ]
 1858        );
 1859    });
 1860
 1861    _ = editor.update(cx, |editor, window, cx| {
 1862        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1863        assert_eq!(editor.display_text(cx), "ab\n  de");
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1869            ]
 1870        );
 1871    });
 1872
 1873    _ = editor.update(cx, |editor, window, cx| {
 1874        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1875        assert_eq!(editor.display_text(cx), "\n");
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889    let move_to_beg = MoveToBeginningOfLine {
 1890        stop_at_soft_wraps: false,
 1891        stop_at_indent: false,
 1892    };
 1893
 1894    let move_to_end = MoveToEndOfLine {
 1895        stop_at_soft_wraps: false,
 1896    };
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.set_wrap_width(Some(140.0.into()), cx);
 1905
 1906        // We expect the following lines after wrapping
 1907        // ```
 1908        // thequickbrownfox
 1909        // jumpedoverthelazydo
 1910        // gs
 1911        // ```
 1912        // The final `gs` was soft-wrapped onto a new line.
 1913        assert_eq!(
 1914            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1915            editor.display_text(cx),
 1916        );
 1917
 1918        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1919        // Start the cursor at the `k` on the first line
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1923            ]);
 1924        });
 1925
 1926        // Moving to the beginning of the line should put us at the beginning of the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1930            editor.selections.display_ranges(cx)
 1931        );
 1932
 1933        // Moving to the end of the line should put us at the end of the line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1937            editor.selections.display_ranges(cx)
 1938        );
 1939
 1940        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1941        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1943            s.select_display_ranges([
 1944                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1945            ]);
 1946        });
 1947
 1948        // Moving to the beginning of the line should put us at the start of the second line of
 1949        // display text, i.e., the `j`.
 1950        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1951        assert_eq!(
 1952            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1953            editor.selections.display_ranges(cx)
 1954        );
 1955
 1956        // Moving to the beginning of the line again should be a no-op.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1960            editor.selections.display_ranges(cx)
 1961        );
 1962
 1963        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1964        // next display line.
 1965        editor.move_to_end_of_line(&move_to_end, window, cx);
 1966        assert_eq!(
 1967            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1968            editor.selections.display_ranges(cx)
 1969        );
 1970
 1971        // Moving to the end of the line again should be a no-op.
 1972        editor.move_to_end_of_line(&move_to_end, window, cx);
 1973        assert_eq!(
 1974            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1975            editor.selections.display_ranges(cx)
 1976        );
 1977    });
 1978}
 1979
 1980#[gpui::test]
 1981fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1982    init_test(cx, |_| {});
 1983
 1984    let move_to_beg = MoveToBeginningOfLine {
 1985        stop_at_soft_wraps: true,
 1986        stop_at_indent: true,
 1987    };
 1988
 1989    let select_to_beg = SelectToBeginningOfLine {
 1990        stop_at_soft_wraps: true,
 1991        stop_at_indent: true,
 1992    };
 1993
 1994    let delete_to_beg = DeleteToBeginningOfLine {
 1995        stop_at_indent: true,
 1996    };
 1997
 1998    let move_to_end = MoveToEndOfLine {
 1999        stop_at_soft_wraps: false,
 2000    };
 2001
 2002    let editor = cx.add_window(|window, cx| {
 2003        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2004        build_editor(buffer, window, cx)
 2005    });
 2006
 2007    _ = editor.update(cx, |editor, window, cx| {
 2008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2009            s.select_display_ranges([
 2010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2011                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2012            ]);
 2013        });
 2014
 2015        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2016        // and the second cursor at the first non-whitespace character in the line.
 2017        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2018        assert_eq!(
 2019            editor.selections.display_ranges(cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should move the second cursor to the beginning of the line.
 2028        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2029        assert_eq!(
 2030            editor.selections.display_ranges(cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2038        // and should move the second cursor back to the first non-whitespace character in the line.
 2039        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2040        assert_eq!(
 2041            editor.selections.display_ranges(cx),
 2042            &[
 2043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2044                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2045            ]
 2046        );
 2047
 2048        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2049        // and to the first non-whitespace character in the line for the second cursor.
 2050        editor.move_to_end_of_line(&move_to_end, window, cx);
 2051        editor.move_left(&MoveLeft, window, cx);
 2052        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should select to the beginning of the line for the second cursor.
 2063        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2064        assert_eq!(
 2065            editor.selections.display_ranges(cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2073        // and should delete to the first non-whitespace character in the line for the second cursor.
 2074        editor.move_to_end_of_line(&move_to_end, window, cx);
 2075        editor.move_left(&MoveLeft, window, cx);
 2076        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2077        assert_eq!(editor.text(cx), "c\n  f");
 2078    });
 2079}
 2080
 2081#[gpui::test]
 2082fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2083    init_test(cx, |_| {});
 2084
 2085    let move_to_beg = MoveToBeginningOfLine {
 2086        stop_at_soft_wraps: true,
 2087        stop_at_indent: true,
 2088    };
 2089
 2090    let editor = cx.add_window(|window, cx| {
 2091        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2092        build_editor(buffer, window, cx)
 2093    });
 2094
 2095    _ = editor.update(cx, |editor, window, cx| {
 2096        // test cursor between line_start and indent_start
 2097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2098            s.select_display_ranges([
 2099                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2100            ]);
 2101        });
 2102
 2103        // cursor should move to line_start
 2104        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2108        );
 2109
 2110        // cursor should move to indent_start
 2111        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2115        );
 2116
 2117        // cursor should move to back to line_start
 2118        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2119        assert_eq!(
 2120            editor.selections.display_ranges(cx),
 2121            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2122        );
 2123    });
 2124}
 2125
 2126#[gpui::test]
 2127fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2128    init_test(cx, |_| {});
 2129
 2130    let editor = cx.add_window(|window, cx| {
 2131        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2132        build_editor(buffer, window, cx)
 2133    });
 2134    _ = editor.update(cx, |editor, window, cx| {
 2135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2136            s.select_display_ranges([
 2137                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2138                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2139            ])
 2140        });
 2141        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2142        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2143
 2144        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2145        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2148        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2149
 2150        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2151        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2152
 2153        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2154        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2158
 2159        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2160        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2164
 2165        editor.move_right(&MoveRight, window, cx);
 2166        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2167        assert_selection_ranges(
 2168            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2169            editor,
 2170            cx,
 2171        );
 2172
 2173        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2174        assert_selection_ranges(
 2175            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2176            editor,
 2177            cx,
 2178        );
 2179
 2180        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2181        assert_selection_ranges(
 2182            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2183            editor,
 2184            cx,
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192
 2193    let editor = cx.add_window(|window, cx| {
 2194        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2195        build_editor(buffer, window, cx)
 2196    });
 2197
 2198    _ = editor.update(cx, |editor, window, cx| {
 2199        editor.set_wrap_width(Some(140.0.into()), cx);
 2200        assert_eq!(
 2201            editor.display_text(cx),
 2202            "use one::{\n    two::three::\n    four::five\n};"
 2203        );
 2204
 2205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2206            s.select_display_ranges([
 2207                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2208            ]);
 2209        });
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            editor.selections.display_ranges(cx),
 2214            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2215        );
 2216
 2217        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2218        assert_eq!(
 2219            editor.selections.display_ranges(cx),
 2220            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2221        );
 2222
 2223        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2224        assert_eq!(
 2225            editor.selections.display_ranges(cx),
 2226            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2227        );
 2228
 2229        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2230        assert_eq!(
 2231            editor.selections.display_ranges(cx),
 2232            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2233        );
 2234
 2235        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2236        assert_eq!(
 2237            editor.selections.display_ranges(cx),
 2238            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2239        );
 2240
 2241        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2242        assert_eq!(
 2243            editor.selections.display_ranges(cx),
 2244            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2245        );
 2246    });
 2247}
 2248
 2249#[gpui::test]
 2250async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2251    init_test(cx, |_| {});
 2252    let mut cx = EditorTestContext::new(cx).await;
 2253
 2254    let line_height = cx.editor(|editor, window, _| {
 2255        editor
 2256            .style()
 2257            .unwrap()
 2258            .text
 2259            .line_height_in_pixels(window.rem_size())
 2260    });
 2261    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2262
 2263    cx.set_state(
 2264        &r#"ˇone
 2265        two
 2266
 2267        three
 2268        fourˇ
 2269        five
 2270
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285        ˇ
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"one
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300        ˇ
 2301        sixˇ"#
 2302            .unindent(),
 2303    );
 2304
 2305    cx.update_editor(|editor, window, cx| {
 2306        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2307    });
 2308    cx.assert_editor_state(
 2309        &r#"one
 2310        two
 2311
 2312        three
 2313        four
 2314        five
 2315
 2316        sixˇ"#
 2317            .unindent(),
 2318    );
 2319
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2322    });
 2323    cx.assert_editor_state(
 2324        &r#"one
 2325        two
 2326
 2327        three
 2328        four
 2329        five
 2330        ˇ
 2331        six"#
 2332            .unindent(),
 2333    );
 2334
 2335    cx.update_editor(|editor, window, cx| {
 2336        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2337    });
 2338    cx.assert_editor_state(
 2339        &r#"one
 2340        two
 2341        ˇ
 2342        three
 2343        four
 2344        five
 2345
 2346        six"#
 2347            .unindent(),
 2348    );
 2349
 2350    cx.update_editor(|editor, window, cx| {
 2351        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2352    });
 2353    cx.assert_editor_state(
 2354        &r#"ˇone
 2355        two
 2356
 2357        three
 2358        four
 2359        five
 2360
 2361        six"#
 2362            .unindent(),
 2363    );
 2364}
 2365
 2366#[gpui::test]
 2367async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2368    init_test(cx, |_| {});
 2369    let mut cx = EditorTestContext::new(cx).await;
 2370    let line_height = cx.editor(|editor, window, _| {
 2371        editor
 2372            .style()
 2373            .unwrap()
 2374            .text
 2375            .line_height_in_pixels(window.rem_size())
 2376    });
 2377    let window = cx.window;
 2378    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2379
 2380    cx.set_state(
 2381        r#"ˇone
 2382        two
 2383        three
 2384        four
 2385        five
 2386        six
 2387        seven
 2388        eight
 2389        nine
 2390        ten
 2391        "#,
 2392    );
 2393
 2394    cx.update_editor(|editor, window, cx| {
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 0.)
 2398        );
 2399        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2400        assert_eq!(
 2401            editor.snapshot(window, cx).scroll_position(),
 2402            gpui::Point::new(0., 3.)
 2403        );
 2404        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2405        assert_eq!(
 2406            editor.snapshot(window, cx).scroll_position(),
 2407            gpui::Point::new(0., 6.)
 2408        );
 2409        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2410        assert_eq!(
 2411            editor.snapshot(window, cx).scroll_position(),
 2412            gpui::Point::new(0., 3.)
 2413        );
 2414
 2415        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 1.)
 2419        );
 2420        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2421        assert_eq!(
 2422            editor.snapshot(window, cx).scroll_position(),
 2423            gpui::Point::new(0., 3.)
 2424        );
 2425    });
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_autoscroll(cx: &mut TestAppContext) {
 2430    init_test(cx, |_| {});
 2431    let mut cx = EditorTestContext::new(cx).await;
 2432
 2433    let line_height = cx.update_editor(|editor, window, cx| {
 2434        editor.set_vertical_scroll_margin(2, cx);
 2435        editor
 2436            .style()
 2437            .unwrap()
 2438            .text
 2439            .line_height_in_pixels(window.rem_size())
 2440    });
 2441    let window = cx.window;
 2442    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2443
 2444    cx.set_state(
 2445        r#"ˇone
 2446            two
 2447            three
 2448            four
 2449            five
 2450            six
 2451            seven
 2452            eight
 2453            nine
 2454            ten
 2455        "#,
 2456    );
 2457    cx.update_editor(|editor, window, cx| {
 2458        assert_eq!(
 2459            editor.snapshot(window, cx).scroll_position(),
 2460            gpui::Point::new(0., 0.0)
 2461        );
 2462    });
 2463
 2464    // Add a cursor below the visible area. Since both cursors cannot fit
 2465    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2466    // allows the vertical scroll margin below that cursor.
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.change_selections(Default::default(), window, cx, |selections| {
 2469            selections.select_ranges([
 2470                Point::new(0, 0)..Point::new(0, 0),
 2471                Point::new(6, 0)..Point::new(6, 0),
 2472            ]);
 2473        })
 2474    });
 2475    cx.update_editor(|editor, window, cx| {
 2476        assert_eq!(
 2477            editor.snapshot(window, cx).scroll_position(),
 2478            gpui::Point::new(0., 3.0)
 2479        );
 2480    });
 2481
 2482    // Move down. The editor cursor scrolls down to track the newest cursor.
 2483    cx.update_editor(|editor, window, cx| {
 2484        editor.move_down(&Default::default(), window, cx);
 2485    });
 2486    cx.update_editor(|editor, window, cx| {
 2487        assert_eq!(
 2488            editor.snapshot(window, cx).scroll_position(),
 2489            gpui::Point::new(0., 4.0)
 2490        );
 2491    });
 2492
 2493    // Add a cursor above the visible area. Since both cursors fit on screen,
 2494    // the editor scrolls to show both.
 2495    cx.update_editor(|editor, window, cx| {
 2496        editor.change_selections(Default::default(), window, cx, |selections| {
 2497            selections.select_ranges([
 2498                Point::new(1, 0)..Point::new(1, 0),
 2499                Point::new(6, 0)..Point::new(6, 0),
 2500            ]);
 2501        })
 2502    });
 2503    cx.update_editor(|editor, window, cx| {
 2504        assert_eq!(
 2505            editor.snapshot(window, cx).scroll_position(),
 2506            gpui::Point::new(0., 1.0)
 2507        );
 2508    });
 2509}
 2510
 2511#[gpui::test]
 2512async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2513    init_test(cx, |_| {});
 2514    let mut cx = EditorTestContext::new(cx).await;
 2515
 2516    let line_height = cx.editor(|editor, window, _cx| {
 2517        editor
 2518            .style()
 2519            .unwrap()
 2520            .text
 2521            .line_height_in_pixels(window.rem_size())
 2522    });
 2523    let window = cx.window;
 2524    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2525    cx.set_state(
 2526        &r#"
 2527        ˇone
 2528        two
 2529        threeˇ
 2530        four
 2531        five
 2532        six
 2533        seven
 2534        eight
 2535        nine
 2536        ten
 2537        "#
 2538        .unindent(),
 2539    );
 2540
 2541    cx.update_editor(|editor, window, cx| {
 2542        editor.move_page_down(&MovePageDown::default(), window, cx)
 2543    });
 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| {
 2561        editor.move_page_down(&MovePageDown::default(), window, cx)
 2562    });
 2563    cx.assert_editor_state(
 2564        &r#"
 2565        one
 2566        two
 2567        three
 2568        four
 2569        five
 2570        six
 2571        ˇseven
 2572        eight
 2573        nineˇ
 2574        ten
 2575        "#
 2576        .unindent(),
 2577    );
 2578
 2579    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2580    cx.assert_editor_state(
 2581        &r#"
 2582        one
 2583        two
 2584        three
 2585        ˇfour
 2586        five
 2587        sixˇ
 2588        seven
 2589        eight
 2590        nine
 2591        ten
 2592        "#
 2593        .unindent(),
 2594    );
 2595
 2596    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        ˇone
 2600        two
 2601        threeˇ
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ten
 2609        "#
 2610        .unindent(),
 2611    );
 2612
 2613    // Test select collapsing
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.move_page_down(&MovePageDown::default(), window, cx);
 2616        editor.move_page_down(&MovePageDown::default(), window, cx);
 2617        editor.move_page_down(&MovePageDown::default(), window, cx);
 2618    });
 2619    cx.assert_editor_state(
 2620        &r#"
 2621        one
 2622        two
 2623        three
 2624        four
 2625        five
 2626        six
 2627        seven
 2628        eight
 2629        nine
 2630        ˇten
 2631        ˇ"#
 2632        .unindent(),
 2633    );
 2634}
 2635
 2636#[gpui::test]
 2637async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2638    init_test(cx, |_| {});
 2639    let mut cx = EditorTestContext::new(cx).await;
 2640    cx.set_state("one «two threeˇ» four");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_beginning_of_line(
 2643            &DeleteToBeginningOfLine {
 2644                stop_at_indent: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649        assert_eq!(editor.text(cx), " four");
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    // For an empty selection, the preceding word fragment is deleted.
 2660    // For non-empty selections, only selected characters are deleted.
 2661    cx.set_state("onˇe two t«hreˇ»e four");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: false,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    cx.assert_editor_state("ˇe two tˇe four");
 2673
 2674    cx.set_state("e tˇwo te «fˇ»our");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("e tˇ te ˇour");
 2686}
 2687
 2688#[gpui::test]
 2689async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2690    init_test(cx, |_| {});
 2691
 2692    let mut cx = EditorTestContext::new(cx).await;
 2693
 2694    cx.set_state("here is some text    ˇwith a space");
 2695    cx.update_editor(|editor, window, cx| {
 2696        editor.delete_to_previous_word_start(
 2697            &DeleteToPreviousWordStart {
 2698                ignore_newlines: false,
 2699                ignore_brackets: true,
 2700            },
 2701            window,
 2702            cx,
 2703        );
 2704    });
 2705    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2706    cx.assert_editor_state("here is some textˇwith a space");
 2707
 2708    cx.set_state("here is some text    ˇwith a space");
 2709    cx.update_editor(|editor, window, cx| {
 2710        editor.delete_to_previous_word_start(
 2711            &DeleteToPreviousWordStart {
 2712                ignore_newlines: false,
 2713                ignore_brackets: false,
 2714            },
 2715            window,
 2716            cx,
 2717        );
 2718    });
 2719    cx.assert_editor_state("here is some textˇwith a space");
 2720
 2721    cx.set_state("here is some textˇ    with a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_next_word_end(
 2724            &DeleteToNextWordEnd {
 2725                ignore_newlines: false,
 2726                ignore_brackets: true,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    // Same happens in the other direction.
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734
 2735    cx.set_state("here is some textˇ    with a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_next_word_end(
 2738            &DeleteToNextWordEnd {
 2739                ignore_newlines: false,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    cx.assert_editor_state("here is some textˇwith a space");
 2747
 2748    cx.set_state("here is some textˇ    with a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_next_word_end(
 2751            &DeleteToNextWordEnd {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("here is some textˇwith a space");
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    cx.assert_editor_state("here is some ˇwith a space");
 2771    cx.update_editor(|editor, window, cx| {
 2772        editor.delete_to_previous_word_start(
 2773            &DeleteToPreviousWordStart {
 2774                ignore_newlines: true,
 2775                ignore_brackets: false,
 2776            },
 2777            window,
 2778            cx,
 2779        );
 2780    });
 2781    // Single whitespaces are removed with the word behind them.
 2782    cx.assert_editor_state("here is ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("here ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_previous_word_start(
 2796            &DeleteToPreviousWordStart {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    cx.assert_editor_state("ˇwith a space");
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    cx.assert_editor_state("ˇwith a space");
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Same happens in the other direction.
 2827    cx.assert_editor_state("ˇ a space");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ space");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_next_word_end(
 2841            &DeleteToNextWordEnd {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850    cx.update_editor(|editor, window, cx| {
 2851        editor.delete_to_next_word_end(
 2852            &DeleteToNextWordEnd {
 2853                ignore_newlines: true,
 2854                ignore_brackets: false,
 2855            },
 2856            window,
 2857            cx,
 2858        );
 2859    });
 2860    cx.assert_editor_state("ˇ");
 2861    cx.update_editor(|editor, window, cx| {
 2862        editor.delete_to_previous_word_start(
 2863            &DeleteToPreviousWordStart {
 2864                ignore_newlines: true,
 2865                ignore_brackets: false,
 2866            },
 2867            window,
 2868            cx,
 2869        );
 2870    });
 2871    cx.assert_editor_state("ˇ");
 2872}
 2873
 2874#[gpui::test]
 2875async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2876    init_test(cx, |_| {});
 2877
 2878    let language = Arc::new(
 2879        Language::new(
 2880            LanguageConfig {
 2881                brackets: BracketPairConfig {
 2882                    pairs: vec![
 2883                        BracketPair {
 2884                            start: "\"".to_string(),
 2885                            end: "\"".to_string(),
 2886                            close: true,
 2887                            surround: true,
 2888                            newline: false,
 2889                        },
 2890                        BracketPair {
 2891                            start: "(".to_string(),
 2892                            end: ")".to_string(),
 2893                            close: true,
 2894                            surround: true,
 2895                            newline: true,
 2896                        },
 2897                    ],
 2898                    ..BracketPairConfig::default()
 2899                },
 2900                ..LanguageConfig::default()
 2901            },
 2902            Some(tree_sitter_rust::LANGUAGE.into()),
 2903        )
 2904        .with_brackets_query(
 2905            r#"
 2906                ("(" @open ")" @close)
 2907                ("\"" @open "\"" @close)
 2908            "#,
 2909        )
 2910        .unwrap(),
 2911    );
 2912
 2913    let mut cx = EditorTestContext::new(cx).await;
 2914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2915
 2916    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Deletion stops before brackets if asked to not ignore them.
 2928    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    // Deletion has to remove a single bracket and then stop again.
 2940    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_previous_word_start(
 2944            &DeleteToPreviousWordStart {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_previous_word_start(
 2956            &DeleteToPreviousWordStart {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_previous_word_start(
 2968            &DeleteToPreviousWordStart {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2977
 2978    cx.update_editor(|editor, window, cx| {
 2979        editor.delete_to_next_word_end(
 2980            &DeleteToNextWordEnd {
 2981                ignore_newlines: true,
 2982                ignore_brackets: false,
 2983            },
 2984            window,
 2985            cx,
 2986        );
 2987    });
 2988    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2989    cx.assert_editor_state(r#"ˇ");"#);
 2990
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_next_word_end(
 2993            &DeleteToNextWordEnd {
 2994                ignore_newlines: true,
 2995                ignore_brackets: false,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"ˇ"#);
 3002
 3003    cx.update_editor(|editor, window, cx| {
 3004        editor.delete_to_next_word_end(
 3005            &DeleteToNextWordEnd {
 3006                ignore_newlines: true,
 3007                ignore_brackets: false,
 3008            },
 3009            window,
 3010            cx,
 3011        );
 3012    });
 3013    cx.assert_editor_state(r#"ˇ"#);
 3014
 3015    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3016    cx.update_editor(|editor, window, cx| {
 3017        editor.delete_to_previous_word_start(
 3018            &DeleteToPreviousWordStart {
 3019                ignore_newlines: true,
 3020                ignore_brackets: true,
 3021            },
 3022            window,
 3023            cx,
 3024        );
 3025    });
 3026    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3027}
 3028
 3029#[gpui::test]
 3030fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    let editor = cx.add_window(|window, cx| {
 3034        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3035        build_editor(buffer, window, cx)
 3036    });
 3037    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3038        ignore_newlines: false,
 3039        ignore_brackets: false,
 3040    };
 3041    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3042        ignore_newlines: true,
 3043        ignore_brackets: false,
 3044    };
 3045
 3046    _ = editor.update(cx, |editor, window, cx| {
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_display_ranges([
 3049                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3050            ])
 3051        });
 3052        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3053        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3054        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3056        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3058        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3060        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3062        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3064    });
 3065}
 3066
 3067#[gpui::test]
 3068fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3069    init_test(cx, |_| {});
 3070
 3071    let editor = cx.add_window(|window, cx| {
 3072        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3073        build_editor(buffer, window, cx)
 3074    });
 3075    let del_to_next_word_end = DeleteToNextWordEnd {
 3076        ignore_newlines: false,
 3077        ignore_brackets: false,
 3078    };
 3079    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3080        ignore_newlines: true,
 3081        ignore_brackets: false,
 3082    };
 3083
 3084    _ = editor.update(cx, |editor, window, cx| {
 3085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3086            s.select_display_ranges([
 3087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3088            ])
 3089        });
 3090        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3091        assert_eq!(
 3092            editor.buffer.read(cx).read(cx).text(),
 3093            "one\n   two\nthree\n   four"
 3094        );
 3095        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3096        assert_eq!(
 3097            editor.buffer.read(cx).read(cx).text(),
 3098            "\n   two\nthree\n   four"
 3099        );
 3100        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3101        assert_eq!(
 3102            editor.buffer.read(cx).read(cx).text(),
 3103            "two\nthree\n   four"
 3104        );
 3105        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3107        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3108        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3109        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3110        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3111        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let editor = cx.add_window(|window, cx| {
 3144        let buffer = MultiBuffer::build_simple(
 3145            "
 3146                a
 3147                b(
 3148                    X
 3149                )
 3150                c(
 3151                    X
 3152                )
 3153            "
 3154            .unindent()
 3155            .as_str(),
 3156            cx,
 3157        );
 3158        let mut editor = build_editor(buffer, window, cx);
 3159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3160            s.select_ranges([
 3161                Point::new(2, 4)..Point::new(2, 5),
 3162                Point::new(5, 4)..Point::new(5, 5),
 3163            ])
 3164        });
 3165        editor
 3166    });
 3167
 3168    _ = editor.update(cx, |editor, window, cx| {
 3169        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3170        editor.buffer.update(cx, |buffer, cx| {
 3171            buffer.edit(
 3172                [
 3173                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3174                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3175                ],
 3176                None,
 3177                cx,
 3178            );
 3179            assert_eq!(
 3180                buffer.read(cx).text(),
 3181                "
 3182                    a
 3183                    b()
 3184                    c()
 3185                "
 3186                .unindent()
 3187            );
 3188        });
 3189        assert_eq!(
 3190            editor.selections.ranges(&editor.display_snapshot(cx)),
 3191            &[
 3192                Point::new(1, 2)..Point::new(1, 2),
 3193                Point::new(2, 2)..Point::new(2, 2),
 3194            ],
 3195        );
 3196
 3197        editor.newline(&Newline, window, cx);
 3198        assert_eq!(
 3199            editor.text(cx),
 3200            "
 3201                a
 3202                b(
 3203                )
 3204                c(
 3205                )
 3206            "
 3207            .unindent()
 3208        );
 3209
 3210        // The selections are moved after the inserted newlines
 3211        assert_eq!(
 3212            editor.selections.ranges(&editor.display_snapshot(cx)),
 3213            &[
 3214                Point::new(2, 0)..Point::new(2, 0),
 3215                Point::new(4, 0)..Point::new(4, 0),
 3216            ],
 3217        );
 3218    });
 3219}
 3220
 3221#[gpui::test]
 3222async fn test_newline_above(cx: &mut TestAppContext) {
 3223    init_test(cx, |settings| {
 3224        settings.defaults.tab_size = NonZeroU32::new(4)
 3225    });
 3226
 3227    let language = Arc::new(
 3228        Language::new(
 3229            LanguageConfig::default(),
 3230            Some(tree_sitter_rust::LANGUAGE.into()),
 3231        )
 3232        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3233        .unwrap(),
 3234    );
 3235
 3236    let mut cx = EditorTestContext::new(cx).await;
 3237    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3238    cx.set_state(indoc! {"
 3239        const a: ˇA = (
 3240 3241                «const_functionˇ»(ˇ),
 3242                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3243 3244        ˇ);ˇ
 3245    "});
 3246
 3247    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3248    cx.assert_editor_state(indoc! {"
 3249        ˇ
 3250        const a: A = (
 3251            ˇ
 3252            (
 3253                ˇ
 3254                ˇ
 3255                const_function(),
 3256                ˇ
 3257                ˇ
 3258                ˇ
 3259                ˇ
 3260                something_else,
 3261                ˇ
 3262            )
 3263            ˇ
 3264            ˇ
 3265        );
 3266    "});
 3267}
 3268
 3269#[gpui::test]
 3270async fn test_newline_below(cx: &mut TestAppContext) {
 3271    init_test(cx, |settings| {
 3272        settings.defaults.tab_size = NonZeroU32::new(4)
 3273    });
 3274
 3275    let language = Arc::new(
 3276        Language::new(
 3277            LanguageConfig::default(),
 3278            Some(tree_sitter_rust::LANGUAGE.into()),
 3279        )
 3280        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3281        .unwrap(),
 3282    );
 3283
 3284    let mut cx = EditorTestContext::new(cx).await;
 3285    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3286    cx.set_state(indoc! {"
 3287        const a: ˇA = (
 3288 3289                «const_functionˇ»(ˇ),
 3290                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3291 3292        ˇ);ˇ
 3293    "});
 3294
 3295    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: A = (
 3298            ˇ
 3299            (
 3300                ˇ
 3301                const_function(),
 3302                ˇ
 3303                ˇ
 3304                something_else,
 3305                ˇ
 3306                ˇ
 3307                ˇ
 3308                ˇ
 3309            )
 3310            ˇ
 3311        );
 3312        ˇ
 3313        ˇ
 3314    "});
 3315}
 3316
 3317#[gpui::test]
 3318async fn test_newline_comments(cx: &mut TestAppContext) {
 3319    init_test(cx, |settings| {
 3320        settings.defaults.tab_size = NonZeroU32::new(4)
 3321    });
 3322
 3323    let language = Arc::new(Language::new(
 3324        LanguageConfig {
 3325            line_comments: vec!["// ".into()],
 3326            ..LanguageConfig::default()
 3327        },
 3328        None,
 3329    ));
 3330    {
 3331        let mut cx = EditorTestContext::new(cx).await;
 3332        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3333        cx.set_state(indoc! {"
 3334        // Fooˇ
 3335    "});
 3336
 3337        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3338        cx.assert_editor_state(indoc! {"
 3339        // Foo
 3340        // ˇ
 3341    "});
 3342        // Ensure that we add comment prefix when existing line contains space
 3343        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3344        cx.assert_editor_state(
 3345            indoc! {"
 3346        // Foo
 3347        //s
 3348        // ˇ
 3349    "}
 3350            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3351            .as_str(),
 3352        );
 3353        // Ensure that we add comment prefix when existing line does not contain space
 3354        cx.set_state(indoc! {"
 3355        // Foo
 3356        //ˇ
 3357    "});
 3358        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3359        cx.assert_editor_state(indoc! {"
 3360        // Foo
 3361        //
 3362        // ˇ
 3363    "});
 3364        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3365        cx.set_state(indoc! {"
 3366        ˇ// Foo
 3367    "});
 3368        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3369        cx.assert_editor_state(indoc! {"
 3370
 3371        ˇ// Foo
 3372    "});
 3373    }
 3374    // Ensure that comment continuations can be disabled.
 3375    update_test_language_settings(cx, |settings| {
 3376        settings.defaults.extend_comment_on_newline = Some(false);
 3377    });
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.set_state(indoc! {"
 3380        // Fooˇ
 3381    "});
 3382    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383    cx.assert_editor_state(indoc! {"
 3384        // Foo
 3385        ˇ
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4)
 3393    });
 3394
 3395    let language = Arc::new(Language::new(
 3396        LanguageConfig {
 3397            line_comments: vec!["// ".into(), "/// ".into()],
 3398            ..LanguageConfig::default()
 3399        },
 3400        None,
 3401    ));
 3402    {
 3403        let mut cx = EditorTestContext::new(cx).await;
 3404        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3405        cx.set_state(indoc! {"
 3406        //ˇ
 3407    "});
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        //
 3411        // ˇ
 3412    "});
 3413
 3414        cx.set_state(indoc! {"
 3415        ///ˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        ///
 3420        /// ˇ
 3421    "});
 3422    }
 3423}
 3424
 3425#[gpui::test]
 3426async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3427    init_test(cx, |settings| {
 3428        settings.defaults.tab_size = NonZeroU32::new(4)
 3429    });
 3430
 3431    let language = Arc::new(
 3432        Language::new(
 3433            LanguageConfig {
 3434                documentation_comment: Some(language::BlockCommentConfig {
 3435                    start: "/**".into(),
 3436                    end: "*/".into(),
 3437                    prefix: "* ".into(),
 3438                    tab_size: 1,
 3439                }),
 3440
 3441                ..LanguageConfig::default()
 3442            },
 3443            Some(tree_sitter_rust::LANGUAGE.into()),
 3444        )
 3445        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3446        .unwrap(),
 3447    );
 3448
 3449    {
 3450        let mut cx = EditorTestContext::new(cx).await;
 3451        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3452        cx.set_state(indoc! {"
 3453        /**ˇ
 3454    "});
 3455
 3456        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3457        cx.assert_editor_state(indoc! {"
 3458        /**
 3459         * ˇ
 3460    "});
 3461        // Ensure that if cursor is before the comment start,
 3462        // we do not actually insert a comment prefix.
 3463        cx.set_state(indoc! {"
 3464        ˇ/**
 3465    "});
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468
 3469        ˇ/**
 3470    "});
 3471        // Ensure that if cursor is between it doesn't add comment prefix.
 3472        cx.set_state(indoc! {"
 3473        /*ˇ*
 3474    "});
 3475        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3476        cx.assert_editor_state(indoc! {"
 3477        /*
 3478        ˇ*
 3479    "});
 3480        // Ensure that if suffix exists on same line after cursor it adds new line.
 3481        cx.set_state(indoc! {"
 3482        /**ˇ*/
 3483    "});
 3484        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3485        cx.assert_editor_state(indoc! {"
 3486        /**
 3487         * ˇ
 3488         */
 3489    "});
 3490        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3491        cx.set_state(indoc! {"
 3492        /**ˇ */
 3493    "});
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498         */
 3499    "});
 3500        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3501        cx.set_state(indoc! {"
 3502        /** ˇ*/
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(
 3506            indoc! {"
 3507        /**s
 3508         * ˇ
 3509         */
 3510    "}
 3511            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3512            .as_str(),
 3513        );
 3514        // Ensure that delimiter space is preserved when newline on already
 3515        // spaced delimiter.
 3516        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3517        cx.assert_editor_state(
 3518            indoc! {"
 3519        /**s
 3520         *s
 3521         * ˇ
 3522         */
 3523    "}
 3524            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3525            .as_str(),
 3526        );
 3527        // Ensure that delimiter space is preserved when space is not
 3528        // on existing delimiter.
 3529        cx.set_state(indoc! {"
 3530        /**
 3531 3532         */
 3533    "});
 3534        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3535        cx.assert_editor_state(indoc! {"
 3536        /**
 3537         *
 3538         * ˇ
 3539         */
 3540    "});
 3541        // Ensure that if suffix exists on same line after cursor it
 3542        // doesn't add extra new line if prefix is not on same line.
 3543        cx.set_state(indoc! {"
 3544        /**
 3545        ˇ*/
 3546    "});
 3547        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3548        cx.assert_editor_state(indoc! {"
 3549        /**
 3550
 3551        ˇ*/
 3552    "});
 3553        // Ensure that it detects suffix after existing prefix.
 3554        cx.set_state(indoc! {"
 3555        /**ˇ/
 3556    "});
 3557        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3558        cx.assert_editor_state(indoc! {"
 3559        /**
 3560        ˇ/
 3561    "});
 3562        // Ensure that if suffix exists on same line before
 3563        // cursor it does not add comment prefix.
 3564        cx.set_state(indoc! {"
 3565        /** */ˇ
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /** */
 3570        ˇ
 3571    "});
 3572        // Ensure that if suffix exists on same line before
 3573        // cursor it does not add comment prefix.
 3574        cx.set_state(indoc! {"
 3575        /**
 3576         *
 3577         */ˇ
 3578    "});
 3579        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3580        cx.assert_editor_state(indoc! {"
 3581        /**
 3582         *
 3583         */
 3584         ˇ
 3585    "});
 3586
 3587        // Ensure that inline comment followed by code
 3588        // doesn't add comment prefix on newline
 3589        cx.set_state(indoc! {"
 3590        /** */ textˇ
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        /** */ text
 3595        ˇ
 3596    "});
 3597
 3598        // Ensure that text after comment end tag
 3599        // doesn't add comment prefix on newline
 3600        cx.set_state(indoc! {"
 3601        /**
 3602         *
 3603         */ˇtext
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         */
 3610         ˇtext
 3611    "});
 3612
 3613        // Ensure if not comment block it doesn't
 3614        // add comment prefix on newline
 3615        cx.set_state(indoc! {"
 3616        * textˇ
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        * text
 3621        ˇ
 3622    "});
 3623    }
 3624    // Ensure that comment continuations can be disabled.
 3625    update_test_language_settings(cx, |settings| {
 3626        settings.defaults.extend_comment_on_newline = Some(false);
 3627    });
 3628    let mut cx = EditorTestContext::new(cx).await;
 3629    cx.set_state(indoc! {"
 3630        /**ˇ
 3631    "});
 3632    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634        /**
 3635        ˇ
 3636    "});
 3637}
 3638
 3639#[gpui::test]
 3640async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3641    init_test(cx, |settings| {
 3642        settings.defaults.tab_size = NonZeroU32::new(4)
 3643    });
 3644
 3645    let lua_language = Arc::new(Language::new(
 3646        LanguageConfig {
 3647            line_comments: vec!["--".into()],
 3648            block_comment: Some(language::BlockCommentConfig {
 3649                start: "--[[".into(),
 3650                prefix: "".into(),
 3651                end: "]]".into(),
 3652                tab_size: 0,
 3653            }),
 3654            ..LanguageConfig::default()
 3655        },
 3656        None,
 3657    ));
 3658
 3659    let mut cx = EditorTestContext::new(cx).await;
 3660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3661
 3662    // Line with line comment should extend
 3663    cx.set_state(indoc! {"
 3664        --ˇ
 3665    "});
 3666    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3667    cx.assert_editor_state(indoc! {"
 3668        --
 3669        --ˇ
 3670    "});
 3671
 3672    // Line with block comment that matches line comment should not extend
 3673    cx.set_state(indoc! {"
 3674        --[[ˇ
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        --[[
 3679        ˇ
 3680    "});
 3681}
 3682
 3683#[gpui::test]
 3684fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3685    init_test(cx, |_| {});
 3686
 3687    let editor = cx.add_window(|window, cx| {
 3688        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3689        let mut editor = build_editor(buffer, window, cx);
 3690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3691            s.select_ranges([3..4, 11..12, 19..20])
 3692        });
 3693        editor
 3694    });
 3695
 3696    _ = editor.update(cx, |editor, window, cx| {
 3697        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3698        editor.buffer.update(cx, |buffer, cx| {
 3699            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3700            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3701        });
 3702        assert_eq!(
 3703            editor.selections.ranges(&editor.display_snapshot(cx)),
 3704            &[2..2, 7..7, 12..12],
 3705        );
 3706
 3707        editor.insert("Z", window, cx);
 3708        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3709
 3710        // The selections are moved after the inserted characters
 3711        assert_eq!(
 3712            editor.selections.ranges(&editor.display_snapshot(cx)),
 3713            &[3..3, 9..9, 15..15],
 3714        );
 3715    });
 3716}
 3717
 3718#[gpui::test]
 3719async fn test_tab(cx: &mut TestAppContext) {
 3720    init_test(cx, |settings| {
 3721        settings.defaults.tab_size = NonZeroU32::new(3)
 3722    });
 3723
 3724    let mut cx = EditorTestContext::new(cx).await;
 3725    cx.set_state(indoc! {"
 3726        ˇabˇc
 3727        ˇ🏀ˇ🏀ˇefg
 3728 3729    "});
 3730    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3731    cx.assert_editor_state(indoc! {"
 3732           ˇab ˇc
 3733           ˇ🏀  ˇ🏀  ˇefg
 3734        d  ˇ
 3735    "});
 3736
 3737    cx.set_state(indoc! {"
 3738        a
 3739        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3740    "});
 3741    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3742    cx.assert_editor_state(indoc! {"
 3743        a
 3744           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3745    "});
 3746}
 3747
 3748#[gpui::test]
 3749async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3750    init_test(cx, |_| {});
 3751
 3752    let mut cx = EditorTestContext::new(cx).await;
 3753    let language = Arc::new(
 3754        Language::new(
 3755            LanguageConfig::default(),
 3756            Some(tree_sitter_rust::LANGUAGE.into()),
 3757        )
 3758        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3759        .unwrap(),
 3760    );
 3761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3762
 3763    // test when all cursors are not at suggested indent
 3764    // then simply move to their suggested indent location
 3765    cx.set_state(indoc! {"
 3766        const a: B = (
 3767            c(
 3768        ˇ
 3769        ˇ    )
 3770        );
 3771    "});
 3772    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3773    cx.assert_editor_state(indoc! {"
 3774        const a: B = (
 3775            c(
 3776                ˇ
 3777            ˇ)
 3778        );
 3779    "});
 3780
 3781    // test cursor already at suggested indent not moving when
 3782    // other cursors are yet to reach their suggested indents
 3783    cx.set_state(indoc! {"
 3784        ˇ
 3785        const a: B = (
 3786            c(
 3787                d(
 3788        ˇ
 3789                )
 3790        ˇ
 3791        ˇ    )
 3792        );
 3793    "});
 3794    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3795    cx.assert_editor_state(indoc! {"
 3796        ˇ
 3797        const a: B = (
 3798            c(
 3799                d(
 3800                    ˇ
 3801                )
 3802                ˇ
 3803            ˇ)
 3804        );
 3805    "});
 3806    // test when all cursors are at suggested indent then tab is inserted
 3807    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3808    cx.assert_editor_state(indoc! {"
 3809            ˇ
 3810        const a: B = (
 3811            c(
 3812                d(
 3813                        ˇ
 3814                )
 3815                    ˇ
 3816                ˇ)
 3817        );
 3818    "});
 3819
 3820    // test when current indent is less than suggested indent,
 3821    // we adjust line to match suggested indent and move cursor to it
 3822    //
 3823    // when no other cursor is at word boundary, all of them should move
 3824    cx.set_state(indoc! {"
 3825        const a: B = (
 3826            c(
 3827                d(
 3828        ˇ
 3829        ˇ   )
 3830        ˇ   )
 3831        );
 3832    "});
 3833    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3834    cx.assert_editor_state(indoc! {"
 3835        const a: B = (
 3836            c(
 3837                d(
 3838                    ˇ
 3839                ˇ)
 3840            ˇ)
 3841        );
 3842    "});
 3843
 3844    // test when current indent is less than suggested indent,
 3845    // we adjust line to match suggested indent and move cursor to it
 3846    //
 3847    // when some other cursor is at word boundary, it should not move
 3848    cx.set_state(indoc! {"
 3849        const a: B = (
 3850            c(
 3851                d(
 3852        ˇ
 3853        ˇ   )
 3854           ˇ)
 3855        );
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        const a: B = (
 3860            c(
 3861                d(
 3862                    ˇ
 3863                ˇ)
 3864            ˇ)
 3865        );
 3866    "});
 3867
 3868    // test when current indent is more than suggested indent,
 3869    // we just move cursor to current indent instead of suggested indent
 3870    //
 3871    // when no other cursor is at word boundary, all of them should move
 3872    cx.set_state(indoc! {"
 3873        const a: B = (
 3874            c(
 3875                d(
 3876        ˇ
 3877        ˇ                )
 3878        ˇ   )
 3879        );
 3880    "});
 3881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3882    cx.assert_editor_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886                    ˇ
 3887                        ˇ)
 3888            ˇ)
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                        ˇ
 3897                            ˇ)
 3898                ˇ)
 3899        );
 3900    "});
 3901
 3902    // test when current indent is more than suggested indent,
 3903    // we just move cursor to current indent instead of suggested indent
 3904    //
 3905    // when some other cursor is at word boundary, it doesn't move
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909                d(
 3910        ˇ
 3911        ˇ                )
 3912            ˇ)
 3913        );
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920                    ˇ
 3921                        ˇ)
 3922            ˇ)
 3923        );
 3924    "});
 3925
 3926    // handle auto-indent when there are multiple cursors on the same line
 3927    cx.set_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930        ˇ    ˇ
 3931        ˇ    )
 3932        );
 3933    "});
 3934    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3935    cx.assert_editor_state(indoc! {"
 3936        const a: B = (
 3937            c(
 3938                ˇ
 3939            ˇ)
 3940        );
 3941    "});
 3942}
 3943
 3944#[gpui::test]
 3945async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3946    init_test(cx, |settings| {
 3947        settings.defaults.tab_size = NonZeroU32::new(3)
 3948    });
 3949
 3950    let mut cx = EditorTestContext::new(cx).await;
 3951    cx.set_state(indoc! {"
 3952         ˇ
 3953        \t ˇ
 3954        \t  ˇ
 3955        \t   ˇ
 3956         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3957    "});
 3958
 3959    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3960    cx.assert_editor_state(indoc! {"
 3961           ˇ
 3962        \t   ˇ
 3963        \t   ˇ
 3964        \t      ˇ
 3965         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3966    "});
 3967}
 3968
 3969#[gpui::test]
 3970async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3971    init_test(cx, |settings| {
 3972        settings.defaults.tab_size = NonZeroU32::new(4)
 3973    });
 3974
 3975    let language = Arc::new(
 3976        Language::new(
 3977            LanguageConfig::default(),
 3978            Some(tree_sitter_rust::LANGUAGE.into()),
 3979        )
 3980        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3981        .unwrap(),
 3982    );
 3983
 3984    let mut cx = EditorTestContext::new(cx).await;
 3985    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3986    cx.set_state(indoc! {"
 3987        fn a() {
 3988            if b {
 3989        \t ˇc
 3990            }
 3991        }
 3992    "});
 3993
 3994    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3995    cx.assert_editor_state(indoc! {"
 3996        fn a() {
 3997            if b {
 3998                ˇc
 3999            }
 4000        }
 4001    "});
 4002}
 4003
 4004#[gpui::test]
 4005async fn test_indent_outdent(cx: &mut TestAppContext) {
 4006    init_test(cx, |settings| {
 4007        settings.defaults.tab_size = NonZeroU32::new(4);
 4008    });
 4009
 4010    let mut cx = EditorTestContext::new(cx).await;
 4011
 4012    cx.set_state(indoc! {"
 4013          «oneˇ» «twoˇ»
 4014        three
 4015         four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019            «oneˇ» «twoˇ»
 4020        three
 4021         four
 4022    "});
 4023
 4024    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4025    cx.assert_editor_state(indoc! {"
 4026        «oneˇ» «twoˇ»
 4027        three
 4028         four
 4029    "});
 4030
 4031    // select across line ending
 4032    cx.set_state(indoc! {"
 4033        one two
 4034        t«hree
 4035        ˇ» four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        one two
 4040            t«hree
 4041        ˇ» four
 4042    "});
 4043
 4044    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4045    cx.assert_editor_state(indoc! {"
 4046        one two
 4047        t«hree
 4048        ˇ» four
 4049    "});
 4050
 4051    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4052    cx.set_state(indoc! {"
 4053        one two
 4054        ˇthree
 4055            four
 4056    "});
 4057    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4058    cx.assert_editor_state(indoc! {"
 4059        one two
 4060            ˇthree
 4061            four
 4062    "});
 4063
 4064    cx.set_state(indoc! {"
 4065        one two
 4066        ˇ    three
 4067            four
 4068    "});
 4069    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4070    cx.assert_editor_state(indoc! {"
 4071        one two
 4072        ˇthree
 4073            four
 4074    "});
 4075}
 4076
 4077#[gpui::test]
 4078async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4079    // This is a regression test for issue #33761
 4080    init_test(cx, |_| {});
 4081
 4082    let mut cx = EditorTestContext::new(cx).await;
 4083    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4085
 4086    cx.set_state(
 4087        r#"ˇ#     ingress:
 4088ˇ#         api:
 4089ˇ#             enabled: false
 4090ˇ#             pathType: Prefix
 4091ˇ#           console:
 4092ˇ#               enabled: false
 4093ˇ#               pathType: Prefix
 4094"#,
 4095    );
 4096
 4097    // Press tab to indent all lines
 4098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4099
 4100    cx.assert_editor_state(
 4101        r#"    ˇ#     ingress:
 4102    ˇ#         api:
 4103    ˇ#             enabled: false
 4104    ˇ#             pathType: Prefix
 4105    ˇ#           console:
 4106    ˇ#               enabled: false
 4107    ˇ#               pathType: Prefix
 4108"#,
 4109    );
 4110}
 4111
 4112#[gpui::test]
 4113async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4114    // This is a test to make sure our fix for issue #33761 didn't break anything
 4115    init_test(cx, |_| {});
 4116
 4117    let mut cx = EditorTestContext::new(cx).await;
 4118    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4120
 4121    cx.set_state(
 4122        r#"ˇingress:
 4123ˇ  api:
 4124ˇ    enabled: false
 4125ˇ    pathType: Prefix
 4126"#,
 4127    );
 4128
 4129    // Press tab to indent all lines
 4130    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4131
 4132    cx.assert_editor_state(
 4133        r#"ˇingress:
 4134    ˇapi:
 4135        ˇenabled: false
 4136        ˇpathType: Prefix
 4137"#,
 4138    );
 4139}
 4140
 4141#[gpui::test]
 4142async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4143    init_test(cx, |settings| {
 4144        settings.defaults.hard_tabs = Some(true);
 4145    });
 4146
 4147    let mut cx = EditorTestContext::new(cx).await;
 4148
 4149    // select two ranges on one line
 4150    cx.set_state(indoc! {"
 4151        «oneˇ» «twoˇ»
 4152        three
 4153        four
 4154    "});
 4155    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4156    cx.assert_editor_state(indoc! {"
 4157        \t«oneˇ» «twoˇ»
 4158        three
 4159        four
 4160    "});
 4161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4162    cx.assert_editor_state(indoc! {"
 4163        \t\t«oneˇ» «twoˇ»
 4164        three
 4165        four
 4166    "});
 4167    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4168    cx.assert_editor_state(indoc! {"
 4169        \t«oneˇ» «twoˇ»
 4170        three
 4171        four
 4172    "});
 4173    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4174    cx.assert_editor_state(indoc! {"
 4175        «oneˇ» «twoˇ»
 4176        three
 4177        four
 4178    "});
 4179
 4180    // select across a line ending
 4181    cx.set_state(indoc! {"
 4182        one two
 4183        t«hree
 4184        ˇ»four
 4185    "});
 4186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4187    cx.assert_editor_state(indoc! {"
 4188        one two
 4189        \tt«hree
 4190        ˇ»four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        one two
 4195        \t\tt«hree
 4196        ˇ»four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201        \tt«hree
 4202        ˇ»four
 4203    "});
 4204    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4205    cx.assert_editor_state(indoc! {"
 4206        one two
 4207        t«hree
 4208        ˇ»four
 4209    "});
 4210
 4211    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4212    cx.set_state(indoc! {"
 4213        one two
 4214        ˇthree
 4215        four
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        one two
 4220        ˇthree
 4221        four
 4222    "});
 4223    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4224    cx.assert_editor_state(indoc! {"
 4225        one two
 4226        \tˇthree
 4227        four
 4228    "});
 4229    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4230    cx.assert_editor_state(indoc! {"
 4231        one two
 4232        ˇthree
 4233        four
 4234    "});
 4235}
 4236
 4237#[gpui::test]
 4238fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4239    init_test(cx, |settings| {
 4240        settings.languages.0.extend([
 4241            (
 4242                "TOML".into(),
 4243                LanguageSettingsContent {
 4244                    tab_size: NonZeroU32::new(2),
 4245                    ..Default::default()
 4246                },
 4247            ),
 4248            (
 4249                "Rust".into(),
 4250                LanguageSettingsContent {
 4251                    tab_size: NonZeroU32::new(4),
 4252                    ..Default::default()
 4253                },
 4254            ),
 4255        ]);
 4256    });
 4257
 4258    let toml_language = Arc::new(Language::new(
 4259        LanguageConfig {
 4260            name: "TOML".into(),
 4261            ..Default::default()
 4262        },
 4263        None,
 4264    ));
 4265    let rust_language = Arc::new(Language::new(
 4266        LanguageConfig {
 4267            name: "Rust".into(),
 4268            ..Default::default()
 4269        },
 4270        None,
 4271    ));
 4272
 4273    let toml_buffer =
 4274        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4275    let rust_buffer =
 4276        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4277    let multibuffer = cx.new(|cx| {
 4278        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4279        multibuffer.push_excerpts(
 4280            toml_buffer.clone(),
 4281            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4282            cx,
 4283        );
 4284        multibuffer.push_excerpts(
 4285            rust_buffer.clone(),
 4286            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4287            cx,
 4288        );
 4289        multibuffer
 4290    });
 4291
 4292    cx.add_window(|window, cx| {
 4293        let mut editor = build_editor(multibuffer, window, cx);
 4294
 4295        assert_eq!(
 4296            editor.text(cx),
 4297            indoc! {"
 4298                a = 1
 4299                b = 2
 4300
 4301                const c: usize = 3;
 4302            "}
 4303        );
 4304
 4305        select_ranges(
 4306            &mut editor,
 4307            indoc! {"
 4308                «aˇ» = 1
 4309                b = 2
 4310
 4311                «const c:ˇ» usize = 3;
 4312            "},
 4313            window,
 4314            cx,
 4315        );
 4316
 4317        editor.tab(&Tab, window, cx);
 4318        assert_text_with_selections(
 4319            &mut editor,
 4320            indoc! {"
 4321                  «aˇ» = 1
 4322                b = 2
 4323
 4324                    «const c:ˇ» usize = 3;
 4325            "},
 4326            cx,
 4327        );
 4328        editor.backtab(&Backtab, window, cx);
 4329        assert_text_with_selections(
 4330            &mut editor,
 4331            indoc! {"
 4332                «aˇ» = 1
 4333                b = 2
 4334
 4335                «const c:ˇ» usize = 3;
 4336            "},
 4337            cx,
 4338        );
 4339
 4340        editor
 4341    });
 4342}
 4343
 4344#[gpui::test]
 4345async fn test_backspace(cx: &mut TestAppContext) {
 4346    init_test(cx, |_| {});
 4347
 4348    let mut cx = EditorTestContext::new(cx).await;
 4349
 4350    // Basic backspace
 4351    cx.set_state(indoc! {"
 4352        onˇe two three
 4353        fou«rˇ» five six
 4354        seven «ˇeight nine
 4355        »ten
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        oˇe two three
 4360        fouˇ five six
 4361        seven ˇten
 4362    "});
 4363
 4364    // Test backspace inside and around indents
 4365    cx.set_state(indoc! {"
 4366        zero
 4367            ˇone
 4368                ˇtwo
 4369            ˇ ˇ ˇ  three
 4370        ˇ  ˇ  four
 4371    "});
 4372    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4373    cx.assert_editor_state(indoc! {"
 4374        zero
 4375        ˇone
 4376            ˇtwo
 4377        ˇ  threeˇ  four
 4378    "});
 4379}
 4380
 4381#[gpui::test]
 4382async fn test_delete(cx: &mut TestAppContext) {
 4383    init_test(cx, |_| {});
 4384
 4385    let mut cx = EditorTestContext::new(cx).await;
 4386    cx.set_state(indoc! {"
 4387        onˇe two three
 4388        fou«rˇ» five six
 4389        seven «ˇeight nine
 4390        »ten
 4391    "});
 4392    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4393    cx.assert_editor_state(indoc! {"
 4394        onˇ two three
 4395        fouˇ five six
 4396        seven ˇten
 4397    "});
 4398}
 4399
 4400#[gpui::test]
 4401fn test_delete_line(cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let editor = cx.add_window(|window, cx| {
 4405        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4406        build_editor(buffer, window, cx)
 4407    });
 4408    _ = editor.update(cx, |editor, window, cx| {
 4409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4410            s.select_display_ranges([
 4411                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4412                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4413                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4414            ])
 4415        });
 4416        editor.delete_line(&DeleteLine, window, cx);
 4417        assert_eq!(editor.display_text(cx), "ghi");
 4418        assert_eq!(
 4419            editor.selections.display_ranges(cx),
 4420            vec![
 4421                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4422                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4423            ]
 4424        );
 4425    });
 4426
 4427    let editor = cx.add_window(|window, cx| {
 4428        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4429        build_editor(buffer, window, cx)
 4430    });
 4431    _ = editor.update(cx, |editor, window, cx| {
 4432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4433            s.select_display_ranges([
 4434                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4435            ])
 4436        });
 4437        editor.delete_line(&DeleteLine, window, cx);
 4438        assert_eq!(editor.display_text(cx), "ghi\n");
 4439        assert_eq!(
 4440            editor.selections.display_ranges(cx),
 4441            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4442        );
 4443    });
 4444
 4445    let editor = cx.add_window(|window, cx| {
 4446        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4447        build_editor(buffer, window, cx)
 4448    });
 4449    _ = editor.update(cx, |editor, window, cx| {
 4450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4451            s.select_display_ranges([
 4452                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4453            ])
 4454        });
 4455        editor.delete_line(&DeleteLine, window, cx);
 4456        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4457        assert_eq!(
 4458            editor.selections.display_ranges(cx),
 4459            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4460        );
 4461    });
 4462}
 4463
 4464#[gpui::test]
 4465fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4466    init_test(cx, |_| {});
 4467
 4468    cx.add_window(|window, cx| {
 4469        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4470        let mut editor = build_editor(buffer.clone(), window, cx);
 4471        let buffer = buffer.read(cx).as_singleton().unwrap();
 4472
 4473        assert_eq!(
 4474            editor
 4475                .selections
 4476                .ranges::<Point>(&editor.display_snapshot(cx)),
 4477            &[Point::new(0, 0)..Point::new(0, 0)]
 4478        );
 4479
 4480        // When on single line, replace newline at end by space
 4481        editor.join_lines(&JoinLines, window, cx);
 4482        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4483        assert_eq!(
 4484            editor
 4485                .selections
 4486                .ranges::<Point>(&editor.display_snapshot(cx)),
 4487            &[Point::new(0, 3)..Point::new(0, 3)]
 4488        );
 4489
 4490        // When multiple lines are selected, remove newlines that are spanned by the selection
 4491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4492            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4493        });
 4494        editor.join_lines(&JoinLines, window, cx);
 4495        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4496        assert_eq!(
 4497            editor
 4498                .selections
 4499                .ranges::<Point>(&editor.display_snapshot(cx)),
 4500            &[Point::new(0, 11)..Point::new(0, 11)]
 4501        );
 4502
 4503        // Undo should be transactional
 4504        editor.undo(&Undo, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4506        assert_eq!(
 4507            editor
 4508                .selections
 4509                .ranges::<Point>(&editor.display_snapshot(cx)),
 4510            &[Point::new(0, 5)..Point::new(2, 2)]
 4511        );
 4512
 4513        // When joining an empty line don't insert a space
 4514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4515            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4516        });
 4517        editor.join_lines(&JoinLines, window, cx);
 4518        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4519        assert_eq!(
 4520            editor
 4521                .selections
 4522                .ranges::<Point>(&editor.display_snapshot(cx)),
 4523            [Point::new(2, 3)..Point::new(2, 3)]
 4524        );
 4525
 4526        // We can remove trailing newlines
 4527        editor.join_lines(&JoinLines, window, cx);
 4528        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            [Point::new(2, 3)..Point::new(2, 3)]
 4534        );
 4535
 4536        // We don't blow up on the last line
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            [Point::new(2, 3)..Point::new(2, 3)]
 4544        );
 4545
 4546        // reset to test indentation
 4547        editor.buffer.update(cx, |buffer, cx| {
 4548            buffer.edit(
 4549                [
 4550                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4551                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4552                ],
 4553                None,
 4554                cx,
 4555            )
 4556        });
 4557
 4558        // We remove any leading spaces
 4559        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4560        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4561            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4562        });
 4563        editor.join_lines(&JoinLines, window, cx);
 4564        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4565
 4566        // We don't insert a space for a line containing only spaces
 4567        editor.join_lines(&JoinLines, window, cx);
 4568        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4569
 4570        // We ignore any leading tabs
 4571        editor.join_lines(&JoinLines, window, cx);
 4572        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4573
 4574        editor
 4575    });
 4576}
 4577
 4578#[gpui::test]
 4579fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4580    init_test(cx, |_| {});
 4581
 4582    cx.add_window(|window, cx| {
 4583        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4584        let mut editor = build_editor(buffer.clone(), window, cx);
 4585        let buffer = buffer.read(cx).as_singleton().unwrap();
 4586
 4587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4588            s.select_ranges([
 4589                Point::new(0, 2)..Point::new(1, 1),
 4590                Point::new(1, 2)..Point::new(1, 2),
 4591                Point::new(3, 1)..Point::new(3, 2),
 4592            ])
 4593        });
 4594
 4595        editor.join_lines(&JoinLines, window, cx);
 4596        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4597
 4598        assert_eq!(
 4599            editor
 4600                .selections
 4601                .ranges::<Point>(&editor.display_snapshot(cx)),
 4602            [
 4603                Point::new(0, 7)..Point::new(0, 7),
 4604                Point::new(1, 3)..Point::new(1, 3)
 4605            ]
 4606        );
 4607        editor
 4608    });
 4609}
 4610
 4611#[gpui::test]
 4612async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4613    init_test(cx, |_| {});
 4614
 4615    let mut cx = EditorTestContext::new(cx).await;
 4616
 4617    let diff_base = r#"
 4618        Line 0
 4619        Line 1
 4620        Line 2
 4621        Line 3
 4622        "#
 4623    .unindent();
 4624
 4625    cx.set_state(
 4626        &r#"
 4627        ˇLine 0
 4628        Line 1
 4629        Line 2
 4630        Line 3
 4631        "#
 4632        .unindent(),
 4633    );
 4634
 4635    cx.set_head_text(&diff_base);
 4636    executor.run_until_parked();
 4637
 4638    // Join lines
 4639    cx.update_editor(|editor, window, cx| {
 4640        editor.join_lines(&JoinLines, window, cx);
 4641    });
 4642    executor.run_until_parked();
 4643
 4644    cx.assert_editor_state(
 4645        &r#"
 4646        Line 0ˇ Line 1
 4647        Line 2
 4648        Line 3
 4649        "#
 4650        .unindent(),
 4651    );
 4652    // Join again
 4653    cx.update_editor(|editor, window, cx| {
 4654        editor.join_lines(&JoinLines, window, cx);
 4655    });
 4656    executor.run_until_parked();
 4657
 4658    cx.assert_editor_state(
 4659        &r#"
 4660        Line 0 Line 1ˇ Line 2
 4661        Line 3
 4662        "#
 4663        .unindent(),
 4664    );
 4665}
 4666
 4667#[gpui::test]
 4668async fn test_custom_newlines_cause_no_false_positive_diffs(
 4669    executor: BackgroundExecutor,
 4670    cx: &mut TestAppContext,
 4671) {
 4672    init_test(cx, |_| {});
 4673    let mut cx = EditorTestContext::new(cx).await;
 4674    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4675    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4676    executor.run_until_parked();
 4677
 4678    cx.update_editor(|editor, window, cx| {
 4679        let snapshot = editor.snapshot(window, cx);
 4680        assert_eq!(
 4681            snapshot
 4682                .buffer_snapshot()
 4683                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4684                .collect::<Vec<_>>(),
 4685            Vec::new(),
 4686            "Should not have any diffs for files with custom newlines"
 4687        );
 4688    });
 4689}
 4690
 4691#[gpui::test]
 4692async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4693    init_test(cx, |_| {});
 4694
 4695    let mut cx = EditorTestContext::new(cx).await;
 4696
 4697    // Test sort_lines_case_insensitive()
 4698    cx.set_state(indoc! {"
 4699        «z
 4700        y
 4701        x
 4702        Z
 4703        Y
 4704        Xˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| {
 4707        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4708    });
 4709    cx.assert_editor_state(indoc! {"
 4710        «x
 4711        X
 4712        y
 4713        Y
 4714        z
 4715        Zˇ»
 4716    "});
 4717
 4718    // Test sort_lines_by_length()
 4719    //
 4720    // Demonstrates:
 4721    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4722    // - sort is stable
 4723    cx.set_state(indoc! {"
 4724        «123
 4725        æ
 4726        12
 4727 4728        1
 4729        æˇ»
 4730    "});
 4731    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4732    cx.assert_editor_state(indoc! {"
 4733        «æ
 4734 4735        1
 4736        æ
 4737        12
 4738        123ˇ»
 4739    "});
 4740
 4741    // Test reverse_lines()
 4742    cx.set_state(indoc! {"
 4743        «5
 4744        4
 4745        3
 4746        2
 4747        1ˇ»
 4748    "});
 4749    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4750    cx.assert_editor_state(indoc! {"
 4751        «1
 4752        2
 4753        3
 4754        4
 4755        5ˇ»
 4756    "});
 4757
 4758    // Skip testing shuffle_line()
 4759
 4760    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4761    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4762
 4763    // Don't manipulate when cursor is on single line, but expand the selection
 4764    cx.set_state(indoc! {"
 4765        ddˇdd
 4766        ccc
 4767        bb
 4768        a
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «ddddˇ»
 4775        ccc
 4776        bb
 4777        a
 4778    "});
 4779
 4780    // Basic manipulate case
 4781    // Start selection moves to column 0
 4782    // End of selection shrinks to fit shorter line
 4783    cx.set_state(indoc! {"
 4784        dd«d
 4785        ccc
 4786        bb
 4787        aaaaaˇ»
 4788    "});
 4789    cx.update_editor(|e, window, cx| {
 4790        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4791    });
 4792    cx.assert_editor_state(indoc! {"
 4793        «aaaaa
 4794        bb
 4795        ccc
 4796        dddˇ»
 4797    "});
 4798
 4799    // Manipulate case with newlines
 4800    cx.set_state(indoc! {"
 4801        dd«d
 4802        ccc
 4803
 4804        bb
 4805        aaaaa
 4806
 4807        ˇ»
 4808    "});
 4809    cx.update_editor(|e, window, cx| {
 4810        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4811    });
 4812    cx.assert_editor_state(indoc! {"
 4813        «
 4814
 4815        aaaaa
 4816        bb
 4817        ccc
 4818        dddˇ»
 4819
 4820    "});
 4821
 4822    // Adding new line
 4823    cx.set_state(indoc! {"
 4824        aa«a
 4825        bbˇ»b
 4826    "});
 4827    cx.update_editor(|e, window, cx| {
 4828        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4829    });
 4830    cx.assert_editor_state(indoc! {"
 4831        «aaa
 4832        bbb
 4833        added_lineˇ»
 4834    "});
 4835
 4836    // Removing line
 4837    cx.set_state(indoc! {"
 4838        aa«a
 4839        bbbˇ»
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.manipulate_immutable_lines(window, cx, |lines| {
 4843            lines.pop();
 4844        })
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «aaaˇ»
 4848    "});
 4849
 4850    // Removing all lines
 4851    cx.set_state(indoc! {"
 4852        aa«a
 4853        bbbˇ»
 4854    "});
 4855    cx.update_editor(|e, window, cx| {
 4856        e.manipulate_immutable_lines(window, cx, |lines| {
 4857            lines.drain(..);
 4858        })
 4859    });
 4860    cx.assert_editor_state(indoc! {"
 4861        ˇ
 4862    "});
 4863}
 4864
 4865#[gpui::test]
 4866async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4867    init_test(cx, |_| {});
 4868
 4869    let mut cx = EditorTestContext::new(cx).await;
 4870
 4871    // Consider continuous selection as single selection
 4872    cx.set_state(indoc! {"
 4873        Aaa«aa
 4874        cˇ»c«c
 4875        bb
 4876        aaaˇ»aa
 4877    "});
 4878    cx.update_editor(|e, window, cx| {
 4879        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4880    });
 4881    cx.assert_editor_state(indoc! {"
 4882        «Aaaaa
 4883        ccc
 4884        bb
 4885        aaaaaˇ»
 4886    "});
 4887
 4888    cx.set_state(indoc! {"
 4889        Aaa«aa
 4890        cˇ»c«c
 4891        bb
 4892        aaaˇ»aa
 4893    "});
 4894    cx.update_editor(|e, window, cx| {
 4895        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4896    });
 4897    cx.assert_editor_state(indoc! {"
 4898        «Aaaaa
 4899        ccc
 4900        bbˇ»
 4901    "});
 4902
 4903    // Consider non continuous selection as distinct dedup operations
 4904    cx.set_state(indoc! {"
 4905        «aaaaa
 4906        bb
 4907        aaaaa
 4908        aaaaaˇ»
 4909
 4910        aaa«aaˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4914    });
 4915    cx.assert_editor_state(indoc! {"
 4916        «aaaaa
 4917        bbˇ»
 4918
 4919        «aaaaaˇ»
 4920    "});
 4921}
 4922
 4923#[gpui::test]
 4924async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4925    init_test(cx, |_| {});
 4926
 4927    let mut cx = EditorTestContext::new(cx).await;
 4928
 4929    cx.set_state(indoc! {"
 4930        «Aaa
 4931        aAa
 4932        Aaaˇ»
 4933    "});
 4934    cx.update_editor(|e, window, cx| {
 4935        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4936    });
 4937    cx.assert_editor_state(indoc! {"
 4938        «Aaa
 4939        aAaˇ»
 4940    "});
 4941
 4942    cx.set_state(indoc! {"
 4943        «Aaa
 4944        aAa
 4945        aaAˇ»
 4946    "});
 4947    cx.update_editor(|e, window, cx| {
 4948        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4949    });
 4950    cx.assert_editor_state(indoc! {"
 4951        «Aaaˇ»
 4952    "});
 4953}
 4954
 4955#[gpui::test]
 4956async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4957    init_test(cx, |_| {});
 4958
 4959    let mut cx = EditorTestContext::new(cx).await;
 4960
 4961    let js_language = Arc::new(Language::new(
 4962        LanguageConfig {
 4963            name: "JavaScript".into(),
 4964            wrap_characters: Some(language::WrapCharactersConfig {
 4965                start_prefix: "<".into(),
 4966                start_suffix: ">".into(),
 4967                end_prefix: "</".into(),
 4968                end_suffix: ">".into(),
 4969            }),
 4970            ..LanguageConfig::default()
 4971        },
 4972        None,
 4973    ));
 4974
 4975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4976
 4977    cx.set_state(indoc! {"
 4978        «testˇ»
 4979    "});
 4980    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4981    cx.assert_editor_state(indoc! {"
 4982        <«ˇ»>test</«ˇ»>
 4983    "});
 4984
 4985    cx.set_state(indoc! {"
 4986        «test
 4987         testˇ»
 4988    "});
 4989    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4990    cx.assert_editor_state(indoc! {"
 4991        <«ˇ»>test
 4992         test</«ˇ»>
 4993    "});
 4994
 4995    cx.set_state(indoc! {"
 4996        teˇst
 4997    "});
 4998    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4999    cx.assert_editor_state(indoc! {"
 5000        te<«ˇ»></«ˇ»>st
 5001    "});
 5002}
 5003
 5004#[gpui::test]
 5005async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5006    init_test(cx, |_| {});
 5007
 5008    let mut cx = EditorTestContext::new(cx).await;
 5009
 5010    let js_language = Arc::new(Language::new(
 5011        LanguageConfig {
 5012            name: "JavaScript".into(),
 5013            wrap_characters: Some(language::WrapCharactersConfig {
 5014                start_prefix: "<".into(),
 5015                start_suffix: ">".into(),
 5016                end_prefix: "</".into(),
 5017                end_suffix: ">".into(),
 5018            }),
 5019            ..LanguageConfig::default()
 5020        },
 5021        None,
 5022    ));
 5023
 5024    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5025
 5026    cx.set_state(indoc! {"
 5027        «testˇ»
 5028        «testˇ» «testˇ»
 5029        «testˇ»
 5030    "});
 5031    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5032    cx.assert_editor_state(indoc! {"
 5033        <«ˇ»>test</«ˇ»>
 5034        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5035        <«ˇ»>test</«ˇ»>
 5036    "});
 5037
 5038    cx.set_state(indoc! {"
 5039        «test
 5040         testˇ»
 5041        «test
 5042         testˇ»
 5043    "});
 5044    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5045    cx.assert_editor_state(indoc! {"
 5046        <«ˇ»>test
 5047         test</«ˇ»>
 5048        <«ˇ»>test
 5049         test</«ˇ»>
 5050    "});
 5051}
 5052
 5053#[gpui::test]
 5054async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5055    init_test(cx, |_| {});
 5056
 5057    let mut cx = EditorTestContext::new(cx).await;
 5058
 5059    let plaintext_language = Arc::new(Language::new(
 5060        LanguageConfig {
 5061            name: "Plain Text".into(),
 5062            ..LanguageConfig::default()
 5063        },
 5064        None,
 5065    ));
 5066
 5067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5068
 5069    cx.set_state(indoc! {"
 5070        «testˇ»
 5071    "});
 5072    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5073    cx.assert_editor_state(indoc! {"
 5074      «testˇ»
 5075    "});
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5080    init_test(cx, |_| {});
 5081
 5082    let mut cx = EditorTestContext::new(cx).await;
 5083
 5084    // Manipulate with multiple selections on a single line
 5085    cx.set_state(indoc! {"
 5086        dd«dd
 5087        cˇ»c«c
 5088        bb
 5089        aaaˇ»aa
 5090    "});
 5091    cx.update_editor(|e, window, cx| {
 5092        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5093    });
 5094    cx.assert_editor_state(indoc! {"
 5095        «aaaaa
 5096        bb
 5097        ccc
 5098        ddddˇ»
 5099    "});
 5100
 5101    // Manipulate with multiple disjoin selections
 5102    cx.set_state(indoc! {"
 5103 5104        4
 5105        3
 5106        2
 5107        1ˇ»
 5108
 5109        dd«dd
 5110        ccc
 5111        bb
 5112        aaaˇ»aa
 5113    "});
 5114    cx.update_editor(|e, window, cx| {
 5115        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5116    });
 5117    cx.assert_editor_state(indoc! {"
 5118        «1
 5119        2
 5120        3
 5121        4
 5122        5ˇ»
 5123
 5124        «aaaaa
 5125        bb
 5126        ccc
 5127        ddddˇ»
 5128    "});
 5129
 5130    // Adding lines on each selection
 5131    cx.set_state(indoc! {"
 5132 5133        1ˇ»
 5134
 5135        bb«bb
 5136        aaaˇ»aa
 5137    "});
 5138    cx.update_editor(|e, window, cx| {
 5139        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5140    });
 5141    cx.assert_editor_state(indoc! {"
 5142        «2
 5143        1
 5144        added lineˇ»
 5145
 5146        «bbbb
 5147        aaaaa
 5148        added lineˇ»
 5149    "});
 5150
 5151    // Removing lines on each selection
 5152    cx.set_state(indoc! {"
 5153 5154        1ˇ»
 5155
 5156        bb«bb
 5157        aaaˇ»aa
 5158    "});
 5159    cx.update_editor(|e, window, cx| {
 5160        e.manipulate_immutable_lines(window, cx, |lines| {
 5161            lines.pop();
 5162        })
 5163    });
 5164    cx.assert_editor_state(indoc! {"
 5165        «2ˇ»
 5166
 5167        «bbbbˇ»
 5168    "});
 5169}
 5170
 5171#[gpui::test]
 5172async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5173    init_test(cx, |settings| {
 5174        settings.defaults.tab_size = NonZeroU32::new(3)
 5175    });
 5176
 5177    let mut cx = EditorTestContext::new(cx).await;
 5178
 5179    // MULTI SELECTION
 5180    // Ln.1 "«" tests empty lines
 5181    // Ln.9 tests just leading whitespace
 5182    cx.set_state(indoc! {"
 5183        «
 5184        abc                 // No indentationˇ»
 5185        «\tabc              // 1 tabˇ»
 5186        \t\tabc «      ˇ»   // 2 tabs
 5187        \t ab«c             // Tab followed by space
 5188         \tabc              // Space followed by tab (3 spaces should be the result)
 5189        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5190           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5191        \t
 5192        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5193    "});
 5194    cx.update_editor(|e, window, cx| {
 5195        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5196    });
 5197    cx.assert_editor_state(
 5198        indoc! {"
 5199            «
 5200            abc                 // No indentation
 5201               abc              // 1 tab
 5202                  abc          // 2 tabs
 5203                abc             // Tab followed by space
 5204               abc              // Space followed by tab (3 spaces should be the result)
 5205                           abc   // Mixed indentation (tab conversion depends on the column)
 5206               abc         // Already space indented
 5207               ·
 5208               abc\tdef          // Only the leading tab is manipulatedˇ»
 5209        "}
 5210        .replace("·", "")
 5211        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5212    );
 5213
 5214    // Test on just a few lines, the others should remain unchanged
 5215    // Only lines (3, 5, 10, 11) should change
 5216    cx.set_state(
 5217        indoc! {"
 5218            ·
 5219            abc                 // No indentation
 5220            \tabcˇ               // 1 tab
 5221            \t\tabc             // 2 tabs
 5222            \t abcˇ              // Tab followed by space
 5223             \tabc              // Space followed by tab (3 spaces should be the result)
 5224            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5225               abc              // Already space indented
 5226            «\t
 5227            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5228        "}
 5229        .replace("·", "")
 5230        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5231    );
 5232    cx.update_editor(|e, window, cx| {
 5233        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5234    });
 5235    cx.assert_editor_state(
 5236        indoc! {"
 5237            ·
 5238            abc                 // No indentation
 5239            «   abc               // 1 tabˇ»
 5240            \t\tabc             // 2 tabs
 5241            «    abc              // Tab followed by spaceˇ»
 5242             \tabc              // Space followed by tab (3 spaces should be the result)
 5243            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5244               abc              // Already space indented
 5245            «   ·
 5246               abc\tdef          // Only the leading tab is manipulatedˇ»
 5247        "}
 5248        .replace("·", "")
 5249        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5250    );
 5251
 5252    // SINGLE SELECTION
 5253    // Ln.1 "«" tests empty lines
 5254    // Ln.9 tests just leading whitespace
 5255    cx.set_state(indoc! {"
 5256        «
 5257        abc                 // No indentation
 5258        \tabc               // 1 tab
 5259        \t\tabc             // 2 tabs
 5260        \t abc              // Tab followed by space
 5261         \tabc              // Space followed by tab (3 spaces should be the result)
 5262        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5263           abc              // Already space indented
 5264        \t
 5265        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| {
 5268        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5269    });
 5270    cx.assert_editor_state(
 5271        indoc! {"
 5272            «
 5273            abc                 // No indentation
 5274               abc               // 1 tab
 5275                  abc             // 2 tabs
 5276                abc              // Tab followed by space
 5277               abc              // Space followed by tab (3 spaces should be the result)
 5278                           abc   // Mixed indentation (tab conversion depends on the column)
 5279               abc              // Already space indented
 5280               ·
 5281               abc\tdef          // Only the leading tab is manipulatedˇ»
 5282        "}
 5283        .replace("·", "")
 5284        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5285    );
 5286}
 5287
 5288#[gpui::test]
 5289async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5290    init_test(cx, |settings| {
 5291        settings.defaults.tab_size = NonZeroU32::new(3)
 5292    });
 5293
 5294    let mut cx = EditorTestContext::new(cx).await;
 5295
 5296    // MULTI SELECTION
 5297    // Ln.1 "«" tests empty lines
 5298    // Ln.11 tests just leading whitespace
 5299    cx.set_state(indoc! {"
 5300        «
 5301        abˇ»ˇc                 // No indentation
 5302         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5303          abc  «             // 2 spaces (< 3 so dont convert)
 5304           abc              // 3 spaces (convert)
 5305             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5306        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5307        «\t abc              // Tab followed by space
 5308         \tabc              // Space followed by tab (should be consumed due to tab)
 5309        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5310           \tˇ»  «\t
 5311           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5312    "});
 5313    cx.update_editor(|e, window, cx| {
 5314        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5315    });
 5316    cx.assert_editor_state(indoc! {"
 5317        «
 5318        abc                 // No indentation
 5319         abc                // 1 space (< 3 so dont convert)
 5320          abc               // 2 spaces (< 3 so dont convert)
 5321        \tabc              // 3 spaces (convert)
 5322        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5323        \t\t\tabc           // Already tab indented
 5324        \t abc              // Tab followed by space
 5325        \tabc              // Space followed by tab (should be consumed due to tab)
 5326        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5327        \t\t\t
 5328        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5329    "});
 5330
 5331    // Test on just a few lines, the other should remain unchanged
 5332    // Only lines (4, 8, 11, 12) should change
 5333    cx.set_state(
 5334        indoc! {"
 5335            ·
 5336            abc                 // No indentation
 5337             abc                // 1 space (< 3 so dont convert)
 5338              abc               // 2 spaces (< 3 so dont convert)
 5339            «   abc              // 3 spaces (convert)ˇ»
 5340                 abc            // 5 spaces (1 tab + 2 spaces)
 5341            \t\t\tabc           // Already tab indented
 5342            \t abc              // Tab followed by space
 5343             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5344               \t\t  \tabc      // Mixed indentation
 5345            \t \t  \t   \tabc   // Mixed indentation
 5346               \t  \tˇ
 5347            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5348        "}
 5349        .replace("·", "")
 5350        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5351    );
 5352    cx.update_editor(|e, window, cx| {
 5353        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5354    });
 5355    cx.assert_editor_state(
 5356        indoc! {"
 5357            ·
 5358            abc                 // No indentation
 5359             abc                // 1 space (< 3 so dont convert)
 5360              abc               // 2 spaces (< 3 so dont convert)
 5361            «\tabc              // 3 spaces (convert)ˇ»
 5362                 abc            // 5 spaces (1 tab + 2 spaces)
 5363            \t\t\tabc           // Already tab indented
 5364            \t abc              // Tab followed by space
 5365            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5366               \t\t  \tabc      // Mixed indentation
 5367            \t \t  \t   \tabc   // Mixed indentation
 5368            «\t\t\t
 5369            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5370        "}
 5371        .replace("·", "")
 5372        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5373    );
 5374
 5375    // SINGLE SELECTION
 5376    // Ln.1 "«" tests empty lines
 5377    // Ln.11 tests just leading whitespace
 5378    cx.set_state(indoc! {"
 5379        «
 5380        abc                 // No indentation
 5381         abc                // 1 space (< 3 so dont convert)
 5382          abc               // 2 spaces (< 3 so dont convert)
 5383           abc              // 3 spaces (convert)
 5384             abc            // 5 spaces (1 tab + 2 spaces)
 5385        \t\t\tabc           // Already tab indented
 5386        \t abc              // Tab followed by space
 5387         \tabc              // Space followed by tab (should be consumed due to tab)
 5388        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5389           \t  \t
 5390           abc   \t         // Only the leading spaces should be convertedˇ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| {
 5393        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5394    });
 5395    cx.assert_editor_state(indoc! {"
 5396        «
 5397        abc                 // No indentation
 5398         abc                // 1 space (< 3 so dont convert)
 5399          abc               // 2 spaces (< 3 so dont convert)
 5400        \tabc              // 3 spaces (convert)
 5401        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5402        \t\t\tabc           // Already tab indented
 5403        \t abc              // Tab followed by space
 5404        \tabc              // Space followed by tab (should be consumed due to tab)
 5405        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5406        \t\t\t
 5407        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5408    "});
 5409}
 5410
 5411#[gpui::test]
 5412async fn test_toggle_case(cx: &mut TestAppContext) {
 5413    init_test(cx, |_| {});
 5414
 5415    let mut cx = EditorTestContext::new(cx).await;
 5416
 5417    // If all lower case -> upper case
 5418    cx.set_state(indoc! {"
 5419        «hello worldˇ»
 5420    "});
 5421    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5422    cx.assert_editor_state(indoc! {"
 5423        «HELLO WORLDˇ»
 5424    "});
 5425
 5426    // If all upper case -> lower case
 5427    cx.set_state(indoc! {"
 5428        «HELLO WORLDˇ»
 5429    "});
 5430    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5431    cx.assert_editor_state(indoc! {"
 5432        «hello worldˇ»
 5433    "});
 5434
 5435    // If any upper case characters are identified -> lower case
 5436    // This matches JetBrains IDEs
 5437    cx.set_state(indoc! {"
 5438        «hEllo worldˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5441    cx.assert_editor_state(indoc! {"
 5442        «hello worldˇ»
 5443    "});
 5444}
 5445
 5446#[gpui::test]
 5447async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5448    init_test(cx, |_| {});
 5449
 5450    let mut cx = EditorTestContext::new(cx).await;
 5451
 5452    cx.set_state(indoc! {"
 5453        «implement-windows-supportˇ»
 5454    "});
 5455    cx.update_editor(|e, window, cx| {
 5456        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5457    });
 5458    cx.assert_editor_state(indoc! {"
 5459        «Implement windows supportˇ»
 5460    "});
 5461}
 5462
 5463#[gpui::test]
 5464async fn test_manipulate_text(cx: &mut TestAppContext) {
 5465    init_test(cx, |_| {});
 5466
 5467    let mut cx = EditorTestContext::new(cx).await;
 5468
 5469    // Test convert_to_upper_case()
 5470    cx.set_state(indoc! {"
 5471        «hello worldˇ»
 5472    "});
 5473    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5474    cx.assert_editor_state(indoc! {"
 5475        «HELLO WORLDˇ»
 5476    "});
 5477
 5478    // Test convert_to_lower_case()
 5479    cx.set_state(indoc! {"
 5480        «HELLO WORLDˇ»
 5481    "});
 5482    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5483    cx.assert_editor_state(indoc! {"
 5484        «hello worldˇ»
 5485    "});
 5486
 5487    // Test multiple line, single selection case
 5488    cx.set_state(indoc! {"
 5489        «The quick brown
 5490        fox jumps over
 5491        the lazy dogˇ»
 5492    "});
 5493    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5494    cx.assert_editor_state(indoc! {"
 5495        «The Quick Brown
 5496        Fox Jumps Over
 5497        The Lazy Dogˇ»
 5498    "});
 5499
 5500    // Test multiple line, single selection case
 5501    cx.set_state(indoc! {"
 5502        «The quick brown
 5503        fox jumps over
 5504        the lazy dogˇ»
 5505    "});
 5506    cx.update_editor(|e, window, cx| {
 5507        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5508    });
 5509    cx.assert_editor_state(indoc! {"
 5510        «TheQuickBrown
 5511        FoxJumpsOver
 5512        TheLazyDogˇ»
 5513    "});
 5514
 5515    // From here on out, test more complex cases of manipulate_text()
 5516
 5517    // Test no selection case - should affect words cursors are in
 5518    // Cursor at beginning, middle, and end of word
 5519    cx.set_state(indoc! {"
 5520        ˇhello big beauˇtiful worldˇ
 5521    "});
 5522    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5523    cx.assert_editor_state(indoc! {"
 5524        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5525    "});
 5526
 5527    // Test multiple selections on a single line and across multiple lines
 5528    cx.set_state(indoc! {"
 5529        «Theˇ» quick «brown
 5530        foxˇ» jumps «overˇ»
 5531        the «lazyˇ» dog
 5532    "});
 5533    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5534    cx.assert_editor_state(indoc! {"
 5535        «THEˇ» quick «BROWN
 5536        FOXˇ» jumps «OVERˇ»
 5537        the «LAZYˇ» dog
 5538    "});
 5539
 5540    // Test case where text length grows
 5541    cx.set_state(indoc! {"
 5542        «tschüߡ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «TSCHÜSSˇ»
 5547    "});
 5548
 5549    // Test to make sure we don't crash when text shrinks
 5550    cx.set_state(indoc! {"
 5551        aaa_bbbˇ
 5552    "});
 5553    cx.update_editor(|e, window, cx| {
 5554        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5555    });
 5556    cx.assert_editor_state(indoc! {"
 5557        «aaaBbbˇ»
 5558    "});
 5559
 5560    // Test to make sure we all aware of the fact that each word can grow and shrink
 5561    // Final selections should be aware of this fact
 5562    cx.set_state(indoc! {"
 5563        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5564    "});
 5565    cx.update_editor(|e, window, cx| {
 5566        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5567    });
 5568    cx.assert_editor_state(indoc! {"
 5569        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5570    "});
 5571
 5572    cx.set_state(indoc! {"
 5573        «hElLo, WoRld!ˇ»
 5574    "});
 5575    cx.update_editor(|e, window, cx| {
 5576        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5577    });
 5578    cx.assert_editor_state(indoc! {"
 5579        «HeLlO, wOrLD!ˇ»
 5580    "});
 5581
 5582    // Test selections with `line_mode() = true`.
 5583    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5584    cx.set_state(indoc! {"
 5585        «The quick brown
 5586        fox jumps over
 5587        tˇ»he lazy dog
 5588    "});
 5589    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        «THE QUICK BROWN
 5592        FOX JUMPS OVER
 5593        THE LAZY DOGˇ»
 5594    "});
 5595}
 5596
 5597#[gpui::test]
 5598fn test_duplicate_line(cx: &mut TestAppContext) {
 5599    init_test(cx, |_| {});
 5600
 5601    let editor = cx.add_window(|window, cx| {
 5602        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5603        build_editor(buffer, window, cx)
 5604    });
 5605    _ = editor.update(cx, |editor, window, cx| {
 5606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5607            s.select_display_ranges([
 5608                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5609                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5610                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5611                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5612            ])
 5613        });
 5614        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5615        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5616        assert_eq!(
 5617            editor.selections.display_ranges(cx),
 5618            vec![
 5619                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5621                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5622                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5623            ]
 5624        );
 5625    });
 5626
 5627    let editor = cx.add_window(|window, cx| {
 5628        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5629        build_editor(buffer, window, cx)
 5630    });
 5631    _ = editor.update(cx, |editor, window, cx| {
 5632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5633            s.select_display_ranges([
 5634                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5635                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5636            ])
 5637        });
 5638        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5639        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5640        assert_eq!(
 5641            editor.selections.display_ranges(cx),
 5642            vec![
 5643                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5644                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5645            ]
 5646        );
 5647    });
 5648
 5649    // With `duplicate_line_up` the selections move to the duplicated lines,
 5650    // which are inserted above the original lines
 5651    let editor = cx.add_window(|window, cx| {
 5652        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5653        build_editor(buffer, window, cx)
 5654    });
 5655    _ = editor.update(cx, |editor, window, cx| {
 5656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5657            s.select_display_ranges([
 5658                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5659                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5660                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5661                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5662            ])
 5663        });
 5664        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5665        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5666        assert_eq!(
 5667            editor.selections.display_ranges(cx),
 5668            vec![
 5669                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5670                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5671                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5672                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5673            ]
 5674        );
 5675    });
 5676
 5677    let editor = cx.add_window(|window, cx| {
 5678        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5679        build_editor(buffer, window, cx)
 5680    });
 5681    _ = editor.update(cx, |editor, window, cx| {
 5682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5683            s.select_display_ranges([
 5684                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5685                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5686            ])
 5687        });
 5688        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5689        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5690        assert_eq!(
 5691            editor.selections.display_ranges(cx),
 5692            vec![
 5693                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5694                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5695            ]
 5696        );
 5697    });
 5698
 5699    let editor = cx.add_window(|window, cx| {
 5700        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5701        build_editor(buffer, window, cx)
 5702    });
 5703    _ = editor.update(cx, |editor, window, cx| {
 5704        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5705            s.select_display_ranges([
 5706                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5708            ])
 5709        });
 5710        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5711        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5712        assert_eq!(
 5713            editor.selections.display_ranges(cx),
 5714            vec![
 5715                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5716                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5717            ]
 5718        );
 5719    });
 5720}
 5721
 5722#[gpui::test]
 5723fn test_move_line_up_down(cx: &mut TestAppContext) {
 5724    init_test(cx, |_| {});
 5725
 5726    let editor = cx.add_window(|window, cx| {
 5727        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5728        build_editor(buffer, window, cx)
 5729    });
 5730    _ = editor.update(cx, |editor, window, cx| {
 5731        editor.fold_creases(
 5732            vec![
 5733                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5734                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5735                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5736            ],
 5737            true,
 5738            window,
 5739            cx,
 5740        );
 5741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5742            s.select_display_ranges([
 5743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5744                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5745                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5746                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5747            ])
 5748        });
 5749        assert_eq!(
 5750            editor.display_text(cx),
 5751            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5752        );
 5753
 5754        editor.move_line_up(&MoveLineUp, window, cx);
 5755        assert_eq!(
 5756            editor.display_text(cx),
 5757            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5758        );
 5759        assert_eq!(
 5760            editor.selections.display_ranges(cx),
 5761            vec![
 5762                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5763                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5764                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5765                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5766            ]
 5767        );
 5768    });
 5769
 5770    _ = editor.update(cx, |editor, window, cx| {
 5771        editor.move_line_down(&MoveLineDown, window, cx);
 5772        assert_eq!(
 5773            editor.display_text(cx),
 5774            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5775        );
 5776        assert_eq!(
 5777            editor.selections.display_ranges(cx),
 5778            vec![
 5779                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5780                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5781                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5782                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5783            ]
 5784        );
 5785    });
 5786
 5787    _ = editor.update(cx, |editor, window, cx| {
 5788        editor.move_line_down(&MoveLineDown, window, cx);
 5789        assert_eq!(
 5790            editor.display_text(cx),
 5791            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5792        );
 5793        assert_eq!(
 5794            editor.selections.display_ranges(cx),
 5795            vec![
 5796                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5797                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5798                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5799                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5800            ]
 5801        );
 5802    });
 5803
 5804    _ = editor.update(cx, |editor, window, cx| {
 5805        editor.move_line_up(&MoveLineUp, window, cx);
 5806        assert_eq!(
 5807            editor.display_text(cx),
 5808            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5809        );
 5810        assert_eq!(
 5811            editor.selections.display_ranges(cx),
 5812            vec![
 5813                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5814                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5815                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5816                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5817            ]
 5818        );
 5819    });
 5820}
 5821
 5822#[gpui::test]
 5823fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5824    init_test(cx, |_| {});
 5825    let editor = cx.add_window(|window, cx| {
 5826        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5827        build_editor(buffer, window, cx)
 5828    });
 5829    _ = editor.update(cx, |editor, window, cx| {
 5830        editor.fold_creases(
 5831            vec![Crease::simple(
 5832                Point::new(6, 4)..Point::new(7, 4),
 5833                FoldPlaceholder::test(),
 5834            )],
 5835            true,
 5836            window,
 5837            cx,
 5838        );
 5839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5840            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5841        });
 5842        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5843        editor.move_line_up(&MoveLineUp, window, cx);
 5844        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5845        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5846    });
 5847}
 5848
 5849#[gpui::test]
 5850fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5851    init_test(cx, |_| {});
 5852
 5853    let editor = cx.add_window(|window, cx| {
 5854        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5855        build_editor(buffer, window, cx)
 5856    });
 5857    _ = editor.update(cx, |editor, window, cx| {
 5858        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5859        editor.insert_blocks(
 5860            [BlockProperties {
 5861                style: BlockStyle::Fixed,
 5862                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5863                height: Some(1),
 5864                render: Arc::new(|_| div().into_any()),
 5865                priority: 0,
 5866            }],
 5867            Some(Autoscroll::fit()),
 5868            cx,
 5869        );
 5870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5871            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5872        });
 5873        editor.move_line_down(&MoveLineDown, window, cx);
 5874    });
 5875}
 5876
 5877#[gpui::test]
 5878async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5879    init_test(cx, |_| {});
 5880
 5881    let mut cx = EditorTestContext::new(cx).await;
 5882    cx.set_state(
 5883        &"
 5884            ˇzero
 5885            one
 5886            two
 5887            three
 5888            four
 5889            five
 5890        "
 5891        .unindent(),
 5892    );
 5893
 5894    // Create a four-line block that replaces three lines of text.
 5895    cx.update_editor(|editor, window, cx| {
 5896        let snapshot = editor.snapshot(window, cx);
 5897        let snapshot = &snapshot.buffer_snapshot();
 5898        let placement = BlockPlacement::Replace(
 5899            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5900        );
 5901        editor.insert_blocks(
 5902            [BlockProperties {
 5903                placement,
 5904                height: Some(4),
 5905                style: BlockStyle::Sticky,
 5906                render: Arc::new(|_| gpui::div().into_any_element()),
 5907                priority: 0,
 5908            }],
 5909            None,
 5910            cx,
 5911        );
 5912    });
 5913
 5914    // Move down so that the cursor touches the block.
 5915    cx.update_editor(|editor, window, cx| {
 5916        editor.move_down(&Default::default(), window, cx);
 5917    });
 5918    cx.assert_editor_state(
 5919        &"
 5920            zero
 5921            «one
 5922            two
 5923            threeˇ»
 5924            four
 5925            five
 5926        "
 5927        .unindent(),
 5928    );
 5929
 5930    // Move down past the block.
 5931    cx.update_editor(|editor, window, cx| {
 5932        editor.move_down(&Default::default(), window, cx);
 5933    });
 5934    cx.assert_editor_state(
 5935        &"
 5936            zero
 5937            one
 5938            two
 5939            three
 5940            ˇfour
 5941            five
 5942        "
 5943        .unindent(),
 5944    );
 5945}
 5946
 5947#[gpui::test]
 5948fn test_transpose(cx: &mut TestAppContext) {
 5949    init_test(cx, |_| {});
 5950
 5951    _ = cx.add_window(|window, cx| {
 5952        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5953        editor.set_style(EditorStyle::default(), window, cx);
 5954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5955            s.select_ranges([1..1])
 5956        });
 5957        editor.transpose(&Default::default(), window, cx);
 5958        assert_eq!(editor.text(cx), "bac");
 5959        assert_eq!(
 5960            editor.selections.ranges(&editor.display_snapshot(cx)),
 5961            [2..2]
 5962        );
 5963
 5964        editor.transpose(&Default::default(), window, cx);
 5965        assert_eq!(editor.text(cx), "bca");
 5966        assert_eq!(
 5967            editor.selections.ranges(&editor.display_snapshot(cx)),
 5968            [3..3]
 5969        );
 5970
 5971        editor.transpose(&Default::default(), window, cx);
 5972        assert_eq!(editor.text(cx), "bac");
 5973        assert_eq!(
 5974            editor.selections.ranges(&editor.display_snapshot(cx)),
 5975            [3..3]
 5976        );
 5977
 5978        editor
 5979    });
 5980
 5981    _ = cx.add_window(|window, cx| {
 5982        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5983        editor.set_style(EditorStyle::default(), window, cx);
 5984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5985            s.select_ranges([3..3])
 5986        });
 5987        editor.transpose(&Default::default(), window, cx);
 5988        assert_eq!(editor.text(cx), "acb\nde");
 5989        assert_eq!(
 5990            editor.selections.ranges(&editor.display_snapshot(cx)),
 5991            [3..3]
 5992        );
 5993
 5994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5995            s.select_ranges([4..4])
 5996        });
 5997        editor.transpose(&Default::default(), window, cx);
 5998        assert_eq!(editor.text(cx), "acbd\ne");
 5999        assert_eq!(
 6000            editor.selections.ranges(&editor.display_snapshot(cx)),
 6001            [5..5]
 6002        );
 6003
 6004        editor.transpose(&Default::default(), window, cx);
 6005        assert_eq!(editor.text(cx), "acbde\n");
 6006        assert_eq!(
 6007            editor.selections.ranges(&editor.display_snapshot(cx)),
 6008            [6..6]
 6009        );
 6010
 6011        editor.transpose(&Default::default(), window, cx);
 6012        assert_eq!(editor.text(cx), "acbd\ne");
 6013        assert_eq!(
 6014            editor.selections.ranges(&editor.display_snapshot(cx)),
 6015            [6..6]
 6016        );
 6017
 6018        editor
 6019    });
 6020
 6021    _ = cx.add_window(|window, cx| {
 6022        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6023        editor.set_style(EditorStyle::default(), window, cx);
 6024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6025            s.select_ranges([1..1, 2..2, 4..4])
 6026        });
 6027        editor.transpose(&Default::default(), window, cx);
 6028        assert_eq!(editor.text(cx), "bacd\ne");
 6029        assert_eq!(
 6030            editor.selections.ranges(&editor.display_snapshot(cx)),
 6031            [2..2, 3..3, 5..5]
 6032        );
 6033
 6034        editor.transpose(&Default::default(), window, cx);
 6035        assert_eq!(editor.text(cx), "bcade\n");
 6036        assert_eq!(
 6037            editor.selections.ranges(&editor.display_snapshot(cx)),
 6038            [3..3, 4..4, 6..6]
 6039        );
 6040
 6041        editor.transpose(&Default::default(), window, cx);
 6042        assert_eq!(editor.text(cx), "bcda\ne");
 6043        assert_eq!(
 6044            editor.selections.ranges(&editor.display_snapshot(cx)),
 6045            [4..4, 6..6]
 6046        );
 6047
 6048        editor.transpose(&Default::default(), window, cx);
 6049        assert_eq!(editor.text(cx), "bcade\n");
 6050        assert_eq!(
 6051            editor.selections.ranges(&editor.display_snapshot(cx)),
 6052            [4..4, 6..6]
 6053        );
 6054
 6055        editor.transpose(&Default::default(), window, cx);
 6056        assert_eq!(editor.text(cx), "bcaed\n");
 6057        assert_eq!(
 6058            editor.selections.ranges(&editor.display_snapshot(cx)),
 6059            [5..5, 6..6]
 6060        );
 6061
 6062        editor
 6063    });
 6064
 6065    _ = cx.add_window(|window, cx| {
 6066        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6067        editor.set_style(EditorStyle::default(), window, cx);
 6068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6069            s.select_ranges([4..4])
 6070        });
 6071        editor.transpose(&Default::default(), window, cx);
 6072        assert_eq!(editor.text(cx), "🏀🍐✋");
 6073        assert_eq!(
 6074            editor.selections.ranges(&editor.display_snapshot(cx)),
 6075            [8..8]
 6076        );
 6077
 6078        editor.transpose(&Default::default(), window, cx);
 6079        assert_eq!(editor.text(cx), "🏀✋🍐");
 6080        assert_eq!(
 6081            editor.selections.ranges(&editor.display_snapshot(cx)),
 6082            [11..11]
 6083        );
 6084
 6085        editor.transpose(&Default::default(), window, cx);
 6086        assert_eq!(editor.text(cx), "🏀🍐✋");
 6087        assert_eq!(
 6088            editor.selections.ranges(&editor.display_snapshot(cx)),
 6089            [11..11]
 6090        );
 6091
 6092        editor
 6093    });
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_rewrap(cx: &mut TestAppContext) {
 6098    init_test(cx, |settings| {
 6099        settings.languages.0.extend([
 6100            (
 6101                "Markdown".into(),
 6102                LanguageSettingsContent {
 6103                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6104                    preferred_line_length: Some(40),
 6105                    ..Default::default()
 6106                },
 6107            ),
 6108            (
 6109                "Plain Text".into(),
 6110                LanguageSettingsContent {
 6111                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6112                    preferred_line_length: Some(40),
 6113                    ..Default::default()
 6114                },
 6115            ),
 6116            (
 6117                "C++".into(),
 6118                LanguageSettingsContent {
 6119                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6120                    preferred_line_length: Some(40),
 6121                    ..Default::default()
 6122                },
 6123            ),
 6124            (
 6125                "Python".into(),
 6126                LanguageSettingsContent {
 6127                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6128                    preferred_line_length: Some(40),
 6129                    ..Default::default()
 6130                },
 6131            ),
 6132            (
 6133                "Rust".into(),
 6134                LanguageSettingsContent {
 6135                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6136                    preferred_line_length: Some(40),
 6137                    ..Default::default()
 6138                },
 6139            ),
 6140        ])
 6141    });
 6142
 6143    let mut cx = EditorTestContext::new(cx).await;
 6144
 6145    let cpp_language = Arc::new(Language::new(
 6146        LanguageConfig {
 6147            name: "C++".into(),
 6148            line_comments: vec!["// ".into()],
 6149            ..LanguageConfig::default()
 6150        },
 6151        None,
 6152    ));
 6153    let python_language = Arc::new(Language::new(
 6154        LanguageConfig {
 6155            name: "Python".into(),
 6156            line_comments: vec!["# ".into()],
 6157            ..LanguageConfig::default()
 6158        },
 6159        None,
 6160    ));
 6161    let markdown_language = Arc::new(Language::new(
 6162        LanguageConfig {
 6163            name: "Markdown".into(),
 6164            rewrap_prefixes: vec![
 6165                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6166                regex::Regex::new("[-*+]\\s+").unwrap(),
 6167            ],
 6168            ..LanguageConfig::default()
 6169        },
 6170        None,
 6171    ));
 6172    let rust_language = Arc::new(
 6173        Language::new(
 6174            LanguageConfig {
 6175                name: "Rust".into(),
 6176                line_comments: vec!["// ".into(), "/// ".into()],
 6177                ..LanguageConfig::default()
 6178            },
 6179            Some(tree_sitter_rust::LANGUAGE.into()),
 6180        )
 6181        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6182        .unwrap(),
 6183    );
 6184
 6185    let plaintext_language = Arc::new(Language::new(
 6186        LanguageConfig {
 6187            name: "Plain Text".into(),
 6188            ..LanguageConfig::default()
 6189        },
 6190        None,
 6191    ));
 6192
 6193    // Test basic rewrapping of a long line with a cursor
 6194    assert_rewrap(
 6195        indoc! {"
 6196            // ˇThis is a long comment that needs to be wrapped.
 6197        "},
 6198        indoc! {"
 6199            // ˇThis is a long comment that needs to
 6200            // be wrapped.
 6201        "},
 6202        cpp_language.clone(),
 6203        &mut cx,
 6204    );
 6205
 6206    // Test rewrapping a full selection
 6207    assert_rewrap(
 6208        indoc! {"
 6209            «// This selected long comment needs to be wrapped.ˇ»"
 6210        },
 6211        indoc! {"
 6212            «// This selected long comment needs to
 6213            // be wrapped.ˇ»"
 6214        },
 6215        cpp_language.clone(),
 6216        &mut cx,
 6217    );
 6218
 6219    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6220    assert_rewrap(
 6221        indoc! {"
 6222            // ˇThis is the first line.
 6223            // Thisˇ is the second line.
 6224            // This is the thirdˇ line, all part of one paragraph.
 6225         "},
 6226        indoc! {"
 6227            // ˇThis is the first line. Thisˇ is the
 6228            // second line. This is the thirdˇ line,
 6229            // all part of one paragraph.
 6230         "},
 6231        cpp_language.clone(),
 6232        &mut cx,
 6233    );
 6234
 6235    // Test multiple cursors in different paragraphs trigger separate rewraps
 6236    assert_rewrap(
 6237        indoc! {"
 6238            // ˇThis is the first paragraph, first line.
 6239            // ˇThis is the first paragraph, second line.
 6240
 6241            // ˇThis is the second paragraph, first line.
 6242            // ˇThis is the second paragraph, second line.
 6243        "},
 6244        indoc! {"
 6245            // ˇThis is the first paragraph, first
 6246            // line. ˇThis is the first paragraph,
 6247            // second line.
 6248
 6249            // ˇThis is the second paragraph, first
 6250            // line. ˇThis is the second paragraph,
 6251            // second line.
 6252        "},
 6253        cpp_language.clone(),
 6254        &mut cx,
 6255    );
 6256
 6257    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6258    assert_rewrap(
 6259        indoc! {"
 6260            «// A regular long long comment to be wrapped.
 6261            /// A documentation long comment to be wrapped.ˇ»
 6262          "},
 6263        indoc! {"
 6264            «// A regular long long comment to be
 6265            // wrapped.
 6266            /// A documentation long comment to be
 6267            /// wrapped.ˇ»
 6268          "},
 6269        rust_language.clone(),
 6270        &mut cx,
 6271    );
 6272
 6273    // Test that change in indentation level trigger seperate rewraps
 6274    assert_rewrap(
 6275        indoc! {"
 6276            fn foo() {
 6277                «// This is a long comment at the base indent.
 6278                    // This is a long comment at the next indent.ˇ»
 6279            }
 6280        "},
 6281        indoc! {"
 6282            fn foo() {
 6283                «// This is a long comment at the
 6284                // base indent.
 6285                    // This is a long comment at the
 6286                    // next indent.ˇ»
 6287            }
 6288        "},
 6289        rust_language.clone(),
 6290        &mut cx,
 6291    );
 6292
 6293    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6294    assert_rewrap(
 6295        indoc! {"
 6296            # ˇThis is a long comment using a pound sign.
 6297        "},
 6298        indoc! {"
 6299            # ˇThis is a long comment using a pound
 6300            # sign.
 6301        "},
 6302        python_language,
 6303        &mut cx,
 6304    );
 6305
 6306    // Test rewrapping only affects comments, not code even when selected
 6307    assert_rewrap(
 6308        indoc! {"
 6309            «/// This doc comment is long and should be wrapped.
 6310            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6311        "},
 6312        indoc! {"
 6313            «/// This doc comment is long and should
 6314            /// be wrapped.
 6315            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6316        "},
 6317        rust_language.clone(),
 6318        &mut cx,
 6319    );
 6320
 6321    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6322    assert_rewrap(
 6323        indoc! {"
 6324            # Header
 6325
 6326            A long long long line of markdown text to wrap.ˇ
 6327         "},
 6328        indoc! {"
 6329            # Header
 6330
 6331            A long long long line of markdown text
 6332            to wrap.ˇ
 6333         "},
 6334        markdown_language.clone(),
 6335        &mut cx,
 6336    );
 6337
 6338    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6339    assert_rewrap(
 6340        indoc! {"
 6341            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6342            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6343            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6344        "},
 6345        indoc! {"
 6346            «1. This is a numbered list item that is
 6347               very long and needs to be wrapped
 6348               properly.
 6349            2. This is a numbered list item that is
 6350               very long and needs to be wrapped
 6351               properly.
 6352            - This is an unordered list item that is
 6353              also very long and should not merge
 6354              with the numbered item.ˇ»
 6355        "},
 6356        markdown_language.clone(),
 6357        &mut cx,
 6358    );
 6359
 6360    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6361    assert_rewrap(
 6362        indoc! {"
 6363            «1. This is a numbered list item that is
 6364            very long and needs to be wrapped
 6365            properly.
 6366            2. This is a numbered list item that is
 6367            very long and needs to be wrapped
 6368            properly.
 6369            - This is an unordered list item that is
 6370            also very long and should not merge with
 6371            the numbered item.ˇ»
 6372        "},
 6373        indoc! {"
 6374            «1. This is a numbered list item that is
 6375               very long and needs to be wrapped
 6376               properly.
 6377            2. This is a numbered list item that is
 6378               very long and needs to be wrapped
 6379               properly.
 6380            - This is an unordered list item that is
 6381              also very long and should not merge
 6382              with the numbered item.ˇ»
 6383        "},
 6384        markdown_language.clone(),
 6385        &mut cx,
 6386    );
 6387
 6388    // Test that rewrapping maintain indents even when they already exists.
 6389    assert_rewrap(
 6390        indoc! {"
 6391            «1. This is a numbered list
 6392               item that is very long and needs to be wrapped properly.
 6393            2. This is a numbered list
 6394               item that is very long and needs to be wrapped properly.
 6395            - This is an unordered list item that is also very long and
 6396              should not merge with the numbered item.ˇ»
 6397        "},
 6398        indoc! {"
 6399            «1. This is a numbered list item that is
 6400               very long and needs to be wrapped
 6401               properly.
 6402            2. This is a numbered list item that is
 6403               very long and needs to be wrapped
 6404               properly.
 6405            - This is an unordered list item that is
 6406              also very long and should not merge
 6407              with the numbered item.ˇ»
 6408        "},
 6409        markdown_language,
 6410        &mut cx,
 6411    );
 6412
 6413    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6414    assert_rewrap(
 6415        indoc! {"
 6416            ˇThis is a very long line of plain text that will be wrapped.
 6417        "},
 6418        indoc! {"
 6419            ˇThis is a very long line of plain text
 6420            that will be wrapped.
 6421        "},
 6422        plaintext_language.clone(),
 6423        &mut cx,
 6424    );
 6425
 6426    // Test that non-commented code acts as a paragraph boundary within a selection
 6427    assert_rewrap(
 6428        indoc! {"
 6429               «// This is the first long comment block to be wrapped.
 6430               fn my_func(a: u32);
 6431               // This is the second long comment block to be wrapped.ˇ»
 6432           "},
 6433        indoc! {"
 6434               «// This is the first long comment block
 6435               // to be wrapped.
 6436               fn my_func(a: u32);
 6437               // This is the second long comment block
 6438               // to be wrapped.ˇ»
 6439           "},
 6440        rust_language,
 6441        &mut cx,
 6442    );
 6443
 6444    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6445    assert_rewrap(
 6446        indoc! {"
 6447            «ˇThis is a very long line that will be wrapped.
 6448
 6449            This is another paragraph in the same selection.»
 6450
 6451            «\tThis is a very long indented line that will be wrapped.ˇ»
 6452         "},
 6453        indoc! {"
 6454            «ˇThis is a very long line that will be
 6455            wrapped.
 6456
 6457            This is another paragraph in the same
 6458            selection.»
 6459
 6460            «\tThis is a very long indented line
 6461            \tthat will be wrapped.ˇ»
 6462         "},
 6463        plaintext_language,
 6464        &mut cx,
 6465    );
 6466
 6467    // Test that an empty comment line acts as a paragraph boundary
 6468    assert_rewrap(
 6469        indoc! {"
 6470            // ˇThis is a long comment that will be wrapped.
 6471            //
 6472            // And this is another long comment that will also be wrapped.ˇ
 6473         "},
 6474        indoc! {"
 6475            // ˇThis is a long comment that will be
 6476            // wrapped.
 6477            //
 6478            // And this is another long comment that
 6479            // will also be wrapped.ˇ
 6480         "},
 6481        cpp_language,
 6482        &mut cx,
 6483    );
 6484
 6485    #[track_caller]
 6486    fn assert_rewrap(
 6487        unwrapped_text: &str,
 6488        wrapped_text: &str,
 6489        language: Arc<Language>,
 6490        cx: &mut EditorTestContext,
 6491    ) {
 6492        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6493        cx.set_state(unwrapped_text);
 6494        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6495        cx.assert_editor_state(wrapped_text);
 6496    }
 6497}
 6498
 6499#[gpui::test]
 6500async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6501    init_test(cx, |settings| {
 6502        settings.languages.0.extend([(
 6503            "Rust".into(),
 6504            LanguageSettingsContent {
 6505                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6506                preferred_line_length: Some(40),
 6507                ..Default::default()
 6508            },
 6509        )])
 6510    });
 6511
 6512    let mut cx = EditorTestContext::new(cx).await;
 6513
 6514    let rust_lang = Arc::new(
 6515        Language::new(
 6516            LanguageConfig {
 6517                name: "Rust".into(),
 6518                line_comments: vec!["// ".into()],
 6519                block_comment: Some(BlockCommentConfig {
 6520                    start: "/*".into(),
 6521                    end: "*/".into(),
 6522                    prefix: "* ".into(),
 6523                    tab_size: 1,
 6524                }),
 6525                documentation_comment: Some(BlockCommentConfig {
 6526                    start: "/**".into(),
 6527                    end: "*/".into(),
 6528                    prefix: "* ".into(),
 6529                    tab_size: 1,
 6530                }),
 6531
 6532                ..LanguageConfig::default()
 6533            },
 6534            Some(tree_sitter_rust::LANGUAGE.into()),
 6535        )
 6536        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6537        .unwrap(),
 6538    );
 6539
 6540    // regular block comment
 6541    assert_rewrap(
 6542        indoc! {"
 6543            /*
 6544             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6545             */
 6546            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6547        "},
 6548        indoc! {"
 6549            /*
 6550             *ˇ Lorem ipsum dolor sit amet,
 6551             * consectetur adipiscing elit.
 6552             */
 6553            /*
 6554             *ˇ Lorem ipsum dolor sit amet,
 6555             * consectetur adipiscing elit.
 6556             */
 6557        "},
 6558        rust_lang.clone(),
 6559        &mut cx,
 6560    );
 6561
 6562    // indent is respected
 6563    assert_rewrap(
 6564        indoc! {"
 6565            {}
 6566                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6567        "},
 6568        indoc! {"
 6569            {}
 6570                /*
 6571                 *ˇ Lorem ipsum dolor sit amet,
 6572                 * consectetur adipiscing elit.
 6573                 */
 6574        "},
 6575        rust_lang.clone(),
 6576        &mut cx,
 6577    );
 6578
 6579    // short block comments with inline delimiters
 6580    assert_rewrap(
 6581        indoc! {"
 6582            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6583            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6584             */
 6585            /*
 6586             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6587        "},
 6588        indoc! {"
 6589            /*
 6590             *ˇ Lorem ipsum dolor sit amet,
 6591             * consectetur adipiscing elit.
 6592             */
 6593            /*
 6594             *ˇ Lorem ipsum dolor sit amet,
 6595             * consectetur adipiscing elit.
 6596             */
 6597            /*
 6598             *ˇ Lorem ipsum dolor sit amet,
 6599             * consectetur adipiscing elit.
 6600             */
 6601        "},
 6602        rust_lang.clone(),
 6603        &mut cx,
 6604    );
 6605
 6606    // multiline block comment with inline start/end delimiters
 6607    assert_rewrap(
 6608        indoc! {"
 6609            /*ˇ Lorem ipsum dolor sit amet,
 6610             * consectetur adipiscing elit. */
 6611        "},
 6612        indoc! {"
 6613            /*
 6614             *ˇ Lorem ipsum dolor sit amet,
 6615             * consectetur adipiscing elit.
 6616             */
 6617        "},
 6618        rust_lang.clone(),
 6619        &mut cx,
 6620    );
 6621
 6622    // block comment rewrap still respects paragraph bounds
 6623    assert_rewrap(
 6624        indoc! {"
 6625            /*
 6626             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6627             *
 6628             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6629             */
 6630        "},
 6631        indoc! {"
 6632            /*
 6633             *ˇ Lorem ipsum dolor sit amet,
 6634             * consectetur adipiscing elit.
 6635             *
 6636             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6637             */
 6638        "},
 6639        rust_lang.clone(),
 6640        &mut cx,
 6641    );
 6642
 6643    // documentation comments
 6644    assert_rewrap(
 6645        indoc! {"
 6646            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6647            /**
 6648             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6649             */
 6650        "},
 6651        indoc! {"
 6652            /**
 6653             *ˇ Lorem ipsum dolor sit amet,
 6654             * consectetur adipiscing elit.
 6655             */
 6656            /**
 6657             *ˇ Lorem ipsum dolor sit amet,
 6658             * consectetur adipiscing elit.
 6659             */
 6660        "},
 6661        rust_lang.clone(),
 6662        &mut cx,
 6663    );
 6664
 6665    // different, adjacent comments
 6666    assert_rewrap(
 6667        indoc! {"
 6668            /**
 6669             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6670             */
 6671            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6672            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6673        "},
 6674        indoc! {"
 6675            /**
 6676             *ˇ Lorem ipsum dolor sit amet,
 6677             * consectetur adipiscing elit.
 6678             */
 6679            /*
 6680             *ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit.
 6682             */
 6683            //ˇ Lorem ipsum dolor sit amet,
 6684            // consectetur adipiscing elit.
 6685        "},
 6686        rust_lang.clone(),
 6687        &mut cx,
 6688    );
 6689
 6690    // selection w/ single short block comment
 6691    assert_rewrap(
 6692        indoc! {"
 6693            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6694        "},
 6695        indoc! {"
 6696            «/*
 6697             * Lorem ipsum dolor sit amet,
 6698             * consectetur adipiscing elit.
 6699             */ˇ»
 6700        "},
 6701        rust_lang.clone(),
 6702        &mut cx,
 6703    );
 6704
 6705    // rewrapping a single comment w/ abutting comments
 6706    assert_rewrap(
 6707        indoc! {"
 6708            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6709            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6710        "},
 6711        indoc! {"
 6712            /*
 6713             * ˇLorem ipsum dolor sit amet,
 6714             * consectetur adipiscing elit.
 6715             */
 6716            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6717        "},
 6718        rust_lang.clone(),
 6719        &mut cx,
 6720    );
 6721
 6722    // selection w/ non-abutting short block comments
 6723    assert_rewrap(
 6724        indoc! {"
 6725            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6726
 6727            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6728        "},
 6729        indoc! {"
 6730            «/*
 6731             * Lorem ipsum dolor sit amet,
 6732             * consectetur adipiscing elit.
 6733             */
 6734
 6735            /*
 6736             * Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */ˇ»
 6739        "},
 6740        rust_lang.clone(),
 6741        &mut cx,
 6742    );
 6743
 6744    // selection of multiline block comments
 6745    assert_rewrap(
 6746        indoc! {"
 6747            «/* Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit. */ˇ»
 6749        "},
 6750        indoc! {"
 6751            «/*
 6752             * Lorem ipsum dolor sit amet,
 6753             * consectetur adipiscing elit.
 6754             */ˇ»
 6755        "},
 6756        rust_lang.clone(),
 6757        &mut cx,
 6758    );
 6759
 6760    // partial selection of multiline block comments
 6761    assert_rewrap(
 6762        indoc! {"
 6763            «/* Lorem ipsum dolor sit amet,ˇ»
 6764             * consectetur adipiscing elit. */
 6765            /* Lorem ipsum dolor sit amet,
 6766             «* consectetur adipiscing elit. */ˇ»
 6767        "},
 6768        indoc! {"
 6769            «/*
 6770             * Lorem ipsum dolor sit amet,ˇ»
 6771             * consectetur adipiscing elit. */
 6772            /* Lorem ipsum dolor sit amet,
 6773             «* consectetur adipiscing elit.
 6774             */ˇ»
 6775        "},
 6776        rust_lang.clone(),
 6777        &mut cx,
 6778    );
 6779
 6780    // selection w/ abutting short block comments
 6781    // TODO: should not be combined; should rewrap as 2 comments
 6782    assert_rewrap(
 6783        indoc! {"
 6784            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6785            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6786        "},
 6787        // desired behavior:
 6788        // indoc! {"
 6789        //     «/*
 6790        //      * Lorem ipsum dolor sit amet,
 6791        //      * consectetur adipiscing elit.
 6792        //      */
 6793        //     /*
 6794        //      * Lorem ipsum dolor sit amet,
 6795        //      * consectetur adipiscing elit.
 6796        //      */ˇ»
 6797        // "},
 6798        // actual behaviour:
 6799        indoc! {"
 6800            «/*
 6801             * Lorem ipsum dolor sit amet,
 6802             * consectetur adipiscing elit. Lorem
 6803             * ipsum dolor sit amet, consectetur
 6804             * adipiscing elit.
 6805             */ˇ»
 6806        "},
 6807        rust_lang.clone(),
 6808        &mut cx,
 6809    );
 6810
 6811    // TODO: same as above, but with delimiters on separate line
 6812    // assert_rewrap(
 6813    //     indoc! {"
 6814    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6815    //          */
 6816    //         /*
 6817    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6818    //     "},
 6819    //     // desired:
 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    //     // actual: (but with trailing w/s on the empty lines)
 6831    //     indoc! {"
 6832    //         «/*
 6833    //          * Lorem ipsum dolor sit amet,
 6834    //          * consectetur adipiscing elit.
 6835    //          *
 6836    //          */
 6837    //         /*
 6838    //          *
 6839    //          * Lorem ipsum dolor sit amet,
 6840    //          * consectetur adipiscing elit.
 6841    //          */ˇ»
 6842    //     "},
 6843    //     rust_lang.clone(),
 6844    //     &mut cx,
 6845    // );
 6846
 6847    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6848    assert_rewrap(
 6849        indoc! {"
 6850            /*
 6851             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6852             */
 6853            /*
 6854             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6856        "},
 6857        // desired:
 6858        // indoc! {"
 6859        //     /*
 6860        //      *ˇ Lorem ipsum dolor sit amet,
 6861        //      * consectetur adipiscing elit.
 6862        //      */
 6863        //     /*
 6864        //      *ˇ Lorem ipsum dolor sit amet,
 6865        //      * consectetur adipiscing elit.
 6866        //      */
 6867        //     /*
 6868        //      *ˇ Lorem ipsum dolor sit amet
 6869        //      */ /* consectetur adipiscing elit. */
 6870        // "},
 6871        // actual:
 6872        indoc! {"
 6873            /*
 6874             //ˇ Lorem ipsum dolor sit amet,
 6875             // consectetur adipiscing elit.
 6876             */
 6877            /*
 6878             * //ˇ Lorem ipsum dolor sit amet,
 6879             * consectetur adipiscing elit.
 6880             */
 6881            /*
 6882             *ˇ Lorem ipsum dolor sit amet */ /*
 6883             * consectetur adipiscing elit.
 6884             */
 6885        "},
 6886        rust_lang,
 6887        &mut cx,
 6888    );
 6889
 6890    #[track_caller]
 6891    fn assert_rewrap(
 6892        unwrapped_text: &str,
 6893        wrapped_text: &str,
 6894        language: Arc<Language>,
 6895        cx: &mut EditorTestContext,
 6896    ) {
 6897        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6898        cx.set_state(unwrapped_text);
 6899        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6900        cx.assert_editor_state(wrapped_text);
 6901    }
 6902}
 6903
 6904#[gpui::test]
 6905async fn test_hard_wrap(cx: &mut TestAppContext) {
 6906    init_test(cx, |_| {});
 6907    let mut cx = EditorTestContext::new(cx).await;
 6908
 6909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6910    cx.update_editor(|editor, _, cx| {
 6911        editor.set_hard_wrap(Some(14), cx);
 6912    });
 6913
 6914    cx.set_state(indoc!(
 6915        "
 6916        one two three ˇ
 6917        "
 6918    ));
 6919    cx.simulate_input("four");
 6920    cx.run_until_parked();
 6921
 6922    cx.assert_editor_state(indoc!(
 6923        "
 6924        one two three
 6925        fourˇ
 6926        "
 6927    ));
 6928
 6929    cx.update_editor(|editor, window, cx| {
 6930        editor.newline(&Default::default(), window, cx);
 6931    });
 6932    cx.run_until_parked();
 6933    cx.assert_editor_state(indoc!(
 6934        "
 6935        one two three
 6936        four
 6937        ˇ
 6938        "
 6939    ));
 6940
 6941    cx.simulate_input("five");
 6942    cx.run_until_parked();
 6943    cx.assert_editor_state(indoc!(
 6944        "
 6945        one two three
 6946        four
 6947        fiveˇ
 6948        "
 6949    ));
 6950
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.newline(&Default::default(), window, cx);
 6953    });
 6954    cx.run_until_parked();
 6955    cx.simulate_input("# ");
 6956    cx.run_until_parked();
 6957    cx.assert_editor_state(indoc!(
 6958        "
 6959        one two three
 6960        four
 6961        five
 6962        # ˇ
 6963        "
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.newline(&Default::default(), window, cx);
 6968    });
 6969    cx.run_until_parked();
 6970    cx.assert_editor_state(indoc!(
 6971        "
 6972        one two three
 6973        four
 6974        five
 6975        #\x20
 6976 6977        "
 6978    ));
 6979
 6980    cx.simulate_input(" 6");
 6981    cx.run_until_parked();
 6982    cx.assert_editor_state(indoc!(
 6983        "
 6984        one two three
 6985        four
 6986        five
 6987        #
 6988        # 6ˇ
 6989        "
 6990    ));
 6991}
 6992
 6993#[gpui::test]
 6994async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6995    init_test(cx, |_| {});
 6996
 6997    let mut cx = EditorTestContext::new(cx).await;
 6998
 6999    cx.set_state(indoc! {"The quick brownˇ"});
 7000    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7001    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7002
 7003    cx.set_state(indoc! {"The emacs foxˇ"});
 7004    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7005    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7006
 7007    cx.set_state(indoc! {"
 7008        The quick« brownˇ»
 7009        fox jumps overˇ
 7010        the lazy dog"});
 7011    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7012    cx.assert_editor_state(indoc! {"
 7013        The quickˇ
 7014        ˇthe lazy dog"});
 7015
 7016    cx.set_state(indoc! {"
 7017        The quick« brownˇ»
 7018        fox jumps overˇ
 7019        the lazy dog"});
 7020    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7021    cx.assert_editor_state(indoc! {"
 7022        The quickˇ
 7023        fox jumps overˇthe lazy dog"});
 7024
 7025    cx.set_state(indoc! {"
 7026        The quick« brownˇ»
 7027        fox jumps overˇ
 7028        the lazy dog"});
 7029    cx.update_editor(|e, window, cx| {
 7030        e.cut_to_end_of_line(
 7031            &CutToEndOfLine {
 7032                stop_at_newlines: true,
 7033            },
 7034            window,
 7035            cx,
 7036        )
 7037    });
 7038    cx.assert_editor_state(indoc! {"
 7039        The quickˇ
 7040        fox jumps overˇ
 7041        the lazy dog"});
 7042
 7043    cx.set_state(indoc! {"
 7044        The quick« brownˇ»
 7045        fox jumps overˇ
 7046        the lazy dog"});
 7047    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7048    cx.assert_editor_state(indoc! {"
 7049        The quickˇ
 7050        fox jumps overˇthe lazy dog"});
 7051}
 7052
 7053#[gpui::test]
 7054async fn test_clipboard(cx: &mut TestAppContext) {
 7055    init_test(cx, |_| {});
 7056
 7057    let mut cx = EditorTestContext::new(cx).await;
 7058
 7059    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7060    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7061    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7062
 7063    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7064    cx.set_state("two ˇfour ˇsix ˇ");
 7065    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7066    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7067
 7068    // Paste again but with only two cursors. Since the number of cursors doesn't
 7069    // match the number of slices in the clipboard, the entire clipboard text
 7070    // is pasted at each cursor.
 7071    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7072    cx.update_editor(|e, window, cx| {
 7073        e.handle_input("( ", window, cx);
 7074        e.paste(&Paste, window, cx);
 7075        e.handle_input(") ", window, cx);
 7076    });
 7077    cx.assert_editor_state(
 7078        &([
 7079            "( one✅ ",
 7080            "three ",
 7081            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7082            "three ",
 7083            "five ) ˇ",
 7084        ]
 7085        .join("\n")),
 7086    );
 7087
 7088    // Cut with three selections, one of which is full-line.
 7089    cx.set_state(indoc! {"
 7090        1«2ˇ»3
 7091        4ˇ567
 7092        «8ˇ»9"});
 7093    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7094    cx.assert_editor_state(indoc! {"
 7095        1ˇ3
 7096        ˇ9"});
 7097
 7098    // Paste with three selections, noticing how the copied selection that was full-line
 7099    // gets inserted before the second cursor.
 7100    cx.set_state(indoc! {"
 7101        1ˇ3
 7102 7103        «oˇ»ne"});
 7104    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7105    cx.assert_editor_state(indoc! {"
 7106        12ˇ3
 7107        4567
 7108 7109        8ˇne"});
 7110
 7111    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7112    cx.set_state(indoc! {"
 7113        The quick brown
 7114        fox juˇmps over
 7115        the lazy dog"});
 7116    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7117    assert_eq!(
 7118        cx.read_from_clipboard()
 7119            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7120        Some("fox jumps over\n".to_string())
 7121    );
 7122
 7123    // Paste with three selections, noticing how the copied full-line selection is inserted
 7124    // before the empty selections but replaces the selection that is non-empty.
 7125    cx.set_state(indoc! {"
 7126        Tˇhe quick brown
 7127        «foˇ»x jumps over
 7128        tˇhe lazy dog"});
 7129    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7130    cx.assert_editor_state(indoc! {"
 7131        fox jumps over
 7132        Tˇhe quick brown
 7133        fox jumps over
 7134        ˇx jumps over
 7135        fox jumps over
 7136        tˇhe lazy dog"});
 7137}
 7138
 7139#[gpui::test]
 7140async fn test_copy_trim(cx: &mut TestAppContext) {
 7141    init_test(cx, |_| {});
 7142
 7143    let mut cx = EditorTestContext::new(cx).await;
 7144    cx.set_state(
 7145        r#"            «for selection in selections.iter() {
 7146            let mut start = selection.start;
 7147            let mut end = selection.end;
 7148            let is_entire_line = selection.is_empty();
 7149            if is_entire_line {
 7150                start = Point::new(start.row, 0);ˇ»
 7151                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7152            }
 7153        "#,
 7154    );
 7155    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7156    assert_eq!(
 7157        cx.read_from_clipboard()
 7158            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7159        Some(
 7160            "for selection in selections.iter() {
 7161            let mut start = selection.start;
 7162            let mut end = selection.end;
 7163            let is_entire_line = selection.is_empty();
 7164            if is_entire_line {
 7165                start = Point::new(start.row, 0);"
 7166                .to_string()
 7167        ),
 7168        "Regular copying preserves all indentation selected",
 7169    );
 7170    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7171    assert_eq!(
 7172        cx.read_from_clipboard()
 7173            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7174        Some(
 7175            "for selection in selections.iter() {
 7176let mut start = selection.start;
 7177let mut end = selection.end;
 7178let is_entire_line = selection.is_empty();
 7179if is_entire_line {
 7180    start = Point::new(start.row, 0);"
 7181                .to_string()
 7182        ),
 7183        "Copying with stripping should strip all leading whitespaces"
 7184    );
 7185
 7186    cx.set_state(
 7187        r#"       «     for selection in selections.iter() {
 7188            let mut start = selection.start;
 7189            let mut end = selection.end;
 7190            let is_entire_line = selection.is_empty();
 7191            if is_entire_line {
 7192                start = Point::new(start.row, 0);ˇ»
 7193                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7194            }
 7195        "#,
 7196    );
 7197    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7198    assert_eq!(
 7199        cx.read_from_clipboard()
 7200            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7201        Some(
 7202            "     for selection in selections.iter() {
 7203            let mut start = selection.start;
 7204            let mut end = selection.end;
 7205            let is_entire_line = selection.is_empty();
 7206            if is_entire_line {
 7207                start = Point::new(start.row, 0);"
 7208                .to_string()
 7209        ),
 7210        "Regular copying preserves all indentation selected",
 7211    );
 7212    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7213    assert_eq!(
 7214        cx.read_from_clipboard()
 7215            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7216        Some(
 7217            "for selection in selections.iter() {
 7218let mut start = selection.start;
 7219let mut end = selection.end;
 7220let is_entire_line = selection.is_empty();
 7221if is_entire_line {
 7222    start = Point::new(start.row, 0);"
 7223                .to_string()
 7224        ),
 7225        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7226    );
 7227
 7228    cx.set_state(
 7229        r#"       «ˇ     for selection in selections.iter() {
 7230            let mut start = selection.start;
 7231            let mut end = selection.end;
 7232            let is_entire_line = selection.is_empty();
 7233            if is_entire_line {
 7234                start = Point::new(start.row, 0);»
 7235                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7236            }
 7237        "#,
 7238    );
 7239    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7240    assert_eq!(
 7241        cx.read_from_clipboard()
 7242            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7243        Some(
 7244            "     for selection in selections.iter() {
 7245            let mut start = selection.start;
 7246            let mut end = selection.end;
 7247            let is_entire_line = selection.is_empty();
 7248            if is_entire_line {
 7249                start = Point::new(start.row, 0);"
 7250                .to_string()
 7251        ),
 7252        "Regular copying for reverse selection works the same",
 7253    );
 7254    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7255    assert_eq!(
 7256        cx.read_from_clipboard()
 7257            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7258        Some(
 7259            "for selection in selections.iter() {
 7260let mut start = selection.start;
 7261let mut end = selection.end;
 7262let is_entire_line = selection.is_empty();
 7263if is_entire_line {
 7264    start = Point::new(start.row, 0);"
 7265                .to_string()
 7266        ),
 7267        "Copying with stripping for reverse selection works the same"
 7268    );
 7269
 7270    cx.set_state(
 7271        r#"            for selection «in selections.iter() {
 7272            let mut start = selection.start;
 7273            let mut end = selection.end;
 7274            let is_entire_line = selection.is_empty();
 7275            if is_entire_line {
 7276                start = Point::new(start.row, 0);ˇ»
 7277                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7278            }
 7279        "#,
 7280    );
 7281    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7282    assert_eq!(
 7283        cx.read_from_clipboard()
 7284            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7285        Some(
 7286            "in selections.iter() {
 7287            let mut start = selection.start;
 7288            let mut end = selection.end;
 7289            let is_entire_line = selection.is_empty();
 7290            if is_entire_line {
 7291                start = Point::new(start.row, 0);"
 7292                .to_string()
 7293        ),
 7294        "When selecting past the indent, the copying works as usual",
 7295    );
 7296    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7297    assert_eq!(
 7298        cx.read_from_clipboard()
 7299            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7300        Some(
 7301            "in selections.iter() {
 7302            let mut start = selection.start;
 7303            let mut end = selection.end;
 7304            let is_entire_line = selection.is_empty();
 7305            if is_entire_line {
 7306                start = Point::new(start.row, 0);"
 7307                .to_string()
 7308        ),
 7309        "When selecting past the indent, nothing is trimmed"
 7310    );
 7311
 7312    cx.set_state(
 7313        r#"            «for selection in selections.iter() {
 7314            let mut start = selection.start;
 7315
 7316            let mut end = selection.end;
 7317            let is_entire_line = selection.is_empty();
 7318            if is_entire_line {
 7319                start = Point::new(start.row, 0);
 7320ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7321            }
 7322        "#,
 7323    );
 7324    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7325    assert_eq!(
 7326        cx.read_from_clipboard()
 7327            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7328        Some(
 7329            "for selection in selections.iter() {
 7330let mut start = selection.start;
 7331
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);
 7336"
 7337            .to_string()
 7338        ),
 7339        "Copying with stripping should ignore empty lines"
 7340    );
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_paste_multiline(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346
 7347    let mut cx = EditorTestContext::new(cx).await;
 7348    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7349
 7350    // Cut an indented block, without the leading whitespace.
 7351    cx.set_state(indoc! {"
 7352        const a: B = (
 7353            c(),
 7354            «d(
 7355                e,
 7356                f
 7357            )ˇ»
 7358        );
 7359    "});
 7360    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7361    cx.assert_editor_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            ˇ
 7365        );
 7366    "});
 7367
 7368    // Paste it at the same position.
 7369    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7370    cx.assert_editor_state(indoc! {"
 7371        const a: B = (
 7372            c(),
 7373            d(
 7374                e,
 7375                f
 7376 7377        );
 7378    "});
 7379
 7380    // Paste it at a line with a lower indent level.
 7381    cx.set_state(indoc! {"
 7382        ˇ
 7383        const a: B = (
 7384            c(),
 7385        );
 7386    "});
 7387    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7388    cx.assert_editor_state(indoc! {"
 7389        d(
 7390            e,
 7391            f
 7392 7393        const a: B = (
 7394            c(),
 7395        );
 7396    "});
 7397
 7398    // Cut an indented block, with the leading whitespace.
 7399    cx.set_state(indoc! {"
 7400        const a: B = (
 7401            c(),
 7402        «    d(
 7403                e,
 7404                f
 7405            )
 7406        ˇ»);
 7407    "});
 7408    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7409    cx.assert_editor_state(indoc! {"
 7410        const a: B = (
 7411            c(),
 7412        ˇ);
 7413    "});
 7414
 7415    // Paste it at the same position.
 7416    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7417    cx.assert_editor_state(indoc! {"
 7418        const a: B = (
 7419            c(),
 7420            d(
 7421                e,
 7422                f
 7423            )
 7424        ˇ);
 7425    "});
 7426
 7427    // Paste it at a line with a higher indent level.
 7428    cx.set_state(indoc! {"
 7429        const a: B = (
 7430            c(),
 7431            d(
 7432                e,
 7433 7434            )
 7435        );
 7436    "});
 7437    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7438    cx.assert_editor_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            d(
 7442                e,
 7443                f    d(
 7444                    e,
 7445                    f
 7446                )
 7447        ˇ
 7448            )
 7449        );
 7450    "});
 7451
 7452    // Copy an indented block, starting mid-line
 7453    cx.set_state(indoc! {"
 7454        const a: B = (
 7455            c(),
 7456            somethin«g(
 7457                e,
 7458                f
 7459            )ˇ»
 7460        );
 7461    "});
 7462    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7463
 7464    // Paste it on a line with a lower indent level
 7465    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7466    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7467    cx.assert_editor_state(indoc! {"
 7468        const a: B = (
 7469            c(),
 7470            something(
 7471                e,
 7472                f
 7473            )
 7474        );
 7475        g(
 7476            e,
 7477            f
 7478"});
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    cx.write_to_clipboard(ClipboardItem::new_string(
 7486        "    d(\n        e\n    );\n".into(),
 7487    ));
 7488
 7489    let mut cx = EditorTestContext::new(cx).await;
 7490    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7491
 7492    cx.set_state(indoc! {"
 7493        fn a() {
 7494            b();
 7495            if c() {
 7496                ˇ
 7497            }
 7498        }
 7499    "});
 7500
 7501    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7502    cx.assert_editor_state(indoc! {"
 7503        fn a() {
 7504            b();
 7505            if c() {
 7506                d(
 7507                    e
 7508                );
 7509        ˇ
 7510            }
 7511        }
 7512    "});
 7513
 7514    cx.set_state(indoc! {"
 7515        fn a() {
 7516            b();
 7517            ˇ
 7518        }
 7519    "});
 7520
 7521    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7522    cx.assert_editor_state(indoc! {"
 7523        fn a() {
 7524            b();
 7525            d(
 7526                e
 7527            );
 7528        ˇ
 7529        }
 7530    "});
 7531}
 7532
 7533#[gpui::test]
 7534fn test_select_all(cx: &mut TestAppContext) {
 7535    init_test(cx, |_| {});
 7536
 7537    let editor = cx.add_window(|window, cx| {
 7538        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7539        build_editor(buffer, window, cx)
 7540    });
 7541    _ = editor.update(cx, |editor, window, cx| {
 7542        editor.select_all(&SelectAll, window, cx);
 7543        assert_eq!(
 7544            editor.selections.display_ranges(cx),
 7545            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7546        );
 7547    });
 7548}
 7549
 7550#[gpui::test]
 7551fn test_select_line(cx: &mut TestAppContext) {
 7552    init_test(cx, |_| {});
 7553
 7554    let editor = cx.add_window(|window, cx| {
 7555        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7556        build_editor(buffer, window, cx)
 7557    });
 7558    _ = editor.update(cx, |editor, window, cx| {
 7559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7560            s.select_display_ranges([
 7561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7563                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7564                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7565            ])
 7566        });
 7567        editor.select_line(&SelectLine, window, cx);
 7568        assert_eq!(
 7569            editor.selections.display_ranges(cx),
 7570            vec![
 7571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7572                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7573            ]
 7574        );
 7575    });
 7576
 7577    _ = editor.update(cx, |editor, window, cx| {
 7578        editor.select_line(&SelectLine, window, cx);
 7579        assert_eq!(
 7580            editor.selections.display_ranges(cx),
 7581            vec![
 7582                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7583                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7584            ]
 7585        );
 7586    });
 7587
 7588    _ = editor.update(cx, |editor, window, cx| {
 7589        editor.select_line(&SelectLine, window, cx);
 7590        assert_eq!(
 7591            editor.selections.display_ranges(cx),
 7592            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7593        );
 7594    });
 7595}
 7596
 7597#[gpui::test]
 7598async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7599    init_test(cx, |_| {});
 7600    let mut cx = EditorTestContext::new(cx).await;
 7601
 7602    #[track_caller]
 7603    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7604        cx.set_state(initial_state);
 7605        cx.update_editor(|e, window, cx| {
 7606            e.split_selection_into_lines(&Default::default(), window, cx)
 7607        });
 7608        cx.assert_editor_state(expected_state);
 7609    }
 7610
 7611    // Selection starts and ends at the middle of lines, left-to-right
 7612    test(
 7613        &mut cx,
 7614        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7615        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7616    );
 7617    // Same thing, right-to-left
 7618    test(
 7619        &mut cx,
 7620        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7621        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7622    );
 7623
 7624    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7625    test(
 7626        &mut cx,
 7627        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7628        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7629    );
 7630    // Same thing, right-to-left
 7631    test(
 7632        &mut cx,
 7633        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7634        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7635    );
 7636
 7637    // Whole buffer, left-to-right, last line ends with newline
 7638    test(
 7639        &mut cx,
 7640        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7641        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7642    );
 7643    // Same thing, right-to-left
 7644    test(
 7645        &mut cx,
 7646        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7647        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7648    );
 7649
 7650    // Starts at the end of a line, ends at the start of another
 7651    test(
 7652        &mut cx,
 7653        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7654        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7655    );
 7656}
 7657
 7658#[gpui::test]
 7659async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7660    init_test(cx, |_| {});
 7661
 7662    let editor = cx.add_window(|window, cx| {
 7663        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7664        build_editor(buffer, window, cx)
 7665    });
 7666
 7667    // setup
 7668    _ = editor.update(cx, |editor, window, cx| {
 7669        editor.fold_creases(
 7670            vec![
 7671                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7672                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7673                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7674            ],
 7675            true,
 7676            window,
 7677            cx,
 7678        );
 7679        assert_eq!(
 7680            editor.display_text(cx),
 7681            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7682        );
 7683    });
 7684
 7685    _ = editor.update(cx, |editor, window, cx| {
 7686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7687            s.select_display_ranges([
 7688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7689                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7691                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7692            ])
 7693        });
 7694        editor.split_selection_into_lines(&Default::default(), window, cx);
 7695        assert_eq!(
 7696            editor.display_text(cx),
 7697            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7698        );
 7699    });
 7700    EditorTestContext::for_editor(editor, cx)
 7701        .await
 7702        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7703
 7704    _ = editor.update(cx, |editor, window, cx| {
 7705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7706            s.select_display_ranges([
 7707                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7708            ])
 7709        });
 7710        editor.split_selection_into_lines(&Default::default(), window, cx);
 7711        assert_eq!(
 7712            editor.display_text(cx),
 7713            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7714        );
 7715        assert_eq!(
 7716            editor.selections.display_ranges(cx),
 7717            [
 7718                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7719                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7720                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7721                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7722                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7723                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7724                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7725            ]
 7726        );
 7727    });
 7728    EditorTestContext::for_editor(editor, cx)
 7729        .await
 7730        .assert_editor_state(
 7731            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7732        );
 7733}
 7734
 7735#[gpui::test]
 7736async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7737    init_test(cx, |_| {});
 7738
 7739    let mut cx = EditorTestContext::new(cx).await;
 7740
 7741    cx.set_state(indoc!(
 7742        r#"abc
 7743           defˇghi
 7744
 7745           jk
 7746           nlmo
 7747           "#
 7748    ));
 7749
 7750    cx.update_editor(|editor, window, cx| {
 7751        editor.add_selection_above(&Default::default(), window, cx);
 7752    });
 7753
 7754    cx.assert_editor_state(indoc!(
 7755        r#"abcˇ
 7756           defˇghi
 7757
 7758           jk
 7759           nlmo
 7760           "#
 7761    ));
 7762
 7763    cx.update_editor(|editor, window, cx| {
 7764        editor.add_selection_above(&Default::default(), window, cx);
 7765    });
 7766
 7767    cx.assert_editor_state(indoc!(
 7768        r#"abcˇ
 7769            defˇghi
 7770
 7771            jk
 7772            nlmo
 7773            "#
 7774    ));
 7775
 7776    cx.update_editor(|editor, window, cx| {
 7777        editor.add_selection_below(&Default::default(), window, cx);
 7778    });
 7779
 7780    cx.assert_editor_state(indoc!(
 7781        r#"abc
 7782           defˇghi
 7783
 7784           jk
 7785           nlmo
 7786           "#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.undo_selection(&Default::default(), window, cx);
 7791    });
 7792
 7793    cx.assert_editor_state(indoc!(
 7794        r#"abcˇ
 7795           defˇghi
 7796
 7797           jk
 7798           nlmo
 7799           "#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.redo_selection(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.assert_editor_state(indoc!(
 7807        r#"abc
 7808           defˇghi
 7809
 7810           jk
 7811           nlmo
 7812           "#
 7813    ));
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_below(&Default::default(), window, cx);
 7817    });
 7818
 7819    cx.assert_editor_state(indoc!(
 7820        r#"abc
 7821           defˇghi
 7822           ˇ
 7823           jk
 7824           nlmo
 7825           "#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    cx.assert_editor_state(indoc!(
 7833        r#"abc
 7834           defˇghi
 7835           ˇ
 7836           jkˇ
 7837           nlmo
 7838           "#
 7839    ));
 7840
 7841    cx.update_editor(|editor, window, cx| {
 7842        editor.add_selection_below(&Default::default(), window, cx);
 7843    });
 7844
 7845    cx.assert_editor_state(indoc!(
 7846        r#"abc
 7847           defˇghi
 7848           ˇ
 7849           jkˇ
 7850           nlmˇo
 7851           "#
 7852    ));
 7853
 7854    cx.update_editor(|editor, window, cx| {
 7855        editor.add_selection_below(&Default::default(), window, cx);
 7856    });
 7857
 7858    cx.assert_editor_state(indoc!(
 7859        r#"abc
 7860           defˇghi
 7861           ˇ
 7862           jkˇ
 7863           nlmˇo
 7864           ˇ"#
 7865    ));
 7866
 7867    // change selections
 7868    cx.set_state(indoc!(
 7869        r#"abc
 7870           def«ˇg»hi
 7871
 7872           jk
 7873           nlmo
 7874           "#
 7875    ));
 7876
 7877    cx.update_editor(|editor, window, cx| {
 7878        editor.add_selection_below(&Default::default(), window, cx);
 7879    });
 7880
 7881    cx.assert_editor_state(indoc!(
 7882        r#"abc
 7883           def«ˇg»hi
 7884
 7885           jk
 7886           nlm«ˇo»
 7887           "#
 7888    ));
 7889
 7890    cx.update_editor(|editor, window, cx| {
 7891        editor.add_selection_below(&Default::default(), window, cx);
 7892    });
 7893
 7894    cx.assert_editor_state(indoc!(
 7895        r#"abc
 7896           def«ˇg»hi
 7897
 7898           jk
 7899           nlm«ˇo»
 7900           "#
 7901    ));
 7902
 7903    cx.update_editor(|editor, window, cx| {
 7904        editor.add_selection_above(&Default::default(), window, cx);
 7905    });
 7906
 7907    cx.assert_editor_state(indoc!(
 7908        r#"abc
 7909           def«ˇg»hi
 7910
 7911           jk
 7912           nlmo
 7913           "#
 7914    ));
 7915
 7916    cx.update_editor(|editor, window, cx| {
 7917        editor.add_selection_above(&Default::default(), window, cx);
 7918    });
 7919
 7920    cx.assert_editor_state(indoc!(
 7921        r#"abc
 7922           def«ˇg»hi
 7923
 7924           jk
 7925           nlmo
 7926           "#
 7927    ));
 7928
 7929    // Change selections again
 7930    cx.set_state(indoc!(
 7931        r#"a«bc
 7932           defgˇ»hi
 7933
 7934           jk
 7935           nlmo
 7936           "#
 7937    ));
 7938
 7939    cx.update_editor(|editor, window, cx| {
 7940        editor.add_selection_below(&Default::default(), window, cx);
 7941    });
 7942
 7943    cx.assert_editor_state(indoc!(
 7944        r#"a«bcˇ»
 7945           d«efgˇ»hi
 7946
 7947           j«kˇ»
 7948           nlmo
 7949           "#
 7950    ));
 7951
 7952    cx.update_editor(|editor, window, cx| {
 7953        editor.add_selection_below(&Default::default(), window, cx);
 7954    });
 7955    cx.assert_editor_state(indoc!(
 7956        r#"a«bcˇ»
 7957           d«efgˇ»hi
 7958
 7959           j«kˇ»
 7960           n«lmoˇ»
 7961           "#
 7962    ));
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.add_selection_above(&Default::default(), window, cx);
 7965    });
 7966
 7967    cx.assert_editor_state(indoc!(
 7968        r#"a«bcˇ»
 7969           d«efgˇ»hi
 7970
 7971           j«kˇ»
 7972           nlmo
 7973           "#
 7974    ));
 7975
 7976    // Change selections again
 7977    cx.set_state(indoc!(
 7978        r#"abc
 7979           d«ˇefghi
 7980
 7981           jk
 7982           nlm»o
 7983           "#
 7984    ));
 7985
 7986    cx.update_editor(|editor, window, cx| {
 7987        editor.add_selection_above(&Default::default(), window, cx);
 7988    });
 7989
 7990    cx.assert_editor_state(indoc!(
 7991        r#"a«ˇbc»
 7992           d«ˇef»ghi
 7993
 7994           j«ˇk»
 7995           n«ˇlm»o
 7996           "#
 7997    ));
 7998
 7999    cx.update_editor(|editor, window, cx| {
 8000        editor.add_selection_below(&Default::default(), window, cx);
 8001    });
 8002
 8003    cx.assert_editor_state(indoc!(
 8004        r#"abc
 8005           d«ˇef»ghi
 8006
 8007           j«ˇk»
 8008           n«ˇlm»o
 8009           "#
 8010    ));
 8011}
 8012
 8013#[gpui::test]
 8014async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8015    init_test(cx, |_| {});
 8016    let mut cx = EditorTestContext::new(cx).await;
 8017
 8018    cx.set_state(indoc!(
 8019        r#"line onˇe
 8020           liˇne two
 8021           line three
 8022           line four"#
 8023    ));
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.add_selection_below(&Default::default(), window, cx);
 8027    });
 8028
 8029    // test multiple cursors expand in the same direction
 8030    cx.assert_editor_state(indoc!(
 8031        r#"line onˇe
 8032           liˇne twˇo
 8033           liˇne three
 8034           line four"#
 8035    ));
 8036
 8037    cx.update_editor(|editor, window, cx| {
 8038        editor.add_selection_below(&Default::default(), window, cx);
 8039    });
 8040
 8041    cx.update_editor(|editor, window, cx| {
 8042        editor.add_selection_below(&Default::default(), window, cx);
 8043    });
 8044
 8045    // test multiple cursors expand below overflow
 8046    cx.assert_editor_state(indoc!(
 8047        r#"line onˇe
 8048           liˇne twˇo
 8049           liˇne thˇree
 8050           liˇne foˇur"#
 8051    ));
 8052
 8053    cx.update_editor(|editor, window, cx| {
 8054        editor.add_selection_above(&Default::default(), window, cx);
 8055    });
 8056
 8057    // test multiple cursors retrieves back correctly
 8058    cx.assert_editor_state(indoc!(
 8059        r#"line onˇe
 8060           liˇne twˇo
 8061           liˇne thˇree
 8062           line four"#
 8063    ));
 8064
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.add_selection_above(&Default::default(), window, cx);
 8067    });
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.add_selection_above(&Default::default(), window, cx);
 8071    });
 8072
 8073    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8074    cx.assert_editor_state(indoc!(
 8075        r#"liˇne onˇe
 8076           liˇne two
 8077           line three
 8078           line four"#
 8079    ));
 8080
 8081    cx.update_editor(|editor, window, cx| {
 8082        editor.undo_selection(&Default::default(), window, cx);
 8083    });
 8084
 8085    // test undo
 8086    cx.assert_editor_state(indoc!(
 8087        r#"line onˇe
 8088           liˇne twˇo
 8089           line three
 8090           line four"#
 8091    ));
 8092
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.redo_selection(&Default::default(), window, cx);
 8095    });
 8096
 8097    // test redo
 8098    cx.assert_editor_state(indoc!(
 8099        r#"liˇne onˇe
 8100           liˇne two
 8101           line three
 8102           line four"#
 8103    ));
 8104
 8105    cx.set_state(indoc!(
 8106        r#"abcd
 8107           ef«ghˇ»
 8108           ijkl
 8109           «mˇ»nop"#
 8110    ));
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_above(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple selections expand in the same direction
 8117    cx.assert_editor_state(indoc!(
 8118        r#"ab«cdˇ»
 8119           ef«ghˇ»
 8120           «iˇ»jkl
 8121           «mˇ»nop"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple selection upward overflow
 8129    cx.assert_editor_state(indoc!(
 8130        r#"ab«cdˇ»
 8131           «eˇ»f«ghˇ»
 8132           «iˇ»jkl
 8133           «mˇ»nop"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_below(&Default::default(), window, cx);
 8138    });
 8139
 8140    // test multiple selection retrieves back correctly
 8141    cx.assert_editor_state(indoc!(
 8142        r#"abcd
 8143           ef«ghˇ»
 8144           «iˇ»jkl
 8145           «mˇ»nop"#
 8146    ));
 8147
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_below(&Default::default(), window, cx);
 8150    });
 8151
 8152    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8153    cx.assert_editor_state(indoc!(
 8154        r#"abcd
 8155           ef«ghˇ»
 8156           ij«klˇ»
 8157           «mˇ»nop"#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    // test undo
 8165    cx.assert_editor_state(indoc!(
 8166        r#"abcd
 8167           ef«ghˇ»
 8168           «iˇ»jkl
 8169           «mˇ»nop"#
 8170    ));
 8171
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.redo_selection(&Default::default(), window, cx);
 8174    });
 8175
 8176    // test redo
 8177    cx.assert_editor_state(indoc!(
 8178        r#"abcd
 8179           ef«ghˇ»
 8180           ij«klˇ»
 8181           «mˇ»nop"#
 8182    ));
 8183}
 8184
 8185#[gpui::test]
 8186async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8187    init_test(cx, |_| {});
 8188    let mut cx = EditorTestContext::new(cx).await;
 8189
 8190    cx.set_state(indoc!(
 8191        r#"line onˇe
 8192           liˇne two
 8193           line three
 8194           line four"#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199        editor.add_selection_below(&Default::default(), window, cx);
 8200        editor.add_selection_below(&Default::default(), window, cx);
 8201    });
 8202
 8203    // initial state with two multi cursor groups
 8204    cx.assert_editor_state(indoc!(
 8205        r#"line onˇe
 8206           liˇne twˇo
 8207           liˇne thˇree
 8208           liˇne foˇur"#
 8209    ));
 8210
 8211    // add single cursor in middle - simulate opt click
 8212    cx.update_editor(|editor, window, cx| {
 8213        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8214        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8215        editor.end_selection(window, cx);
 8216    });
 8217
 8218    cx.assert_editor_state(indoc!(
 8219        r#"line onˇe
 8220           liˇne twˇo
 8221           liˇneˇ thˇree
 8222           liˇne foˇur"#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_above(&Default::default(), window, cx);
 8227    });
 8228
 8229    // test new added selection expands above and existing selection shrinks
 8230    cx.assert_editor_state(indoc!(
 8231        r#"line onˇe
 8232           liˇneˇ twˇo
 8233           liˇneˇ thˇree
 8234           line four"#
 8235    ));
 8236
 8237    cx.update_editor(|editor, window, cx| {
 8238        editor.add_selection_above(&Default::default(), window, cx);
 8239    });
 8240
 8241    // test new added selection expands above and existing selection shrinks
 8242    cx.assert_editor_state(indoc!(
 8243        r#"lineˇ onˇe
 8244           liˇneˇ twˇo
 8245           lineˇ three
 8246           line four"#
 8247    ));
 8248
 8249    // intial state with two selection groups
 8250    cx.set_state(indoc!(
 8251        r#"abcd
 8252           ef«ghˇ»
 8253           ijkl
 8254           «mˇ»nop"#
 8255    ));
 8256
 8257    cx.update_editor(|editor, window, cx| {
 8258        editor.add_selection_above(&Default::default(), window, cx);
 8259        editor.add_selection_above(&Default::default(), window, cx);
 8260    });
 8261
 8262    cx.assert_editor_state(indoc!(
 8263        r#"ab«cdˇ»
 8264           «eˇ»f«ghˇ»
 8265           «iˇ»jkl
 8266           «mˇ»nop"#
 8267    ));
 8268
 8269    // add single selection in middle - simulate opt drag
 8270    cx.update_editor(|editor, window, cx| {
 8271        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8272        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8273        editor.update_selection(
 8274            DisplayPoint::new(DisplayRow(2), 4),
 8275            0,
 8276            gpui::Point::<f32>::default(),
 8277            window,
 8278            cx,
 8279        );
 8280        editor.end_selection(window, cx);
 8281    });
 8282
 8283    cx.assert_editor_state(indoc!(
 8284        r#"ab«cdˇ»
 8285           «eˇ»f«ghˇ»
 8286           «iˇ»jk«lˇ»
 8287           «mˇ»nop"#
 8288    ));
 8289
 8290    cx.update_editor(|editor, window, cx| {
 8291        editor.add_selection_below(&Default::default(), window, cx);
 8292    });
 8293
 8294    // test new added selection expands below, others shrinks from above
 8295    cx.assert_editor_state(indoc!(
 8296        r#"abcd
 8297           ef«ghˇ»
 8298           «iˇ»jk«lˇ»
 8299           «mˇ»no«pˇ»"#
 8300    ));
 8301}
 8302
 8303#[gpui::test]
 8304async fn test_select_next(cx: &mut TestAppContext) {
 8305    init_test(cx, |_| {});
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8309
 8310    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8311        .unwrap();
 8312    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8313
 8314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8315        .unwrap();
 8316    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8317
 8318    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8319    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8320
 8321    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8322    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8323
 8324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8325        .unwrap();
 8326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8327
 8328    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8329        .unwrap();
 8330    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8331
 8332    // Test selection direction should be preserved
 8333    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8334
 8335    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8336        .unwrap();
 8337    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8338}
 8339
 8340#[gpui::test]
 8341async fn test_select_all_matches(cx: &mut TestAppContext) {
 8342    init_test(cx, |_| {});
 8343
 8344    let mut cx = EditorTestContext::new(cx).await;
 8345
 8346    // Test caret-only selections
 8347    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8348    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8349        .unwrap();
 8350    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8351
 8352    // Test left-to-right selections
 8353    cx.set_state("abc\n«abcˇ»\nabc");
 8354    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8355        .unwrap();
 8356    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8357
 8358    // Test right-to-left selections
 8359    cx.set_state("abc\n«ˇabc»\nabc");
 8360    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8361        .unwrap();
 8362    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8363
 8364    // Test selecting whitespace with caret selection
 8365    cx.set_state("abc\nˇ   abc\nabc");
 8366    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8367        .unwrap();
 8368    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8369
 8370    // Test selecting whitespace with left-to-right selection
 8371    cx.set_state("abc\n«ˇ  »abc\nabc");
 8372    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8373        .unwrap();
 8374    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8375
 8376    // Test no matches with right-to-left selection
 8377    cx.set_state("abc\n«  ˇ»abc\nabc");
 8378    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8379        .unwrap();
 8380    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8381
 8382    // Test with a single word and clip_at_line_ends=true (#29823)
 8383    cx.set_state("aˇbc");
 8384    cx.update_editor(|e, window, cx| {
 8385        e.set_clip_at_line_ends(true, cx);
 8386        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8387        e.set_clip_at_line_ends(false, cx);
 8388    });
 8389    cx.assert_editor_state("«abcˇ»");
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx = EditorTestContext::new(cx).await;
 8397
 8398    let large_body_1 = "\nd".repeat(200);
 8399    let large_body_2 = "\ne".repeat(200);
 8400
 8401    cx.set_state(&format!(
 8402        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8403    ));
 8404    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8405        let scroll_position = editor.scroll_position(cx);
 8406        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8407        scroll_position
 8408    });
 8409
 8410    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8411        .unwrap();
 8412    cx.assert_editor_state(&format!(
 8413        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8414    ));
 8415    let scroll_position_after_selection =
 8416        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8417    assert_eq!(
 8418        initial_scroll_position, scroll_position_after_selection,
 8419        "Scroll position should not change after selecting all matches"
 8420    );
 8421}
 8422
 8423#[gpui::test]
 8424async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8425    init_test(cx, |_| {});
 8426
 8427    let mut cx = EditorLspTestContext::new_rust(
 8428        lsp::ServerCapabilities {
 8429            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8430            ..Default::default()
 8431        },
 8432        cx,
 8433    )
 8434    .await;
 8435
 8436    cx.set_state(indoc! {"
 8437        line 1
 8438        line 2
 8439        linˇe 3
 8440        line 4
 8441        line 5
 8442    "});
 8443
 8444    // Make an edit
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.handle_input("X", window, cx);
 8447    });
 8448
 8449    // Move cursor to a different position
 8450    cx.update_editor(|editor, window, cx| {
 8451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8452            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8453        });
 8454    });
 8455
 8456    cx.assert_editor_state(indoc! {"
 8457        line 1
 8458        line 2
 8459        linXe 3
 8460        line 4
 8461        liˇne 5
 8462    "});
 8463
 8464    cx.lsp
 8465        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8466            Ok(Some(vec![lsp::TextEdit::new(
 8467                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8468                "PREFIX ".to_string(),
 8469            )]))
 8470        });
 8471
 8472    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8473        .unwrap()
 8474        .await
 8475        .unwrap();
 8476
 8477    cx.assert_editor_state(indoc! {"
 8478        PREFIX line 1
 8479        line 2
 8480        linXe 3
 8481        line 4
 8482        liˇne 5
 8483    "});
 8484
 8485    // Undo formatting
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.undo(&Default::default(), window, cx);
 8488    });
 8489
 8490    // Verify cursor moved back to position after edit
 8491    cx.assert_editor_state(indoc! {"
 8492        line 1
 8493        line 2
 8494        linXˇe 3
 8495        line 4
 8496        line 5
 8497    "});
 8498}
 8499
 8500#[gpui::test]
 8501async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8502    init_test(cx, |_| {});
 8503
 8504    let mut cx = EditorTestContext::new(cx).await;
 8505
 8506    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8507    cx.update_editor(|editor, window, cx| {
 8508        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8509    });
 8510
 8511    cx.set_state(indoc! {"
 8512        line 1
 8513        line 2
 8514        linˇe 3
 8515        line 4
 8516        line 5
 8517        line 6
 8518        line 7
 8519        line 8
 8520        line 9
 8521        line 10
 8522    "});
 8523
 8524    let snapshot = cx.buffer_snapshot();
 8525    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8526
 8527    cx.update(|_, cx| {
 8528        provider.update(cx, |provider, _| {
 8529            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8530                id: None,
 8531                edits: vec![(edit_position..edit_position, "X".into())],
 8532                edit_preview: None,
 8533            }))
 8534        })
 8535    });
 8536
 8537    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8538    cx.update_editor(|editor, window, cx| {
 8539        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8540    });
 8541
 8542    cx.assert_editor_state(indoc! {"
 8543        line 1
 8544        line 2
 8545        lineXˇ 3
 8546        line 4
 8547        line 5
 8548        line 6
 8549        line 7
 8550        line 8
 8551        line 9
 8552        line 10
 8553    "});
 8554
 8555    cx.update_editor(|editor, window, cx| {
 8556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8557            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8558        });
 8559    });
 8560
 8561    cx.assert_editor_state(indoc! {"
 8562        line 1
 8563        line 2
 8564        lineX 3
 8565        line 4
 8566        line 5
 8567        line 6
 8568        line 7
 8569        line 8
 8570        line 9
 8571        liˇne 10
 8572    "});
 8573
 8574    cx.update_editor(|editor, window, cx| {
 8575        editor.undo(&Default::default(), window, cx);
 8576    });
 8577
 8578    cx.assert_editor_state(indoc! {"
 8579        line 1
 8580        line 2
 8581        lineˇ 3
 8582        line 4
 8583        line 5
 8584        line 6
 8585        line 7
 8586        line 8
 8587        line 9
 8588        line 10
 8589    "});
 8590}
 8591
 8592#[gpui::test]
 8593async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let mut cx = EditorTestContext::new(cx).await;
 8597    cx.set_state(
 8598        r#"let foo = 2;
 8599lˇet foo = 2;
 8600let fooˇ = 2;
 8601let foo = 2;
 8602let foo = ˇ2;"#,
 8603    );
 8604
 8605    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8606        .unwrap();
 8607    cx.assert_editor_state(
 8608        r#"let foo = 2;
 8609«letˇ» foo = 2;
 8610let «fooˇ» = 2;
 8611let foo = 2;
 8612let foo = «2ˇ»;"#,
 8613    );
 8614
 8615    // noop for multiple selections with different contents
 8616    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8617        .unwrap();
 8618    cx.assert_editor_state(
 8619        r#"let foo = 2;
 8620«letˇ» foo = 2;
 8621let «fooˇ» = 2;
 8622let foo = 2;
 8623let foo = «2ˇ»;"#,
 8624    );
 8625
 8626    // Test last selection direction should be preserved
 8627    cx.set_state(
 8628        r#"let foo = 2;
 8629let foo = 2;
 8630let «fooˇ» = 2;
 8631let «ˇfoo» = 2;
 8632let foo = 2;"#,
 8633    );
 8634
 8635    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8636        .unwrap();
 8637    cx.assert_editor_state(
 8638        r#"let foo = 2;
 8639let foo = 2;
 8640let «fooˇ» = 2;
 8641let «ˇfoo» = 2;
 8642let «ˇfoo» = 2;"#,
 8643    );
 8644}
 8645
 8646#[gpui::test]
 8647async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8648    init_test(cx, |_| {});
 8649
 8650    let mut cx =
 8651        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8652
 8653    cx.assert_editor_state(indoc! {"
 8654        ˇbbb
 8655        ccc
 8656
 8657        bbb
 8658        ccc
 8659        "});
 8660    cx.dispatch_action(SelectPrevious::default());
 8661    cx.assert_editor_state(indoc! {"
 8662                «bbbˇ»
 8663                ccc
 8664
 8665                bbb
 8666                ccc
 8667                "});
 8668    cx.dispatch_action(SelectPrevious::default());
 8669    cx.assert_editor_state(indoc! {"
 8670                «bbbˇ»
 8671                ccc
 8672
 8673                «bbbˇ»
 8674                ccc
 8675                "});
 8676}
 8677
 8678#[gpui::test]
 8679async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8680    init_test(cx, |_| {});
 8681
 8682    let mut cx = EditorTestContext::new(cx).await;
 8683    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8684
 8685    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8686        .unwrap();
 8687    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8688
 8689    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8690        .unwrap();
 8691    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8692
 8693    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8694    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8695
 8696    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8697    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8698
 8699    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8700        .unwrap();
 8701    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8702
 8703    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8704        .unwrap();
 8705    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8706}
 8707
 8708#[gpui::test]
 8709async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8710    init_test(cx, |_| {});
 8711
 8712    let mut cx = EditorTestContext::new(cx).await;
 8713    cx.set_state("");
 8714
 8715    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8716        .unwrap();
 8717    cx.assert_editor_state("«aˇ»");
 8718    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8719        .unwrap();
 8720    cx.assert_editor_state("«aˇ»");
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728    cx.set_state(
 8729        r#"let foo = 2;
 8730lˇet foo = 2;
 8731let fooˇ = 2;
 8732let foo = 2;
 8733let foo = ˇ2;"#,
 8734    );
 8735
 8736    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8737        .unwrap();
 8738    cx.assert_editor_state(
 8739        r#"let foo = 2;
 8740«letˇ» foo = 2;
 8741let «fooˇ» = 2;
 8742let foo = 2;
 8743let foo = «2ˇ»;"#,
 8744    );
 8745
 8746    // noop for multiple selections with different contents
 8747    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8748        .unwrap();
 8749    cx.assert_editor_state(
 8750        r#"let foo = 2;
 8751«letˇ» foo = 2;
 8752let «fooˇ» = 2;
 8753let foo = 2;
 8754let foo = «2ˇ»;"#,
 8755    );
 8756}
 8757
 8758#[gpui::test]
 8759async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8760    init_test(cx, |_| {});
 8761
 8762    let mut cx = EditorTestContext::new(cx).await;
 8763    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8764
 8765    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8766        .unwrap();
 8767    // selection direction is preserved
 8768    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8773
 8774    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8775    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8776
 8777    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8778    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8783
 8784    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8785        .unwrap();
 8786    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8787}
 8788
 8789#[gpui::test]
 8790async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8791    init_test(cx, |_| {});
 8792
 8793    let language = Arc::new(Language::new(
 8794        LanguageConfig::default(),
 8795        Some(tree_sitter_rust::LANGUAGE.into()),
 8796    ));
 8797
 8798    let text = r#"
 8799        use mod1::mod2::{mod3, mod4};
 8800
 8801        fn fn_1(param1: bool, param2: &str) {
 8802            let var1 = "text";
 8803        }
 8804    "#
 8805    .unindent();
 8806
 8807    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8809    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8810
 8811    editor
 8812        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8813        .await;
 8814
 8815    editor.update_in(cx, |editor, window, cx| {
 8816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8817            s.select_display_ranges([
 8818                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8819                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8820                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8821            ]);
 8822        });
 8823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8824    });
 8825    editor.update(cx, |editor, cx| {
 8826        assert_text_with_selections(
 8827            editor,
 8828            indoc! {r#"
 8829                use mod1::mod2::{mod3, «mod4ˇ»};
 8830
 8831                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8832                    let var1 = "«ˇtext»";
 8833                }
 8834            "#},
 8835            cx,
 8836        );
 8837    });
 8838
 8839    editor.update_in(cx, |editor, window, cx| {
 8840        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8841    });
 8842    editor.update(cx, |editor, cx| {
 8843        assert_text_with_selections(
 8844            editor,
 8845            indoc! {r#"
 8846                use mod1::mod2::«{mod3, mod4}ˇ»;
 8847
 8848                «ˇfn fn_1(param1: bool, param2: &str) {
 8849                    let var1 = "text";
 8850 8851            "#},
 8852            cx,
 8853        );
 8854    });
 8855
 8856    editor.update_in(cx, |editor, window, cx| {
 8857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8858    });
 8859    assert_eq!(
 8860        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8861        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8862    );
 8863
 8864    // Trying to expand the selected syntax node one more time has no effect.
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    assert_eq!(
 8869        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8870        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8871    );
 8872
 8873    editor.update_in(cx, |editor, window, cx| {
 8874        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8875    });
 8876    editor.update(cx, |editor, cx| {
 8877        assert_text_with_selections(
 8878            editor,
 8879            indoc! {r#"
 8880                use mod1::mod2::«{mod3, mod4}ˇ»;
 8881
 8882                «ˇfn fn_1(param1: bool, param2: &str) {
 8883                    let var1 = "text";
 8884 8885            "#},
 8886            cx,
 8887        );
 8888    });
 8889
 8890    editor.update_in(cx, |editor, window, cx| {
 8891        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8892    });
 8893    editor.update(cx, |editor, cx| {
 8894        assert_text_with_selections(
 8895            editor,
 8896            indoc! {r#"
 8897                use mod1::mod2::{mod3, «mod4ˇ»};
 8898
 8899                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8900                    let var1 = "«ˇtext»";
 8901                }
 8902            "#},
 8903            cx,
 8904        );
 8905    });
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8909    });
 8910    editor.update(cx, |editor, cx| {
 8911        assert_text_with_selections(
 8912            editor,
 8913            indoc! {r#"
 8914                use mod1::mod2::{mod3, moˇd4};
 8915
 8916                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8917                    let var1 = "teˇxt";
 8918                }
 8919            "#},
 8920            cx,
 8921        );
 8922    });
 8923
 8924    // Trying to shrink the selected syntax node one more time has no effect.
 8925    editor.update_in(cx, |editor, window, cx| {
 8926        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8927    });
 8928    editor.update_in(cx, |editor, _, cx| {
 8929        assert_text_with_selections(
 8930            editor,
 8931            indoc! {r#"
 8932                use mod1::mod2::{mod3, moˇd4};
 8933
 8934                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8935                    let var1 = "teˇxt";
 8936                }
 8937            "#},
 8938            cx,
 8939        );
 8940    });
 8941
 8942    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8943    // a fold.
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.fold_creases(
 8946            vec![
 8947                Crease::simple(
 8948                    Point::new(0, 21)..Point::new(0, 24),
 8949                    FoldPlaceholder::test(),
 8950                ),
 8951                Crease::simple(
 8952                    Point::new(3, 20)..Point::new(3, 22),
 8953                    FoldPlaceholder::test(),
 8954                ),
 8955            ],
 8956            true,
 8957            window,
 8958            cx,
 8959        );
 8960        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8961    });
 8962    editor.update(cx, |editor, cx| {
 8963        assert_text_with_selections(
 8964            editor,
 8965            indoc! {r#"
 8966                use mod1::mod2::«{mod3, mod4}ˇ»;
 8967
 8968                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8969                    let var1 = "«ˇtext»";
 8970                }
 8971            "#},
 8972            cx,
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig::default(),
 8983        Some(tree_sitter_rust::LANGUAGE.into()),
 8984    ));
 8985
 8986    let text = "let a = 2;";
 8987
 8988    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8991
 8992    editor
 8993        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8994        .await;
 8995
 8996    // Test case 1: Cursor at end of word
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8999            s.select_display_ranges([
 9000                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9001            ]);
 9002        });
 9003    });
 9004    editor.update(cx, |editor, cx| {
 9005        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9006    });
 9007    editor.update_in(cx, |editor, window, cx| {
 9008        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9009    });
 9010    editor.update(cx, |editor, cx| {
 9011        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9012    });
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9015    });
 9016    editor.update(cx, |editor, cx| {
 9017        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9018    });
 9019
 9020    // Test case 2: Cursor at end of statement
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9023            s.select_display_ranges([
 9024                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9025            ]);
 9026        });
 9027    });
 9028    editor.update(cx, |editor, cx| {
 9029        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9033    });
 9034    editor.update(cx, |editor, cx| {
 9035        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9036    });
 9037}
 9038
 9039#[gpui::test]
 9040async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9041    init_test(cx, |_| {});
 9042
 9043    let language = Arc::new(Language::new(
 9044        LanguageConfig {
 9045            name: "JavaScript".into(),
 9046            ..Default::default()
 9047        },
 9048        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9049    ));
 9050
 9051    let text = r#"
 9052        let a = {
 9053            key: "value",
 9054        };
 9055    "#
 9056    .unindent();
 9057
 9058    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9059    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9061
 9062    editor
 9063        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9064        .await;
 9065
 9066    // Test case 1: Cursor after '{'
 9067    editor.update_in(cx, |editor, window, cx| {
 9068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9069            s.select_display_ranges([
 9070                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9071            ]);
 9072        });
 9073    });
 9074    editor.update(cx, |editor, cx| {
 9075        assert_text_with_selections(
 9076            editor,
 9077            indoc! {r#"
 9078                let a = {ˇ
 9079                    key: "value",
 9080                };
 9081            "#},
 9082            cx,
 9083        );
 9084    });
 9085    editor.update_in(cx, |editor, window, cx| {
 9086        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9087    });
 9088    editor.update(cx, |editor, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                let a = «ˇ{
 9093                    key: "value",
 9094                }»;
 9095            "#},
 9096            cx,
 9097        );
 9098    });
 9099
 9100    // Test case 2: Cursor after ':'
 9101    editor.update_in(cx, |editor, window, cx| {
 9102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9103            s.select_display_ranges([
 9104                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9105            ]);
 9106        });
 9107    });
 9108    editor.update(cx, |editor, cx| {
 9109        assert_text_with_selections(
 9110            editor,
 9111            indoc! {r#"
 9112                let a = {
 9113                    key:ˇ "value",
 9114                };
 9115            "#},
 9116            cx,
 9117        );
 9118    });
 9119    editor.update_in(cx, |editor, window, cx| {
 9120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                let a = {
 9127                    «ˇkey: "value"»,
 9128                };
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(
 9138            editor,
 9139            indoc! {r#"
 9140                let a = «ˇ{
 9141                    key: "value",
 9142                }»;
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147
 9148    // Test case 3: Cursor after ','
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9151            s.select_display_ranges([
 9152                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9153            ]);
 9154        });
 9155    });
 9156    editor.update(cx, |editor, cx| {
 9157        assert_text_with_selections(
 9158            editor,
 9159            indoc! {r#"
 9160                let a = {
 9161                    key: "value",ˇ
 9162                };
 9163            "#},
 9164            cx,
 9165        );
 9166    });
 9167    editor.update_in(cx, |editor, window, cx| {
 9168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9169    });
 9170    editor.update(cx, |editor, cx| {
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                let a = «ˇ{
 9175                    key: "value",
 9176                }»;
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181
 9182    // Test case 4: Cursor after ';'
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9185            s.select_display_ranges([
 9186                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9187            ]);
 9188        });
 9189    });
 9190    editor.update(cx, |editor, cx| {
 9191        assert_text_with_selections(
 9192            editor,
 9193            indoc! {r#"
 9194                let a = {
 9195                    key: "value",
 9196                };ˇ
 9197            "#},
 9198            cx,
 9199        );
 9200    });
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                «ˇlet a = {
 9209                    key: "value",
 9210                };
 9211                »"#},
 9212            cx,
 9213        );
 9214    });
 9215}
 9216
 9217#[gpui::test]
 9218async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9219    init_test(cx, |_| {});
 9220
 9221    let language = Arc::new(Language::new(
 9222        LanguageConfig::default(),
 9223        Some(tree_sitter_rust::LANGUAGE.into()),
 9224    ));
 9225
 9226    let text = r#"
 9227        use mod1::mod2::{mod3, mod4};
 9228
 9229        fn fn_1(param1: bool, param2: &str) {
 9230            let var1 = "hello world";
 9231        }
 9232    "#
 9233    .unindent();
 9234
 9235    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9236    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9237    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9238
 9239    editor
 9240        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9241        .await;
 9242
 9243    // Test 1: Cursor on a letter of a string word
 9244    editor.update_in(cx, |editor, window, cx| {
 9245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9246            s.select_display_ranges([
 9247                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9248            ]);
 9249        });
 9250    });
 9251    editor.update_in(cx, |editor, window, cx| {
 9252        assert_text_with_selections(
 9253            editor,
 9254            indoc! {r#"
 9255                use mod1::mod2::{mod3, mod4};
 9256
 9257                fn fn_1(param1: bool, param2: &str) {
 9258                    let var1 = "hˇello world";
 9259                }
 9260            "#},
 9261            cx,
 9262        );
 9263        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9264        assert_text_with_selections(
 9265            editor,
 9266            indoc! {r#"
 9267                use mod1::mod2::{mod3, mod4};
 9268
 9269                fn fn_1(param1: bool, param2: &str) {
 9270                    let var1 = "«ˇhello» world";
 9271                }
 9272            "#},
 9273            cx,
 9274        );
 9275    });
 9276
 9277    // Test 2: Partial selection within a word
 9278    editor.update_in(cx, |editor, window, cx| {
 9279        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9280            s.select_display_ranges([
 9281                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9282            ]);
 9283        });
 9284    });
 9285    editor.update_in(cx, |editor, window, cx| {
 9286        assert_text_with_selections(
 9287            editor,
 9288            indoc! {r#"
 9289                use mod1::mod2::{mod3, mod4};
 9290
 9291                fn fn_1(param1: bool, param2: &str) {
 9292                    let var1 = "h«elˇ»lo world";
 9293                }
 9294            "#},
 9295            cx,
 9296        );
 9297        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9298        assert_text_with_selections(
 9299            editor,
 9300            indoc! {r#"
 9301                use mod1::mod2::{mod3, mod4};
 9302
 9303                fn fn_1(param1: bool, param2: &str) {
 9304                    let var1 = "«ˇhello» world";
 9305                }
 9306            "#},
 9307            cx,
 9308        );
 9309    });
 9310
 9311    // Test 3: Complete word already selected
 9312    editor.update_in(cx, |editor, window, cx| {
 9313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9314            s.select_display_ranges([
 9315                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9316            ]);
 9317        });
 9318    });
 9319    editor.update_in(cx, |editor, window, cx| {
 9320        assert_text_with_selections(
 9321            editor,
 9322            indoc! {r#"
 9323                use mod1::mod2::{mod3, mod4};
 9324
 9325                fn fn_1(param1: bool, param2: &str) {
 9326                    let var1 = "«helloˇ» world";
 9327                }
 9328            "#},
 9329            cx,
 9330        );
 9331        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9332        assert_text_with_selections(
 9333            editor,
 9334            indoc! {r#"
 9335                use mod1::mod2::{mod3, mod4};
 9336
 9337                fn fn_1(param1: bool, param2: &str) {
 9338                    let var1 = "«hello worldˇ»";
 9339                }
 9340            "#},
 9341            cx,
 9342        );
 9343    });
 9344
 9345    // Test 4: Selection spanning across words
 9346    editor.update_in(cx, |editor, window, cx| {
 9347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9348            s.select_display_ranges([
 9349                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9350            ]);
 9351        });
 9352    });
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        assert_text_with_selections(
 9355            editor,
 9356            indoc! {r#"
 9357                use mod1::mod2::{mod3, mod4};
 9358
 9359                fn fn_1(param1: bool, param2: &str) {
 9360                    let var1 = "hel«lo woˇ»rld";
 9361                }
 9362            "#},
 9363            cx,
 9364        );
 9365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9366        assert_text_with_selections(
 9367            editor,
 9368            indoc! {r#"
 9369                use mod1::mod2::{mod3, mod4};
 9370
 9371                fn fn_1(param1: bool, param2: &str) {
 9372                    let var1 = "«ˇhello world»";
 9373                }
 9374            "#},
 9375            cx,
 9376        );
 9377    });
 9378
 9379    // Test 5: Expansion beyond string
 9380    editor.update_in(cx, |editor, window, cx| {
 9381        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9382        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9383        assert_text_with_selections(
 9384            editor,
 9385            indoc! {r#"
 9386                use mod1::mod2::{mod3, mod4};
 9387
 9388                fn fn_1(param1: bool, param2: &str) {
 9389                    «ˇlet var1 = "hello world";»
 9390                }
 9391            "#},
 9392            cx,
 9393        );
 9394    });
 9395}
 9396
 9397#[gpui::test]
 9398async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9399    init_test(cx, |_| {});
 9400
 9401    let mut cx = EditorTestContext::new(cx).await;
 9402
 9403    let language = Arc::new(Language::new(
 9404        LanguageConfig::default(),
 9405        Some(tree_sitter_rust::LANGUAGE.into()),
 9406    ));
 9407
 9408    cx.update_buffer(|buffer, cx| {
 9409        buffer.set_language(Some(language), cx);
 9410    });
 9411
 9412    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9413    cx.update_editor(|editor, window, cx| {
 9414        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9415    });
 9416
 9417    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9418
 9419    cx.set_state(indoc! { r#"fn a() {
 9420          // what
 9421          // a
 9422          // ˇlong
 9423          // method
 9424          // I
 9425          // sure
 9426          // hope
 9427          // it
 9428          // works
 9429    }"# });
 9430
 9431    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9432    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9433    cx.update(|_, cx| {
 9434        multi_buffer.update(cx, |multi_buffer, cx| {
 9435            multi_buffer.set_excerpts_for_path(
 9436                PathKey::for_buffer(&buffer, cx),
 9437                buffer,
 9438                [Point::new(1, 0)..Point::new(1, 0)],
 9439                3,
 9440                cx,
 9441            );
 9442        });
 9443    });
 9444
 9445    let editor2 = cx.new_window_entity(|window, cx| {
 9446        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9447    });
 9448
 9449    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9450    cx.update_editor(|editor, window, cx| {
 9451        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9452            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9453        })
 9454    });
 9455
 9456    cx.assert_editor_state(indoc! { "
 9457        fn a() {
 9458              // what
 9459              // a
 9460        ˇ      // long
 9461              // method"});
 9462
 9463    cx.update_editor(|editor, window, cx| {
 9464        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9465    });
 9466
 9467    // Although we could potentially make the action work when the syntax node
 9468    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9469    // did. Maybe we could also expand the excerpt to contain the range?
 9470    cx.assert_editor_state(indoc! { "
 9471        fn a() {
 9472              // what
 9473              // a
 9474        ˇ      // long
 9475              // method"});
 9476}
 9477
 9478#[gpui::test]
 9479async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9480    init_test(cx, |_| {});
 9481
 9482    let base_text = r#"
 9483        impl A {
 9484            // this is an uncommitted comment
 9485
 9486            fn b() {
 9487                c();
 9488            }
 9489
 9490            // this is another uncommitted comment
 9491
 9492            fn d() {
 9493                // e
 9494                // f
 9495            }
 9496        }
 9497
 9498        fn g() {
 9499            // h
 9500        }
 9501    "#
 9502    .unindent();
 9503
 9504    let text = r#"
 9505        ˇimpl A {
 9506
 9507            fn b() {
 9508                c();
 9509            }
 9510
 9511            fn d() {
 9512                // e
 9513                // f
 9514            }
 9515        }
 9516
 9517        fn g() {
 9518            // h
 9519        }
 9520    "#
 9521    .unindent();
 9522
 9523    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9524    cx.set_state(&text);
 9525    cx.set_head_text(&base_text);
 9526    cx.update_editor(|editor, window, cx| {
 9527        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9528    });
 9529
 9530    cx.assert_state_with_diff(
 9531        "
 9532        ˇimpl A {
 9533      -     // this is an uncommitted comment
 9534
 9535            fn b() {
 9536                c();
 9537            }
 9538
 9539      -     // this is another uncommitted comment
 9540      -
 9541            fn d() {
 9542                // e
 9543                // f
 9544            }
 9545        }
 9546
 9547        fn g() {
 9548            // h
 9549        }
 9550    "
 9551        .unindent(),
 9552    );
 9553
 9554    let expected_display_text = "
 9555        impl A {
 9556            // this is an uncommitted comment
 9557
 9558            fn b() {
 9559 9560            }
 9561
 9562            // this is another uncommitted comment
 9563
 9564            fn d() {
 9565 9566            }
 9567        }
 9568
 9569        fn g() {
 9570 9571        }
 9572        "
 9573    .unindent();
 9574
 9575    cx.update_editor(|editor, window, cx| {
 9576        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9577        assert_eq!(editor.display_text(cx), expected_display_text);
 9578    });
 9579}
 9580
 9581#[gpui::test]
 9582async fn test_autoindent(cx: &mut TestAppContext) {
 9583    init_test(cx, |_| {});
 9584
 9585    let language = Arc::new(
 9586        Language::new(
 9587            LanguageConfig {
 9588                brackets: BracketPairConfig {
 9589                    pairs: vec![
 9590                        BracketPair {
 9591                            start: "{".to_string(),
 9592                            end: "}".to_string(),
 9593                            close: false,
 9594                            surround: false,
 9595                            newline: true,
 9596                        },
 9597                        BracketPair {
 9598                            start: "(".to_string(),
 9599                            end: ")".to_string(),
 9600                            close: false,
 9601                            surround: false,
 9602                            newline: true,
 9603                        },
 9604                    ],
 9605                    ..Default::default()
 9606                },
 9607                ..Default::default()
 9608            },
 9609            Some(tree_sitter_rust::LANGUAGE.into()),
 9610        )
 9611        .with_indents_query(
 9612            r#"
 9613                (_ "(" ")" @end) @indent
 9614                (_ "{" "}" @end) @indent
 9615            "#,
 9616        )
 9617        .unwrap(),
 9618    );
 9619
 9620    let text = "fn a() {}";
 9621
 9622    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9623    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9624    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9625    editor
 9626        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9627        .await;
 9628
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9631            s.select_ranges([5..5, 8..8, 9..9])
 9632        });
 9633        editor.newline(&Newline, window, cx);
 9634        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9635        assert_eq!(
 9636            editor.selections.ranges(&editor.display_snapshot(cx)),
 9637            &[
 9638                Point::new(1, 4)..Point::new(1, 4),
 9639                Point::new(3, 4)..Point::new(3, 4),
 9640                Point::new(5, 0)..Point::new(5, 0)
 9641            ]
 9642        );
 9643    });
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9648    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9649
 9650    let language = Arc::new(
 9651        Language::new(
 9652            LanguageConfig {
 9653                brackets: BracketPairConfig {
 9654                    pairs: vec![
 9655                        BracketPair {
 9656                            start: "{".to_string(),
 9657                            end: "}".to_string(),
 9658                            close: false,
 9659                            surround: false,
 9660                            newline: true,
 9661                        },
 9662                        BracketPair {
 9663                            start: "(".to_string(),
 9664                            end: ")".to_string(),
 9665                            close: false,
 9666                            surround: false,
 9667                            newline: true,
 9668                        },
 9669                    ],
 9670                    ..Default::default()
 9671                },
 9672                ..Default::default()
 9673            },
 9674            Some(tree_sitter_rust::LANGUAGE.into()),
 9675        )
 9676        .with_indents_query(
 9677            r#"
 9678                (_ "(" ")" @end) @indent
 9679                (_ "{" "}" @end) @indent
 9680            "#,
 9681        )
 9682        .unwrap(),
 9683    );
 9684
 9685    let text = "fn a() {}";
 9686
 9687    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9688    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9689    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9690    editor
 9691        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9692        .await;
 9693
 9694    editor.update_in(cx, |editor, window, cx| {
 9695        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9696            s.select_ranges([5..5, 8..8, 9..9])
 9697        });
 9698        editor.newline(&Newline, window, cx);
 9699        assert_eq!(
 9700            editor.text(cx),
 9701            indoc!(
 9702                "
 9703                fn a(
 9704
 9705                ) {
 9706
 9707                }
 9708                "
 9709            )
 9710        );
 9711        assert_eq!(
 9712            editor.selections.ranges(&editor.display_snapshot(cx)),
 9713            &[
 9714                Point::new(1, 0)..Point::new(1, 0),
 9715                Point::new(3, 0)..Point::new(3, 0),
 9716                Point::new(5, 0)..Point::new(5, 0)
 9717            ]
 9718        );
 9719    });
 9720}
 9721
 9722#[gpui::test]
 9723async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9724    init_test(cx, |settings| {
 9725        settings.defaults.auto_indent = Some(true);
 9726        settings.languages.0.insert(
 9727            "python".into(),
 9728            LanguageSettingsContent {
 9729                auto_indent: Some(false),
 9730                ..Default::default()
 9731            },
 9732        );
 9733    });
 9734
 9735    let mut cx = EditorTestContext::new(cx).await;
 9736
 9737    let injected_language = Arc::new(
 9738        Language::new(
 9739            LanguageConfig {
 9740                brackets: BracketPairConfig {
 9741                    pairs: vec![
 9742                        BracketPair {
 9743                            start: "{".to_string(),
 9744                            end: "}".to_string(),
 9745                            close: false,
 9746                            surround: false,
 9747                            newline: true,
 9748                        },
 9749                        BracketPair {
 9750                            start: "(".to_string(),
 9751                            end: ")".to_string(),
 9752                            close: true,
 9753                            surround: false,
 9754                            newline: true,
 9755                        },
 9756                    ],
 9757                    ..Default::default()
 9758                },
 9759                name: "python".into(),
 9760                ..Default::default()
 9761            },
 9762            Some(tree_sitter_python::LANGUAGE.into()),
 9763        )
 9764        .with_indents_query(
 9765            r#"
 9766                (_ "(" ")" @end) @indent
 9767                (_ "{" "}" @end) @indent
 9768            "#,
 9769        )
 9770        .unwrap(),
 9771    );
 9772
 9773    let language = Arc::new(
 9774        Language::new(
 9775            LanguageConfig {
 9776                brackets: BracketPairConfig {
 9777                    pairs: vec![
 9778                        BracketPair {
 9779                            start: "{".to_string(),
 9780                            end: "}".to_string(),
 9781                            close: false,
 9782                            surround: false,
 9783                            newline: true,
 9784                        },
 9785                        BracketPair {
 9786                            start: "(".to_string(),
 9787                            end: ")".to_string(),
 9788                            close: true,
 9789                            surround: false,
 9790                            newline: true,
 9791                        },
 9792                    ],
 9793                    ..Default::default()
 9794                },
 9795                name: LanguageName::new("rust"),
 9796                ..Default::default()
 9797            },
 9798            Some(tree_sitter_rust::LANGUAGE.into()),
 9799        )
 9800        .with_indents_query(
 9801            r#"
 9802                (_ "(" ")" @end) @indent
 9803                (_ "{" "}" @end) @indent
 9804            "#,
 9805        )
 9806        .unwrap()
 9807        .with_injection_query(
 9808            r#"
 9809            (macro_invocation
 9810                macro: (identifier) @_macro_name
 9811                (token_tree) @injection.content
 9812                (#set! injection.language "python"))
 9813           "#,
 9814        )
 9815        .unwrap(),
 9816    );
 9817
 9818    cx.language_registry().add(injected_language);
 9819    cx.language_registry().add(language.clone());
 9820
 9821    cx.update_buffer(|buffer, cx| {
 9822        buffer.set_language(Some(language), cx);
 9823    });
 9824
 9825    cx.set_state(r#"struct A {ˇ}"#);
 9826
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.newline(&Default::default(), window, cx);
 9829    });
 9830
 9831    cx.assert_editor_state(indoc!(
 9832        "struct A {
 9833            ˇ
 9834        }"
 9835    ));
 9836
 9837    cx.set_state(r#"select_biased!(ˇ)"#);
 9838
 9839    cx.update_editor(|editor, window, cx| {
 9840        editor.newline(&Default::default(), window, cx);
 9841        editor.handle_input("def ", window, cx);
 9842        editor.handle_input("(", window, cx);
 9843        editor.newline(&Default::default(), window, cx);
 9844        editor.handle_input("a", window, cx);
 9845    });
 9846
 9847    cx.assert_editor_state(indoc!(
 9848        "select_biased!(
 9849        def (
 9850 9851        )
 9852        )"
 9853    ));
 9854}
 9855
 9856#[gpui::test]
 9857async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9858    init_test(cx, |_| {});
 9859
 9860    {
 9861        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9862        cx.set_state(indoc! {"
 9863            impl A {
 9864
 9865                fn b() {}
 9866
 9867            «fn c() {
 9868
 9869            }ˇ»
 9870            }
 9871        "});
 9872
 9873        cx.update_editor(|editor, window, cx| {
 9874            editor.autoindent(&Default::default(), window, cx);
 9875        });
 9876
 9877        cx.assert_editor_state(indoc! {"
 9878            impl A {
 9879
 9880                fn b() {}
 9881
 9882                «fn c() {
 9883
 9884                }ˇ»
 9885            }
 9886        "});
 9887    }
 9888
 9889    {
 9890        let mut cx = EditorTestContext::new_multibuffer(
 9891            cx,
 9892            [indoc! { "
 9893                impl A {
 9894                «
 9895                // a
 9896                fn b(){}
 9897                »
 9898                «
 9899                    }
 9900                    fn c(){}
 9901                »
 9902            "}],
 9903        );
 9904
 9905        let buffer = cx.update_editor(|editor, _, cx| {
 9906            let buffer = editor.buffer().update(cx, |buffer, _| {
 9907                buffer.all_buffers().iter().next().unwrap().clone()
 9908            });
 9909            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9910            buffer
 9911        });
 9912
 9913        cx.run_until_parked();
 9914        cx.update_editor(|editor, window, cx| {
 9915            editor.select_all(&Default::default(), window, cx);
 9916            editor.autoindent(&Default::default(), window, cx)
 9917        });
 9918        cx.run_until_parked();
 9919
 9920        cx.update(|_, cx| {
 9921            assert_eq!(
 9922                buffer.read(cx).text(),
 9923                indoc! { "
 9924                    impl A {
 9925
 9926                        // a
 9927                        fn b(){}
 9928
 9929
 9930                    }
 9931                    fn c(){}
 9932
 9933                " }
 9934            )
 9935        });
 9936    }
 9937}
 9938
 9939#[gpui::test]
 9940async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9941    init_test(cx, |_| {});
 9942
 9943    let mut cx = EditorTestContext::new(cx).await;
 9944
 9945    let language = Arc::new(Language::new(
 9946        LanguageConfig {
 9947            brackets: BracketPairConfig {
 9948                pairs: vec![
 9949                    BracketPair {
 9950                        start: "{".to_string(),
 9951                        end: "}".to_string(),
 9952                        close: true,
 9953                        surround: true,
 9954                        newline: true,
 9955                    },
 9956                    BracketPair {
 9957                        start: "(".to_string(),
 9958                        end: ")".to_string(),
 9959                        close: true,
 9960                        surround: true,
 9961                        newline: true,
 9962                    },
 9963                    BracketPair {
 9964                        start: "/*".to_string(),
 9965                        end: " */".to_string(),
 9966                        close: true,
 9967                        surround: true,
 9968                        newline: true,
 9969                    },
 9970                    BracketPair {
 9971                        start: "[".to_string(),
 9972                        end: "]".to_string(),
 9973                        close: false,
 9974                        surround: false,
 9975                        newline: true,
 9976                    },
 9977                    BracketPair {
 9978                        start: "\"".to_string(),
 9979                        end: "\"".to_string(),
 9980                        close: true,
 9981                        surround: true,
 9982                        newline: false,
 9983                    },
 9984                    BracketPair {
 9985                        start: "<".to_string(),
 9986                        end: ">".to_string(),
 9987                        close: false,
 9988                        surround: true,
 9989                        newline: true,
 9990                    },
 9991                ],
 9992                ..Default::default()
 9993            },
 9994            autoclose_before: "})]".to_string(),
 9995            ..Default::default()
 9996        },
 9997        Some(tree_sitter_rust::LANGUAGE.into()),
 9998    ));
 9999
10000    cx.language_registry().add(language.clone());
10001    cx.update_buffer(|buffer, cx| {
10002        buffer.set_language(Some(language), cx);
10003    });
10004
10005    cx.set_state(
10006        &r#"
10007            🏀ˇ
10008            εˇ
10009            ❤️ˇ
10010        "#
10011        .unindent(),
10012    );
10013
10014    // autoclose multiple nested brackets at multiple cursors
10015    cx.update_editor(|editor, window, cx| {
10016        editor.handle_input("{", window, cx);
10017        editor.handle_input("{", window, cx);
10018        editor.handle_input("{", window, cx);
10019    });
10020    cx.assert_editor_state(
10021        &"
10022            🏀{{{ˇ}}}
10023            ε{{{ˇ}}}
10024            ❤️{{{ˇ}}}
10025        "
10026        .unindent(),
10027    );
10028
10029    // insert a different closing bracket
10030    cx.update_editor(|editor, window, cx| {
10031        editor.handle_input(")", window, cx);
10032    });
10033    cx.assert_editor_state(
10034        &"
10035            🏀{{{)ˇ}}}
10036            ε{{{)ˇ}}}
10037            ❤️{{{)ˇ}}}
10038        "
10039        .unindent(),
10040    );
10041
10042    // skip over the auto-closed brackets when typing a closing bracket
10043    cx.update_editor(|editor, window, cx| {
10044        editor.move_right(&MoveRight, window, cx);
10045        editor.handle_input("}", window, cx);
10046        editor.handle_input("}", window, cx);
10047        editor.handle_input("}", window, cx);
10048    });
10049    cx.assert_editor_state(
10050        &"
10051            🏀{{{)}}}}ˇ
10052            ε{{{)}}}}ˇ
10053            ❤️{{{)}}}}ˇ
10054        "
10055        .unindent(),
10056    );
10057
10058    // autoclose multi-character pairs
10059    cx.set_state(
10060        &"
10061            ˇ
10062            ˇ
10063        "
10064        .unindent(),
10065    );
10066    cx.update_editor(|editor, window, cx| {
10067        editor.handle_input("/", window, cx);
10068        editor.handle_input("*", window, cx);
10069    });
10070    cx.assert_editor_state(
10071        &"
10072            /*ˇ */
10073            /*ˇ */
10074        "
10075        .unindent(),
10076    );
10077
10078    // one cursor autocloses a multi-character pair, one cursor
10079    // does not autoclose.
10080    cx.set_state(
10081        &"
1008210083            ˇ
10084        "
10085        .unindent(),
10086    );
10087    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10088    cx.assert_editor_state(
10089        &"
10090            /*ˇ */
1009110092        "
10093        .unindent(),
10094    );
10095
10096    // Don't autoclose if the next character isn't whitespace and isn't
10097    // listed in the language's "autoclose_before" section.
10098    cx.set_state("ˇa b");
10099    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10100    cx.assert_editor_state("{ˇa b");
10101
10102    // Don't autoclose if `close` is false for the bracket pair
10103    cx.set_state("ˇ");
10104    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10105    cx.assert_editor_state("");
10106
10107    // Surround with brackets if text is selected
10108    cx.set_state("«aˇ» b");
10109    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10110    cx.assert_editor_state("{«aˇ»} b");
10111
10112    // Autoclose when not immediately after a word character
10113    cx.set_state("a ˇ");
10114    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10115    cx.assert_editor_state("a \"ˇ\"");
10116
10117    // Autoclose pair where the start and end characters are the same
10118    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10119    cx.assert_editor_state("a \"\"ˇ");
10120
10121    // Don't autoclose when immediately after a word character
10122    cx.set_state("");
10123    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10124    cx.assert_editor_state("a\"ˇ");
10125
10126    // Do autoclose when after a non-word character
10127    cx.set_state("");
10128    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10129    cx.assert_editor_state("{\"ˇ\"");
10130
10131    // Non identical pairs autoclose regardless of preceding character
10132    cx.set_state("");
10133    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10134    cx.assert_editor_state("a{ˇ}");
10135
10136    // Don't autoclose pair if autoclose is disabled
10137    cx.set_state("ˇ");
10138    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10139    cx.assert_editor_state("");
10140
10141    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10142    cx.set_state("«aˇ» b");
10143    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10144    cx.assert_editor_state("<«aˇ»> b");
10145}
10146
10147#[gpui::test]
10148async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10149    init_test(cx, |settings| {
10150        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10151    });
10152
10153    let mut cx = EditorTestContext::new(cx).await;
10154
10155    let language = Arc::new(Language::new(
10156        LanguageConfig {
10157            brackets: BracketPairConfig {
10158                pairs: vec![
10159                    BracketPair {
10160                        start: "{".to_string(),
10161                        end: "}".to_string(),
10162                        close: true,
10163                        surround: true,
10164                        newline: true,
10165                    },
10166                    BracketPair {
10167                        start: "(".to_string(),
10168                        end: ")".to_string(),
10169                        close: true,
10170                        surround: true,
10171                        newline: true,
10172                    },
10173                    BracketPair {
10174                        start: "[".to_string(),
10175                        end: "]".to_string(),
10176                        close: false,
10177                        surround: false,
10178                        newline: true,
10179                    },
10180                ],
10181                ..Default::default()
10182            },
10183            autoclose_before: "})]".to_string(),
10184            ..Default::default()
10185        },
10186        Some(tree_sitter_rust::LANGUAGE.into()),
10187    ));
10188
10189    cx.language_registry().add(language.clone());
10190    cx.update_buffer(|buffer, cx| {
10191        buffer.set_language(Some(language), cx);
10192    });
10193
10194    cx.set_state(
10195        &"
10196            ˇ
10197            ˇ
10198            ˇ
10199        "
10200        .unindent(),
10201    );
10202
10203    // ensure only matching closing brackets are skipped over
10204    cx.update_editor(|editor, window, cx| {
10205        editor.handle_input("}", window, cx);
10206        editor.move_left(&MoveLeft, window, cx);
10207        editor.handle_input(")", window, cx);
10208        editor.move_left(&MoveLeft, window, cx);
10209    });
10210    cx.assert_editor_state(
10211        &"
10212            ˇ)}
10213            ˇ)}
10214            ˇ)}
10215        "
10216        .unindent(),
10217    );
10218
10219    // skip-over closing brackets at multiple cursors
10220    cx.update_editor(|editor, window, cx| {
10221        editor.handle_input(")", window, cx);
10222        editor.handle_input("}", window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &"
10226            )}ˇ
10227            )}ˇ
10228            )}ˇ
10229        "
10230        .unindent(),
10231    );
10232
10233    // ignore non-close brackets
10234    cx.update_editor(|editor, window, cx| {
10235        editor.handle_input("]", window, cx);
10236        editor.move_left(&MoveLeft, window, cx);
10237        editor.handle_input("]", window, cx);
10238    });
10239    cx.assert_editor_state(
10240        &"
10241            )}]ˇ]
10242            )}]ˇ]
10243            )}]ˇ]
10244        "
10245        .unindent(),
10246    );
10247}
10248
10249#[gpui::test]
10250async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10251    init_test(cx, |_| {});
10252
10253    let mut cx = EditorTestContext::new(cx).await;
10254
10255    let html_language = Arc::new(
10256        Language::new(
10257            LanguageConfig {
10258                name: "HTML".into(),
10259                brackets: BracketPairConfig {
10260                    pairs: vec![
10261                        BracketPair {
10262                            start: "<".into(),
10263                            end: ">".into(),
10264                            close: true,
10265                            ..Default::default()
10266                        },
10267                        BracketPair {
10268                            start: "{".into(),
10269                            end: "}".into(),
10270                            close: true,
10271                            ..Default::default()
10272                        },
10273                        BracketPair {
10274                            start: "(".into(),
10275                            end: ")".into(),
10276                            close: true,
10277                            ..Default::default()
10278                        },
10279                    ],
10280                    ..Default::default()
10281                },
10282                autoclose_before: "})]>".into(),
10283                ..Default::default()
10284            },
10285            Some(tree_sitter_html::LANGUAGE.into()),
10286        )
10287        .with_injection_query(
10288            r#"
10289            (script_element
10290                (raw_text) @injection.content
10291                (#set! injection.language "javascript"))
10292            "#,
10293        )
10294        .unwrap(),
10295    );
10296
10297    let javascript_language = Arc::new(Language::new(
10298        LanguageConfig {
10299            name: "JavaScript".into(),
10300            brackets: BracketPairConfig {
10301                pairs: vec![
10302                    BracketPair {
10303                        start: "/*".into(),
10304                        end: " */".into(),
10305                        close: true,
10306                        ..Default::default()
10307                    },
10308                    BracketPair {
10309                        start: "{".into(),
10310                        end: "}".into(),
10311                        close: true,
10312                        ..Default::default()
10313                    },
10314                    BracketPair {
10315                        start: "(".into(),
10316                        end: ")".into(),
10317                        close: true,
10318                        ..Default::default()
10319                    },
10320                ],
10321                ..Default::default()
10322            },
10323            autoclose_before: "})]>".into(),
10324            ..Default::default()
10325        },
10326        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10327    ));
10328
10329    cx.language_registry().add(html_language.clone());
10330    cx.language_registry().add(javascript_language);
10331    cx.executor().run_until_parked();
10332
10333    cx.update_buffer(|buffer, cx| {
10334        buffer.set_language(Some(html_language), cx);
10335    });
10336
10337    cx.set_state(
10338        &r#"
10339            <body>ˇ
10340                <script>
10341                    var x = 1;ˇ
10342                </script>
10343            </body>ˇ
10344        "#
10345        .unindent(),
10346    );
10347
10348    // Precondition: different languages are active at different locations.
10349    cx.update_editor(|editor, window, cx| {
10350        let snapshot = editor.snapshot(window, cx);
10351        let cursors = editor
10352            .selections
10353            .ranges::<usize>(&editor.display_snapshot(cx));
10354        let languages = cursors
10355            .iter()
10356            .map(|c| snapshot.language_at(c.start).unwrap().name())
10357            .collect::<Vec<_>>();
10358        assert_eq!(
10359            languages,
10360            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10361        );
10362    });
10363
10364    // Angle brackets autoclose in HTML, but not JavaScript.
10365    cx.update_editor(|editor, window, cx| {
10366        editor.handle_input("<", window, cx);
10367        editor.handle_input("a", window, cx);
10368    });
10369    cx.assert_editor_state(
10370        &r#"
10371            <body><aˇ>
10372                <script>
10373                    var x = 1;<aˇ
10374                </script>
10375            </body><aˇ>
10376        "#
10377        .unindent(),
10378    );
10379
10380    // Curly braces and parens autoclose in both HTML and JavaScript.
10381    cx.update_editor(|editor, window, cx| {
10382        editor.handle_input(" b=", window, cx);
10383        editor.handle_input("{", window, cx);
10384        editor.handle_input("c", window, cx);
10385        editor.handle_input("(", window, cx);
10386    });
10387    cx.assert_editor_state(
10388        &r#"
10389            <body><a b={c(ˇ)}>
10390                <script>
10391                    var x = 1;<a b={c(ˇ)}
10392                </script>
10393            </body><a b={c(ˇ)}>
10394        "#
10395        .unindent(),
10396    );
10397
10398    // Brackets that were already autoclosed are skipped.
10399    cx.update_editor(|editor, window, cx| {
10400        editor.handle_input(")", window, cx);
10401        editor.handle_input("d", window, cx);
10402        editor.handle_input("}", window, cx);
10403    });
10404    cx.assert_editor_state(
10405        &r#"
10406            <body><a b={c()d}ˇ>
10407                <script>
10408                    var x = 1;<a b={c()d}ˇ
10409                </script>
10410            </body><a b={c()d}ˇ>
10411        "#
10412        .unindent(),
10413    );
10414    cx.update_editor(|editor, window, cx| {
10415        editor.handle_input(">", window, cx);
10416    });
10417    cx.assert_editor_state(
10418        &r#"
10419            <body><a b={c()d}>ˇ
10420                <script>
10421                    var x = 1;<a b={c()d}>ˇ
10422                </script>
10423            </body><a b={c()d}>ˇ
10424        "#
10425        .unindent(),
10426    );
10427
10428    // Reset
10429    cx.set_state(
10430        &r#"
10431            <body>ˇ
10432                <script>
10433                    var x = 1;ˇ
10434                </script>
10435            </body>ˇ
10436        "#
10437        .unindent(),
10438    );
10439
10440    cx.update_editor(|editor, window, cx| {
10441        editor.handle_input("<", window, cx);
10442    });
10443    cx.assert_editor_state(
10444        &r#"
10445            <body><ˇ>
10446                <script>
10447                    var x = 1;<ˇ
10448                </script>
10449            </body><ˇ>
10450        "#
10451        .unindent(),
10452    );
10453
10454    // When backspacing, the closing angle brackets are removed.
10455    cx.update_editor(|editor, window, cx| {
10456        editor.backspace(&Backspace, window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body>ˇ
10461                <script>
10462                    var x = 1;ˇ
10463                </script>
10464            </body>ˇ
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Block comments autoclose in JavaScript, but not HTML.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input("/", window, cx);
10472        editor.handle_input("*", window, cx);
10473    });
10474    cx.assert_editor_state(
10475        &r#"
10476            <body>/*ˇ
10477                <script>
10478                    var x = 1;/*ˇ */
10479                </script>
10480            </body>/*ˇ
10481        "#
10482        .unindent(),
10483    );
10484}
10485
10486#[gpui::test]
10487async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10488    init_test(cx, |_| {});
10489
10490    let mut cx = EditorTestContext::new(cx).await;
10491
10492    let rust_language = Arc::new(
10493        Language::new(
10494            LanguageConfig {
10495                name: "Rust".into(),
10496                brackets: serde_json::from_value(json!([
10497                    { "start": "{", "end": "}", "close": true, "newline": true },
10498                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10499                ]))
10500                .unwrap(),
10501                autoclose_before: "})]>".into(),
10502                ..Default::default()
10503            },
10504            Some(tree_sitter_rust::LANGUAGE.into()),
10505        )
10506        .with_override_query("(string_literal) @string")
10507        .unwrap(),
10508    );
10509
10510    cx.language_registry().add(rust_language.clone());
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(rust_language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            let x = ˇ
10518        "#
10519        .unindent(),
10520    );
10521
10522    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10523    cx.update_editor(|editor, window, cx| {
10524        editor.handle_input("\"", window, cx);
10525    });
10526    cx.assert_editor_state(
10527        &r#"
10528            let x = "ˇ"
10529        "#
10530        .unindent(),
10531    );
10532
10533    // Inserting another quotation mark. The cursor moves across the existing
10534    // automatically-inserted quotation mark.
10535    cx.update_editor(|editor, window, cx| {
10536        editor.handle_input("\"", window, cx);
10537    });
10538    cx.assert_editor_state(
10539        &r#"
10540            let x = ""ˇ
10541        "#
10542        .unindent(),
10543    );
10544
10545    // Reset
10546    cx.set_state(
10547        &r#"
10548            let x = ˇ
10549        "#
10550        .unindent(),
10551    );
10552
10553    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10554    cx.update_editor(|editor, window, cx| {
10555        editor.handle_input("\"", window, cx);
10556        editor.handle_input(" ", window, cx);
10557        editor.move_left(&Default::default(), window, cx);
10558        editor.handle_input("\\", window, cx);
10559        editor.handle_input("\"", window, cx);
10560    });
10561    cx.assert_editor_state(
10562        &r#"
10563            let x = "\"ˇ "
10564        "#
10565        .unindent(),
10566    );
10567
10568    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10569    // mark. Nothing is inserted.
10570    cx.update_editor(|editor, window, cx| {
10571        editor.move_right(&Default::default(), window, cx);
10572        editor.handle_input("\"", window, cx);
10573    });
10574    cx.assert_editor_state(
10575        &r#"
10576            let x = "\" "ˇ
10577        "#
10578        .unindent(),
10579    );
10580}
10581
10582#[gpui::test]
10583async fn test_surround_with_pair(cx: &mut TestAppContext) {
10584    init_test(cx, |_| {});
10585
10586    let language = Arc::new(Language::new(
10587        LanguageConfig {
10588            brackets: BracketPairConfig {
10589                pairs: vec![
10590                    BracketPair {
10591                        start: "{".to_string(),
10592                        end: "}".to_string(),
10593                        close: true,
10594                        surround: true,
10595                        newline: true,
10596                    },
10597                    BracketPair {
10598                        start: "/* ".to_string(),
10599                        end: "*/".to_string(),
10600                        close: true,
10601                        surround: true,
10602                        ..Default::default()
10603                    },
10604                ],
10605                ..Default::default()
10606            },
10607            ..Default::default()
10608        },
10609        Some(tree_sitter_rust::LANGUAGE.into()),
10610    ));
10611
10612    let text = r#"
10613        a
10614        b
10615        c
10616    "#
10617    .unindent();
10618
10619    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10620    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10621    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10622    editor
10623        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10624        .await;
10625
10626    editor.update_in(cx, |editor, window, cx| {
10627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10628            s.select_display_ranges([
10629                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10630                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10631                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10632            ])
10633        });
10634
10635        editor.handle_input("{", window, cx);
10636        editor.handle_input("{", window, cx);
10637        editor.handle_input("{", window, cx);
10638        assert_eq!(
10639            editor.text(cx),
10640            "
10641                {{{a}}}
10642                {{{b}}}
10643                {{{c}}}
10644            "
10645            .unindent()
10646        );
10647        assert_eq!(
10648            editor.selections.display_ranges(cx),
10649            [
10650                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10651                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10652                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10653            ]
10654        );
10655
10656        editor.undo(&Undo, window, cx);
10657        editor.undo(&Undo, window, cx);
10658        editor.undo(&Undo, window, cx);
10659        assert_eq!(
10660            editor.text(cx),
10661            "
10662                a
10663                b
10664                c
10665            "
10666            .unindent()
10667        );
10668        assert_eq!(
10669            editor.selections.display_ranges(cx),
10670            [
10671                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10672                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10673                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10674            ]
10675        );
10676
10677        // Ensure inserting the first character of a multi-byte bracket pair
10678        // doesn't surround the selections with the bracket.
10679        editor.handle_input("/", window, cx);
10680        assert_eq!(
10681            editor.text(cx),
10682            "
10683                /
10684                /
10685                /
10686            "
10687            .unindent()
10688        );
10689        assert_eq!(
10690            editor.selections.display_ranges(cx),
10691            [
10692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10693                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10694                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10695            ]
10696        );
10697
10698        editor.undo(&Undo, window, cx);
10699        assert_eq!(
10700            editor.text(cx),
10701            "
10702                a
10703                b
10704                c
10705            "
10706            .unindent()
10707        );
10708        assert_eq!(
10709            editor.selections.display_ranges(cx),
10710            [
10711                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10712                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10713                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10714            ]
10715        );
10716
10717        // Ensure inserting the last character of a multi-byte bracket pair
10718        // doesn't surround the selections with the bracket.
10719        editor.handle_input("*", window, cx);
10720        assert_eq!(
10721            editor.text(cx),
10722            "
10723                *
10724                *
10725                *
10726            "
10727            .unindent()
10728        );
10729        assert_eq!(
10730            editor.selections.display_ranges(cx),
10731            [
10732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10733                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10734                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10735            ]
10736        );
10737    });
10738}
10739
10740#[gpui::test]
10741async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10742    init_test(cx, |_| {});
10743
10744    let language = Arc::new(Language::new(
10745        LanguageConfig {
10746            brackets: BracketPairConfig {
10747                pairs: vec![BracketPair {
10748                    start: "{".to_string(),
10749                    end: "}".to_string(),
10750                    close: true,
10751                    surround: true,
10752                    newline: true,
10753                }],
10754                ..Default::default()
10755            },
10756            autoclose_before: "}".to_string(),
10757            ..Default::default()
10758        },
10759        Some(tree_sitter_rust::LANGUAGE.into()),
10760    ));
10761
10762    let text = r#"
10763        a
10764        b
10765        c
10766    "#
10767    .unindent();
10768
10769    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10770    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10771    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10772    editor
10773        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10774        .await;
10775
10776    editor.update_in(cx, |editor, window, cx| {
10777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10778            s.select_ranges([
10779                Point::new(0, 1)..Point::new(0, 1),
10780                Point::new(1, 1)..Point::new(1, 1),
10781                Point::new(2, 1)..Point::new(2, 1),
10782            ])
10783        });
10784
10785        editor.handle_input("{", window, cx);
10786        editor.handle_input("{", window, cx);
10787        editor.handle_input("_", window, cx);
10788        assert_eq!(
10789            editor.text(cx),
10790            "
10791                a{{_}}
10792                b{{_}}
10793                c{{_}}
10794            "
10795            .unindent()
10796        );
10797        assert_eq!(
10798            editor
10799                .selections
10800                .ranges::<Point>(&editor.display_snapshot(cx)),
10801            [
10802                Point::new(0, 4)..Point::new(0, 4),
10803                Point::new(1, 4)..Point::new(1, 4),
10804                Point::new(2, 4)..Point::new(2, 4)
10805            ]
10806        );
10807
10808        editor.backspace(&Default::default(), window, cx);
10809        editor.backspace(&Default::default(), window, cx);
10810        assert_eq!(
10811            editor.text(cx),
10812            "
10813                a{}
10814                b{}
10815                c{}
10816            "
10817            .unindent()
10818        );
10819        assert_eq!(
10820            editor
10821                .selections
10822                .ranges::<Point>(&editor.display_snapshot(cx)),
10823            [
10824                Point::new(0, 2)..Point::new(0, 2),
10825                Point::new(1, 2)..Point::new(1, 2),
10826                Point::new(2, 2)..Point::new(2, 2)
10827            ]
10828        );
10829
10830        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10831        assert_eq!(
10832            editor.text(cx),
10833            "
10834                a
10835                b
10836                c
10837            "
10838            .unindent()
10839        );
10840        assert_eq!(
10841            editor
10842                .selections
10843                .ranges::<Point>(&editor.display_snapshot(cx)),
10844            [
10845                Point::new(0, 1)..Point::new(0, 1),
10846                Point::new(1, 1)..Point::new(1, 1),
10847                Point::new(2, 1)..Point::new(2, 1)
10848            ]
10849        );
10850    });
10851}
10852
10853#[gpui::test]
10854async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10855    init_test(cx, |settings| {
10856        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10857    });
10858
10859    let mut cx = EditorTestContext::new(cx).await;
10860
10861    let language = Arc::new(Language::new(
10862        LanguageConfig {
10863            brackets: BracketPairConfig {
10864                pairs: vec![
10865                    BracketPair {
10866                        start: "{".to_string(),
10867                        end: "}".to_string(),
10868                        close: true,
10869                        surround: true,
10870                        newline: true,
10871                    },
10872                    BracketPair {
10873                        start: "(".to_string(),
10874                        end: ")".to_string(),
10875                        close: true,
10876                        surround: true,
10877                        newline: true,
10878                    },
10879                    BracketPair {
10880                        start: "[".to_string(),
10881                        end: "]".to_string(),
10882                        close: false,
10883                        surround: true,
10884                        newline: true,
10885                    },
10886                ],
10887                ..Default::default()
10888            },
10889            autoclose_before: "})]".to_string(),
10890            ..Default::default()
10891        },
10892        Some(tree_sitter_rust::LANGUAGE.into()),
10893    ));
10894
10895    cx.language_registry().add(language.clone());
10896    cx.update_buffer(|buffer, cx| {
10897        buffer.set_language(Some(language), cx);
10898    });
10899
10900    cx.set_state(
10901        &"
10902            {(ˇ)}
10903            [[ˇ]]
10904            {(ˇ)}
10905        "
10906        .unindent(),
10907    );
10908
10909    cx.update_editor(|editor, window, cx| {
10910        editor.backspace(&Default::default(), window, cx);
10911        editor.backspace(&Default::default(), window, cx);
10912    });
10913
10914    cx.assert_editor_state(
10915        &"
10916            ˇ
10917            ˇ]]
10918            ˇ
10919        "
10920        .unindent(),
10921    );
10922
10923    cx.update_editor(|editor, window, cx| {
10924        editor.handle_input("{", window, cx);
10925        editor.handle_input("{", window, cx);
10926        editor.move_right(&MoveRight, window, cx);
10927        editor.move_right(&MoveRight, window, cx);
10928        editor.move_left(&MoveLeft, window, cx);
10929        editor.move_left(&MoveLeft, window, cx);
10930        editor.backspace(&Default::default(), window, cx);
10931    });
10932
10933    cx.assert_editor_state(
10934        &"
10935            {ˇ}
10936            {ˇ}]]
10937            {ˇ}
10938        "
10939        .unindent(),
10940    );
10941
10942    cx.update_editor(|editor, window, cx| {
10943        editor.backspace(&Default::default(), window, cx);
10944    });
10945
10946    cx.assert_editor_state(
10947        &"
10948            ˇ
10949            ˇ]]
10950            ˇ
10951        "
10952        .unindent(),
10953    );
10954}
10955
10956#[gpui::test]
10957async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10958    init_test(cx, |_| {});
10959
10960    let language = Arc::new(Language::new(
10961        LanguageConfig::default(),
10962        Some(tree_sitter_rust::LANGUAGE.into()),
10963    ));
10964
10965    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10968    editor
10969        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10970        .await;
10971
10972    editor.update_in(cx, |editor, window, cx| {
10973        editor.set_auto_replace_emoji_shortcode(true);
10974
10975        editor.handle_input("Hello ", window, cx);
10976        editor.handle_input(":wave", window, cx);
10977        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10978
10979        editor.handle_input(":", window, cx);
10980        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10981
10982        editor.handle_input(" :smile", window, cx);
10983        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10984
10985        editor.handle_input(":", window, cx);
10986        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10987
10988        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10989        editor.handle_input(":wave", window, cx);
10990        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10991
10992        editor.handle_input(":", window, cx);
10993        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10994
10995        editor.handle_input(":1", window, cx);
10996        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10997
10998        editor.handle_input(":", window, cx);
10999        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11000
11001        // Ensure shortcode does not get replaced when it is part of a word
11002        editor.handle_input(" Test:wave", window, cx);
11003        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11004
11005        editor.handle_input(":", window, cx);
11006        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11007
11008        editor.set_auto_replace_emoji_shortcode(false);
11009
11010        // Ensure shortcode does not get replaced when auto replace is off
11011        editor.handle_input(" :wave", window, cx);
11012        assert_eq!(
11013            editor.text(cx),
11014            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11015        );
11016
11017        editor.handle_input(":", window, cx);
11018        assert_eq!(
11019            editor.text(cx),
11020            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11021        );
11022    });
11023}
11024
11025#[gpui::test]
11026async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11027    init_test(cx, |_| {});
11028
11029    let (text, insertion_ranges) = marked_text_ranges(
11030        indoc! {"
11031            ˇ
11032        "},
11033        false,
11034    );
11035
11036    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11037    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11038
11039    _ = editor.update_in(cx, |editor, window, cx| {
11040        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11041
11042        editor
11043            .insert_snippet(&insertion_ranges, snippet, window, cx)
11044            .unwrap();
11045
11046        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11047            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11048            assert_eq!(editor.text(cx), expected_text);
11049            assert_eq!(
11050                editor
11051                    .selections
11052                    .ranges::<usize>(&editor.display_snapshot(cx)),
11053                selection_ranges
11054            );
11055        }
11056
11057        assert(
11058            editor,
11059            cx,
11060            indoc! {"
11061            type «» =•
11062            "},
11063        );
11064
11065        assert!(editor.context_menu_visible(), "There should be a matches");
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11074        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11075        assert_eq!(editor.text(cx), expected_text);
11076        assert_eq!(
11077            editor
11078                .selections
11079                .ranges::<usize>(&editor.display_snapshot(cx)),
11080            selection_ranges
11081        );
11082    }
11083
11084    let (text, insertion_ranges) = marked_text_ranges(
11085        indoc! {"
11086            ˇ
11087        "},
11088        false,
11089    );
11090
11091    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11092    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11093
11094    _ = editor.update_in(cx, |editor, window, cx| {
11095        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11096
11097        editor
11098            .insert_snippet(&insertion_ranges, snippet, window, cx)
11099            .unwrap();
11100
11101        assert_state(
11102            editor,
11103            cx,
11104            indoc! {"
11105            type «» = ;•
11106            "},
11107        );
11108
11109        assert!(
11110            editor.context_menu_visible(),
11111            "Context menu should be visible for placeholder choices"
11112        );
11113
11114        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11115
11116        assert_state(
11117            editor,
11118            cx,
11119            indoc! {"
11120            type  = «»;•
11121            "},
11122        );
11123
11124        assert!(
11125            !editor.context_menu_visible(),
11126            "Context menu should be hidden after moving to next tabstop"
11127        );
11128
11129        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11130
11131        assert_state(
11132            editor,
11133            cx,
11134            indoc! {"
11135            type  = ; ˇ
11136            "},
11137        );
11138
11139        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11140
11141        assert_state(
11142            editor,
11143            cx,
11144            indoc! {"
11145            type  = ; ˇ
11146            "},
11147        );
11148    });
11149
11150    _ = editor.update_in(cx, |editor, window, cx| {
11151        editor.select_all(&SelectAll, window, cx);
11152        editor.backspace(&Backspace, window, cx);
11153
11154        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11155        let insertion_ranges = editor
11156            .selections
11157            .all(&editor.display_snapshot(cx))
11158            .iter()
11159            .map(|s| s.range())
11160            .collect::<Vec<_>>();
11161
11162        editor
11163            .insert_snippet(&insertion_ranges, snippet, window, cx)
11164            .unwrap();
11165
11166        assert_state(editor, cx, "fn «» = value;•");
11167
11168        assert!(
11169            editor.context_menu_visible(),
11170            "Context menu should be visible for placeholder choices"
11171        );
11172
11173        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11174
11175        assert_state(editor, cx, "fn  = «valueˇ»;•");
11176
11177        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11178
11179        assert_state(editor, cx, "fn «» = value;•");
11180
11181        assert!(
11182            editor.context_menu_visible(),
11183            "Context menu should be visible again after returning to first tabstop"
11184        );
11185
11186        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11187
11188        assert_state(editor, cx, "fn «» = value;•");
11189    });
11190}
11191
11192#[gpui::test]
11193async fn test_snippets(cx: &mut TestAppContext) {
11194    init_test(cx, |_| {});
11195
11196    let mut cx = EditorTestContext::new(cx).await;
11197
11198    cx.set_state(indoc! {"
11199        a.ˇ b
11200        a.ˇ b
11201        a.ˇ b
11202    "});
11203
11204    cx.update_editor(|editor, window, cx| {
11205        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11206        let insertion_ranges = editor
11207            .selections
11208            .all(&editor.display_snapshot(cx))
11209            .iter()
11210            .map(|s| s.range())
11211            .collect::<Vec<_>>();
11212        editor
11213            .insert_snippet(&insertion_ranges, snippet, window, cx)
11214            .unwrap();
11215    });
11216
11217    cx.assert_editor_state(indoc! {"
11218        a.f(«oneˇ», two, «threeˇ») b
11219        a.f(«oneˇ», two, «threeˇ») b
11220        a.f(«oneˇ», two, «threeˇ») b
11221    "});
11222
11223    // Can't move earlier than the first tab stop
11224    cx.update_editor(|editor, window, cx| {
11225        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11226    });
11227    cx.assert_editor_state(indoc! {"
11228        a.f(«oneˇ», two, «threeˇ») b
11229        a.f(«oneˇ», two, «threeˇ») b
11230        a.f(«oneˇ», two, «threeˇ») b
11231    "});
11232
11233    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11234    cx.assert_editor_state(indoc! {"
11235        a.f(one, «twoˇ», three) b
11236        a.f(one, «twoˇ», three) b
11237        a.f(one, «twoˇ», three) b
11238    "});
11239
11240    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11241    cx.assert_editor_state(indoc! {"
11242        a.f(«oneˇ», two, «threeˇ») b
11243        a.f(«oneˇ», two, «threeˇ») b
11244        a.f(«oneˇ», two, «threeˇ») b
11245    "});
11246
11247    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11248    cx.assert_editor_state(indoc! {"
11249        a.f(one, «twoˇ», three) b
11250        a.f(one, «twoˇ», three) b
11251        a.f(one, «twoˇ», three) b
11252    "});
11253    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11254    cx.assert_editor_state(indoc! {"
11255        a.f(one, two, three)ˇ b
11256        a.f(one, two, three)ˇ b
11257        a.f(one, two, three)ˇ b
11258    "});
11259
11260    // As soon as the last tab stop is reached, snippet state is gone
11261    cx.update_editor(|editor, window, cx| {
11262        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11263    });
11264    cx.assert_editor_state(indoc! {"
11265        a.f(one, two, three)ˇ b
11266        a.f(one, two, three)ˇ b
11267        a.f(one, two, three)ˇ b
11268    "});
11269}
11270
11271#[gpui::test]
11272async fn test_snippet_indentation(cx: &mut TestAppContext) {
11273    init_test(cx, |_| {});
11274
11275    let mut cx = EditorTestContext::new(cx).await;
11276
11277    cx.update_editor(|editor, window, cx| {
11278        let snippet = Snippet::parse(indoc! {"
11279            /*
11280             * Multiline comment with leading indentation
11281             *
11282             * $1
11283             */
11284            $0"})
11285        .unwrap();
11286        let insertion_ranges = editor
11287            .selections
11288            .all(&editor.display_snapshot(cx))
11289            .iter()
11290            .map(|s| s.range())
11291            .collect::<Vec<_>>();
11292        editor
11293            .insert_snippet(&insertion_ranges, snippet, window, cx)
11294            .unwrap();
11295    });
11296
11297    cx.assert_editor_state(indoc! {"
11298        /*
11299         * Multiline comment with leading indentation
11300         *
11301         * ˇ
11302         */
11303    "});
11304
11305    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11306    cx.assert_editor_state(indoc! {"
11307        /*
11308         * Multiline comment with leading indentation
11309         *
11310         *•
11311         */
11312        ˇ"});
11313}
11314
11315#[gpui::test]
11316async fn test_document_format_during_save(cx: &mut TestAppContext) {
11317    init_test(cx, |_| {});
11318
11319    let fs = FakeFs::new(cx.executor());
11320    fs.insert_file(path!("/file.rs"), Default::default()).await;
11321
11322    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11323
11324    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11325    language_registry.add(rust_lang());
11326    let mut fake_servers = language_registry.register_fake_lsp(
11327        "Rust",
11328        FakeLspAdapter {
11329            capabilities: lsp::ServerCapabilities {
11330                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11331                ..Default::default()
11332            },
11333            ..Default::default()
11334        },
11335    );
11336
11337    let buffer = project
11338        .update(cx, |project, cx| {
11339            project.open_local_buffer(path!("/file.rs"), cx)
11340        })
11341        .await
11342        .unwrap();
11343
11344    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11345    let (editor, cx) = cx.add_window_view(|window, cx| {
11346        build_editor_with_project(project.clone(), buffer, window, cx)
11347    });
11348    editor.update_in(cx, |editor, window, cx| {
11349        editor.set_text("one\ntwo\nthree\n", window, cx)
11350    });
11351    assert!(cx.read(|cx| editor.is_dirty(cx)));
11352
11353    cx.executor().start_waiting();
11354    let fake_server = fake_servers.next().await.unwrap();
11355
11356    {
11357        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11358            move |params, _| async move {
11359                assert_eq!(
11360                    params.text_document.uri,
11361                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11362                );
11363                assert_eq!(params.options.tab_size, 4);
11364                Ok(Some(vec![lsp::TextEdit::new(
11365                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11366                    ", ".to_string(),
11367                )]))
11368            },
11369        );
11370        let save = editor
11371            .update_in(cx, |editor, window, cx| {
11372                editor.save(
11373                    SaveOptions {
11374                        format: true,
11375                        autosave: false,
11376                    },
11377                    project.clone(),
11378                    window,
11379                    cx,
11380                )
11381            })
11382            .unwrap();
11383        cx.executor().start_waiting();
11384        save.await;
11385
11386        assert_eq!(
11387            editor.update(cx, |editor, cx| editor.text(cx)),
11388            "one, two\nthree\n"
11389        );
11390        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11391    }
11392
11393    {
11394        editor.update_in(cx, |editor, window, cx| {
11395            editor.set_text("one\ntwo\nthree\n", window, cx)
11396        });
11397        assert!(cx.read(|cx| editor.is_dirty(cx)));
11398
11399        // Ensure we can still save even if formatting hangs.
11400        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11401            move |params, _| async move {
11402                assert_eq!(
11403                    params.text_document.uri,
11404                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11405                );
11406                futures::future::pending::<()>().await;
11407                unreachable!()
11408            },
11409        );
11410        let save = editor
11411            .update_in(cx, |editor, window, cx| {
11412                editor.save(
11413                    SaveOptions {
11414                        format: true,
11415                        autosave: false,
11416                    },
11417                    project.clone(),
11418                    window,
11419                    cx,
11420                )
11421            })
11422            .unwrap();
11423        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11424        cx.executor().start_waiting();
11425        save.await;
11426        assert_eq!(
11427            editor.update(cx, |editor, cx| editor.text(cx)),
11428            "one\ntwo\nthree\n"
11429        );
11430    }
11431
11432    // Set rust language override and assert overridden tabsize is sent to language server
11433    update_test_language_settings(cx, |settings| {
11434        settings.languages.0.insert(
11435            "Rust".into(),
11436            LanguageSettingsContent {
11437                tab_size: NonZeroU32::new(8),
11438                ..Default::default()
11439            },
11440        );
11441    });
11442
11443    {
11444        editor.update_in(cx, |editor, window, cx| {
11445            editor.set_text("somehting_new\n", window, cx)
11446        });
11447        assert!(cx.read(|cx| editor.is_dirty(cx)));
11448        let _formatting_request_signal = fake_server
11449            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11450                assert_eq!(
11451                    params.text_document.uri,
11452                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11453                );
11454                assert_eq!(params.options.tab_size, 8);
11455                Ok(Some(vec![]))
11456            });
11457        let save = editor
11458            .update_in(cx, |editor, window, cx| {
11459                editor.save(
11460                    SaveOptions {
11461                        format: true,
11462                        autosave: false,
11463                    },
11464                    project.clone(),
11465                    window,
11466                    cx,
11467                )
11468            })
11469            .unwrap();
11470        cx.executor().start_waiting();
11471        save.await;
11472    }
11473}
11474
11475#[gpui::test]
11476async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11477    init_test(cx, |settings| {
11478        settings.defaults.ensure_final_newline_on_save = Some(false);
11479    });
11480
11481    let fs = FakeFs::new(cx.executor());
11482    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11483
11484    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11485
11486    let buffer = project
11487        .update(cx, |project, cx| {
11488            project.open_local_buffer(path!("/file.txt"), cx)
11489        })
11490        .await
11491        .unwrap();
11492
11493    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11494    let (editor, cx) = cx.add_window_view(|window, cx| {
11495        build_editor_with_project(project.clone(), buffer, window, cx)
11496    });
11497    editor.update_in(cx, |editor, window, cx| {
11498        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11499            s.select_ranges([0..0])
11500        });
11501    });
11502    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11503
11504    editor.update_in(cx, |editor, window, cx| {
11505        editor.handle_input("\n", window, cx)
11506    });
11507    cx.run_until_parked();
11508    save(&editor, &project, cx).await;
11509    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11510
11511    editor.update_in(cx, |editor, window, cx| {
11512        editor.undo(&Default::default(), window, cx);
11513    });
11514    save(&editor, &project, cx).await;
11515    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11516
11517    editor.update_in(cx, |editor, window, cx| {
11518        editor.redo(&Default::default(), window, cx);
11519    });
11520    cx.run_until_parked();
11521    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11522
11523    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11524        let save = editor
11525            .update_in(cx, |editor, window, cx| {
11526                editor.save(
11527                    SaveOptions {
11528                        format: true,
11529                        autosave: false,
11530                    },
11531                    project.clone(),
11532                    window,
11533                    cx,
11534                )
11535            })
11536            .unwrap();
11537        cx.executor().start_waiting();
11538        save.await;
11539        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11540    }
11541}
11542
11543#[gpui::test]
11544async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11545    init_test(cx, |_| {});
11546
11547    let cols = 4;
11548    let rows = 10;
11549    let sample_text_1 = sample_text(rows, cols, 'a');
11550    assert_eq!(
11551        sample_text_1,
11552        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11553    );
11554    let sample_text_2 = sample_text(rows, cols, 'l');
11555    assert_eq!(
11556        sample_text_2,
11557        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11558    );
11559    let sample_text_3 = sample_text(rows, cols, 'v');
11560    assert_eq!(
11561        sample_text_3,
11562        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11563    );
11564
11565    let fs = FakeFs::new(cx.executor());
11566    fs.insert_tree(
11567        path!("/a"),
11568        json!({
11569            "main.rs": sample_text_1,
11570            "other.rs": sample_text_2,
11571            "lib.rs": sample_text_3,
11572        }),
11573    )
11574    .await;
11575
11576    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11577    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11578    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11579
11580    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11581    language_registry.add(rust_lang());
11582    let mut fake_servers = language_registry.register_fake_lsp(
11583        "Rust",
11584        FakeLspAdapter {
11585            capabilities: lsp::ServerCapabilities {
11586                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11587                ..Default::default()
11588            },
11589            ..Default::default()
11590        },
11591    );
11592
11593    let worktree = project.update(cx, |project, cx| {
11594        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11595        assert_eq!(worktrees.len(), 1);
11596        worktrees.pop().unwrap()
11597    });
11598    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11599
11600    let buffer_1 = project
11601        .update(cx, |project, cx| {
11602            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11603        })
11604        .await
11605        .unwrap();
11606    let buffer_2 = project
11607        .update(cx, |project, cx| {
11608            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11609        })
11610        .await
11611        .unwrap();
11612    let buffer_3 = project
11613        .update(cx, |project, cx| {
11614            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11615        })
11616        .await
11617        .unwrap();
11618
11619    let multi_buffer = cx.new(|cx| {
11620        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11621        multi_buffer.push_excerpts(
11622            buffer_1.clone(),
11623            [
11624                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11625                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11626                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11627            ],
11628            cx,
11629        );
11630        multi_buffer.push_excerpts(
11631            buffer_2.clone(),
11632            [
11633                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11634                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11635                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11636            ],
11637            cx,
11638        );
11639        multi_buffer.push_excerpts(
11640            buffer_3.clone(),
11641            [
11642                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11643                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11644                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11645            ],
11646            cx,
11647        );
11648        multi_buffer
11649    });
11650    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11651        Editor::new(
11652            EditorMode::full(),
11653            multi_buffer,
11654            Some(project.clone()),
11655            window,
11656            cx,
11657        )
11658    });
11659
11660    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11661        editor.change_selections(
11662            SelectionEffects::scroll(Autoscroll::Next),
11663            window,
11664            cx,
11665            |s| s.select_ranges(Some(1..2)),
11666        );
11667        editor.insert("|one|two|three|", window, cx);
11668    });
11669    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11670    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11671        editor.change_selections(
11672            SelectionEffects::scroll(Autoscroll::Next),
11673            window,
11674            cx,
11675            |s| s.select_ranges(Some(60..70)),
11676        );
11677        editor.insert("|four|five|six|", window, cx);
11678    });
11679    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11680
11681    // First two buffers should be edited, but not the third one.
11682    assert_eq!(
11683        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11684        "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}",
11685    );
11686    buffer_1.update(cx, |buffer, _| {
11687        assert!(buffer.is_dirty());
11688        assert_eq!(
11689            buffer.text(),
11690            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11691        )
11692    });
11693    buffer_2.update(cx, |buffer, _| {
11694        assert!(buffer.is_dirty());
11695        assert_eq!(
11696            buffer.text(),
11697            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11698        )
11699    });
11700    buffer_3.update(cx, |buffer, _| {
11701        assert!(!buffer.is_dirty());
11702        assert_eq!(buffer.text(), sample_text_3,)
11703    });
11704    cx.executor().run_until_parked();
11705
11706    cx.executor().start_waiting();
11707    let save = multi_buffer_editor
11708        .update_in(cx, |editor, window, cx| {
11709            editor.save(
11710                SaveOptions {
11711                    format: true,
11712                    autosave: false,
11713                },
11714                project.clone(),
11715                window,
11716                cx,
11717            )
11718        })
11719        .unwrap();
11720
11721    let fake_server = fake_servers.next().await.unwrap();
11722    fake_server
11723        .server
11724        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11725            Ok(Some(vec![lsp::TextEdit::new(
11726                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11727                format!("[{} formatted]", params.text_document.uri),
11728            )]))
11729        })
11730        .detach();
11731    save.await;
11732
11733    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11734    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11735    assert_eq!(
11736        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11737        uri!(
11738            "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}"
11739        ),
11740    );
11741    buffer_1.update(cx, |buffer, _| {
11742        assert!(!buffer.is_dirty());
11743        assert_eq!(
11744            buffer.text(),
11745            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11746        )
11747    });
11748    buffer_2.update(cx, |buffer, _| {
11749        assert!(!buffer.is_dirty());
11750        assert_eq!(
11751            buffer.text(),
11752            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11753        )
11754    });
11755    buffer_3.update(cx, |buffer, _| {
11756        assert!(!buffer.is_dirty());
11757        assert_eq!(buffer.text(), sample_text_3,)
11758    });
11759}
11760
11761#[gpui::test]
11762async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11763    init_test(cx, |_| {});
11764
11765    let fs = FakeFs::new(cx.executor());
11766    fs.insert_tree(
11767        path!("/dir"),
11768        json!({
11769            "file1.rs": "fn main() { println!(\"hello\"); }",
11770            "file2.rs": "fn test() { println!(\"test\"); }",
11771            "file3.rs": "fn other() { println!(\"other\"); }\n",
11772        }),
11773    )
11774    .await;
11775
11776    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11777    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11778    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11779
11780    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11781    language_registry.add(rust_lang());
11782
11783    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11784    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11785
11786    // Open three buffers
11787    let buffer_1 = project
11788        .update(cx, |project, cx| {
11789            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11790        })
11791        .await
11792        .unwrap();
11793    let buffer_2 = project
11794        .update(cx, |project, cx| {
11795            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11796        })
11797        .await
11798        .unwrap();
11799    let buffer_3 = project
11800        .update(cx, |project, cx| {
11801            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11802        })
11803        .await
11804        .unwrap();
11805
11806    // Create a multi-buffer with all three buffers
11807    let multi_buffer = cx.new(|cx| {
11808        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11809        multi_buffer.push_excerpts(
11810            buffer_1.clone(),
11811            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11812            cx,
11813        );
11814        multi_buffer.push_excerpts(
11815            buffer_2.clone(),
11816            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11817            cx,
11818        );
11819        multi_buffer.push_excerpts(
11820            buffer_3.clone(),
11821            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11822            cx,
11823        );
11824        multi_buffer
11825    });
11826
11827    let editor = cx.new_window_entity(|window, cx| {
11828        Editor::new(
11829            EditorMode::full(),
11830            multi_buffer,
11831            Some(project.clone()),
11832            window,
11833            cx,
11834        )
11835    });
11836
11837    // Edit only the first buffer
11838    editor.update_in(cx, |editor, window, cx| {
11839        editor.change_selections(
11840            SelectionEffects::scroll(Autoscroll::Next),
11841            window,
11842            cx,
11843            |s| s.select_ranges(Some(10..10)),
11844        );
11845        editor.insert("// edited", window, cx);
11846    });
11847
11848    // Verify that only buffer 1 is dirty
11849    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11850    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11851    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11852
11853    // Get write counts after file creation (files were created with initial content)
11854    // We expect each file to have been written once during creation
11855    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11856    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11857    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11858
11859    // Perform autosave
11860    let save_task = editor.update_in(cx, |editor, window, cx| {
11861        editor.save(
11862            SaveOptions {
11863                format: true,
11864                autosave: true,
11865            },
11866            project.clone(),
11867            window,
11868            cx,
11869        )
11870    });
11871    save_task.await.unwrap();
11872
11873    // Only the dirty buffer should have been saved
11874    assert_eq!(
11875        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11876        1,
11877        "Buffer 1 was dirty, so it should have been written once during autosave"
11878    );
11879    assert_eq!(
11880        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11881        0,
11882        "Buffer 2 was clean, so it should not have been written during autosave"
11883    );
11884    assert_eq!(
11885        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11886        0,
11887        "Buffer 3 was clean, so it should not have been written during autosave"
11888    );
11889
11890    // Verify buffer states after autosave
11891    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11892    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11893    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11894
11895    // Now perform a manual save (format = true)
11896    let save_task = editor.update_in(cx, |editor, window, cx| {
11897        editor.save(
11898            SaveOptions {
11899                format: true,
11900                autosave: false,
11901            },
11902            project.clone(),
11903            window,
11904            cx,
11905        )
11906    });
11907    save_task.await.unwrap();
11908
11909    // During manual save, clean buffers don't get written to disk
11910    // They just get did_save called for language server notifications
11911    assert_eq!(
11912        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11913        1,
11914        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11915    );
11916    assert_eq!(
11917        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11918        0,
11919        "Buffer 2 should not have been written at all"
11920    );
11921    assert_eq!(
11922        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11923        0,
11924        "Buffer 3 should not have been written at all"
11925    );
11926}
11927
11928async fn setup_range_format_test(
11929    cx: &mut TestAppContext,
11930) -> (
11931    Entity<Project>,
11932    Entity<Editor>,
11933    &mut gpui::VisualTestContext,
11934    lsp::FakeLanguageServer,
11935) {
11936    init_test(cx, |_| {});
11937
11938    let fs = FakeFs::new(cx.executor());
11939    fs.insert_file(path!("/file.rs"), Default::default()).await;
11940
11941    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11942
11943    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11944    language_registry.add(rust_lang());
11945    let mut fake_servers = language_registry.register_fake_lsp(
11946        "Rust",
11947        FakeLspAdapter {
11948            capabilities: lsp::ServerCapabilities {
11949                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11950                ..lsp::ServerCapabilities::default()
11951            },
11952            ..FakeLspAdapter::default()
11953        },
11954    );
11955
11956    let buffer = project
11957        .update(cx, |project, cx| {
11958            project.open_local_buffer(path!("/file.rs"), cx)
11959        })
11960        .await
11961        .unwrap();
11962
11963    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11964    let (editor, cx) = cx.add_window_view(|window, cx| {
11965        build_editor_with_project(project.clone(), buffer, window, cx)
11966    });
11967
11968    cx.executor().start_waiting();
11969    let fake_server = fake_servers.next().await.unwrap();
11970
11971    (project, editor, cx, fake_server)
11972}
11973
11974#[gpui::test]
11975async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11976    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11977
11978    editor.update_in(cx, |editor, window, cx| {
11979        editor.set_text("one\ntwo\nthree\n", window, cx)
11980    });
11981    assert!(cx.read(|cx| editor.is_dirty(cx)));
11982
11983    let save = editor
11984        .update_in(cx, |editor, window, cx| {
11985            editor.save(
11986                SaveOptions {
11987                    format: true,
11988                    autosave: false,
11989                },
11990                project.clone(),
11991                window,
11992                cx,
11993            )
11994        })
11995        .unwrap();
11996    fake_server
11997        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11998            assert_eq!(
11999                params.text_document.uri,
12000                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12001            );
12002            assert_eq!(params.options.tab_size, 4);
12003            Ok(Some(vec![lsp::TextEdit::new(
12004                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12005                ", ".to_string(),
12006            )]))
12007        })
12008        .next()
12009        .await;
12010    cx.executor().start_waiting();
12011    save.await;
12012    assert_eq!(
12013        editor.update(cx, |editor, cx| editor.text(cx)),
12014        "one, two\nthree\n"
12015    );
12016    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12017}
12018
12019#[gpui::test]
12020async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12021    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12022
12023    editor.update_in(cx, |editor, window, cx| {
12024        editor.set_text("one\ntwo\nthree\n", window, cx)
12025    });
12026    assert!(cx.read(|cx| editor.is_dirty(cx)));
12027
12028    // Test that save still works when formatting hangs
12029    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12030        move |params, _| async move {
12031            assert_eq!(
12032                params.text_document.uri,
12033                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12034            );
12035            futures::future::pending::<()>().await;
12036            unreachable!()
12037        },
12038    );
12039    let save = editor
12040        .update_in(cx, |editor, window, cx| {
12041            editor.save(
12042                SaveOptions {
12043                    format: true,
12044                    autosave: false,
12045                },
12046                project.clone(),
12047                window,
12048                cx,
12049            )
12050        })
12051        .unwrap();
12052    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12053    cx.executor().start_waiting();
12054    save.await;
12055    assert_eq!(
12056        editor.update(cx, |editor, cx| editor.text(cx)),
12057        "one\ntwo\nthree\n"
12058    );
12059    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12060}
12061
12062#[gpui::test]
12063async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12064    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12065
12066    // Buffer starts clean, no formatting should be requested
12067    let save = editor
12068        .update_in(cx, |editor, window, cx| {
12069            editor.save(
12070                SaveOptions {
12071                    format: false,
12072                    autosave: false,
12073                },
12074                project.clone(),
12075                window,
12076                cx,
12077            )
12078        })
12079        .unwrap();
12080    let _pending_format_request = fake_server
12081        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12082            panic!("Should not be invoked");
12083        })
12084        .next();
12085    cx.executor().start_waiting();
12086    save.await;
12087    cx.run_until_parked();
12088}
12089
12090#[gpui::test]
12091async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12092    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12093
12094    // Set Rust language override and assert overridden tabsize is sent to language server
12095    update_test_language_settings(cx, |settings| {
12096        settings.languages.0.insert(
12097            "Rust".into(),
12098            LanguageSettingsContent {
12099                tab_size: NonZeroU32::new(8),
12100                ..Default::default()
12101            },
12102        );
12103    });
12104
12105    editor.update_in(cx, |editor, window, cx| {
12106        editor.set_text("something_new\n", window, cx)
12107    });
12108    assert!(cx.read(|cx| editor.is_dirty(cx)));
12109    let save = editor
12110        .update_in(cx, |editor, window, cx| {
12111            editor.save(
12112                SaveOptions {
12113                    format: true,
12114                    autosave: false,
12115                },
12116                project.clone(),
12117                window,
12118                cx,
12119            )
12120        })
12121        .unwrap();
12122    fake_server
12123        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12124            assert_eq!(
12125                params.text_document.uri,
12126                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12127            );
12128            assert_eq!(params.options.tab_size, 8);
12129            Ok(Some(Vec::new()))
12130        })
12131        .next()
12132        .await;
12133    save.await;
12134}
12135
12136#[gpui::test]
12137async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12138    init_test(cx, |settings| {
12139        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12140            settings::LanguageServerFormatterSpecifier::Current,
12141        )))
12142    });
12143
12144    let fs = FakeFs::new(cx.executor());
12145    fs.insert_file(path!("/file.rs"), Default::default()).await;
12146
12147    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12148
12149    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12150    language_registry.add(Arc::new(Language::new(
12151        LanguageConfig {
12152            name: "Rust".into(),
12153            matcher: LanguageMatcher {
12154                path_suffixes: vec!["rs".to_string()],
12155                ..Default::default()
12156            },
12157            ..LanguageConfig::default()
12158        },
12159        Some(tree_sitter_rust::LANGUAGE.into()),
12160    )));
12161    update_test_language_settings(cx, |settings| {
12162        // Enable Prettier formatting for the same buffer, and ensure
12163        // LSP is called instead of Prettier.
12164        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12165    });
12166    let mut fake_servers = language_registry.register_fake_lsp(
12167        "Rust",
12168        FakeLspAdapter {
12169            capabilities: lsp::ServerCapabilities {
12170                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12171                ..Default::default()
12172            },
12173            ..Default::default()
12174        },
12175    );
12176
12177    let buffer = project
12178        .update(cx, |project, cx| {
12179            project.open_local_buffer(path!("/file.rs"), cx)
12180        })
12181        .await
12182        .unwrap();
12183
12184    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12185    let (editor, cx) = cx.add_window_view(|window, cx| {
12186        build_editor_with_project(project.clone(), buffer, window, cx)
12187    });
12188    editor.update_in(cx, |editor, window, cx| {
12189        editor.set_text("one\ntwo\nthree\n", window, cx)
12190    });
12191
12192    cx.executor().start_waiting();
12193    let fake_server = fake_servers.next().await.unwrap();
12194
12195    let format = editor
12196        .update_in(cx, |editor, window, cx| {
12197            editor.perform_format(
12198                project.clone(),
12199                FormatTrigger::Manual,
12200                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12201                window,
12202                cx,
12203            )
12204        })
12205        .unwrap();
12206    fake_server
12207        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12208            assert_eq!(
12209                params.text_document.uri,
12210                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12211            );
12212            assert_eq!(params.options.tab_size, 4);
12213            Ok(Some(vec![lsp::TextEdit::new(
12214                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12215                ", ".to_string(),
12216            )]))
12217        })
12218        .next()
12219        .await;
12220    cx.executor().start_waiting();
12221    format.await;
12222    assert_eq!(
12223        editor.update(cx, |editor, cx| editor.text(cx)),
12224        "one, two\nthree\n"
12225    );
12226
12227    editor.update_in(cx, |editor, window, cx| {
12228        editor.set_text("one\ntwo\nthree\n", window, cx)
12229    });
12230    // Ensure we don't lock if formatting hangs.
12231    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12232        move |params, _| async move {
12233            assert_eq!(
12234                params.text_document.uri,
12235                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12236            );
12237            futures::future::pending::<()>().await;
12238            unreachable!()
12239        },
12240    );
12241    let format = editor
12242        .update_in(cx, |editor, window, cx| {
12243            editor.perform_format(
12244                project,
12245                FormatTrigger::Manual,
12246                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12247                window,
12248                cx,
12249            )
12250        })
12251        .unwrap();
12252    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12253    cx.executor().start_waiting();
12254    format.await;
12255    assert_eq!(
12256        editor.update(cx, |editor, cx| editor.text(cx)),
12257        "one\ntwo\nthree\n"
12258    );
12259}
12260
12261#[gpui::test]
12262async fn test_multiple_formatters(cx: &mut TestAppContext) {
12263    init_test(cx, |settings| {
12264        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12265        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12266            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12267            Formatter::CodeAction("code-action-1".into()),
12268            Formatter::CodeAction("code-action-2".into()),
12269        ]))
12270    });
12271
12272    let fs = FakeFs::new(cx.executor());
12273    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12274        .await;
12275
12276    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12277    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12278    language_registry.add(rust_lang());
12279
12280    let mut fake_servers = language_registry.register_fake_lsp(
12281        "Rust",
12282        FakeLspAdapter {
12283            capabilities: lsp::ServerCapabilities {
12284                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12285                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12286                    commands: vec!["the-command-for-code-action-1".into()],
12287                    ..Default::default()
12288                }),
12289                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12290                ..Default::default()
12291            },
12292            ..Default::default()
12293        },
12294    );
12295
12296    let buffer = project
12297        .update(cx, |project, cx| {
12298            project.open_local_buffer(path!("/file.rs"), cx)
12299        })
12300        .await
12301        .unwrap();
12302
12303    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12304    let (editor, cx) = cx.add_window_view(|window, cx| {
12305        build_editor_with_project(project.clone(), buffer, window, cx)
12306    });
12307
12308    cx.executor().start_waiting();
12309
12310    let fake_server = fake_servers.next().await.unwrap();
12311    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12312        move |_params, _| async move {
12313            Ok(Some(vec![lsp::TextEdit::new(
12314                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12315                "applied-formatting\n".to_string(),
12316            )]))
12317        },
12318    );
12319    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12320        move |params, _| async move {
12321            let requested_code_actions = params.context.only.expect("Expected code action request");
12322            assert_eq!(requested_code_actions.len(), 1);
12323
12324            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12325            let code_action = match requested_code_actions[0].as_str() {
12326                "code-action-1" => lsp::CodeAction {
12327                    kind: Some("code-action-1".into()),
12328                    edit: Some(lsp::WorkspaceEdit::new(
12329                        [(
12330                            uri,
12331                            vec![lsp::TextEdit::new(
12332                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12333                                "applied-code-action-1-edit\n".to_string(),
12334                            )],
12335                        )]
12336                        .into_iter()
12337                        .collect(),
12338                    )),
12339                    command: Some(lsp::Command {
12340                        command: "the-command-for-code-action-1".into(),
12341                        ..Default::default()
12342                    }),
12343                    ..Default::default()
12344                },
12345                "code-action-2" => lsp::CodeAction {
12346                    kind: Some("code-action-2".into()),
12347                    edit: Some(lsp::WorkspaceEdit::new(
12348                        [(
12349                            uri,
12350                            vec![lsp::TextEdit::new(
12351                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12352                                "applied-code-action-2-edit\n".to_string(),
12353                            )],
12354                        )]
12355                        .into_iter()
12356                        .collect(),
12357                    )),
12358                    ..Default::default()
12359                },
12360                req => panic!("Unexpected code action request: {:?}", req),
12361            };
12362            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12363                code_action,
12364            )]))
12365        },
12366    );
12367
12368    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12369        move |params, _| async move { Ok(params) }
12370    });
12371
12372    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12373    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12374        let fake = fake_server.clone();
12375        let lock = command_lock.clone();
12376        move |params, _| {
12377            assert_eq!(params.command, "the-command-for-code-action-1");
12378            let fake = fake.clone();
12379            let lock = lock.clone();
12380            async move {
12381                lock.lock().await;
12382                fake.server
12383                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12384                        label: None,
12385                        edit: lsp::WorkspaceEdit {
12386                            changes: Some(
12387                                [(
12388                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12389                                    vec![lsp::TextEdit {
12390                                        range: lsp::Range::new(
12391                                            lsp::Position::new(0, 0),
12392                                            lsp::Position::new(0, 0),
12393                                        ),
12394                                        new_text: "applied-code-action-1-command\n".into(),
12395                                    }],
12396                                )]
12397                                .into_iter()
12398                                .collect(),
12399                            ),
12400                            ..Default::default()
12401                        },
12402                    })
12403                    .await
12404                    .into_response()
12405                    .unwrap();
12406                Ok(Some(json!(null)))
12407            }
12408        }
12409    });
12410
12411    cx.executor().start_waiting();
12412    editor
12413        .update_in(cx, |editor, window, cx| {
12414            editor.perform_format(
12415                project.clone(),
12416                FormatTrigger::Manual,
12417                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12418                window,
12419                cx,
12420            )
12421        })
12422        .unwrap()
12423        .await;
12424    editor.update(cx, |editor, cx| {
12425        assert_eq!(
12426            editor.text(cx),
12427            r#"
12428                applied-code-action-2-edit
12429                applied-code-action-1-command
12430                applied-code-action-1-edit
12431                applied-formatting
12432                one
12433                two
12434                three
12435            "#
12436            .unindent()
12437        );
12438    });
12439
12440    editor.update_in(cx, |editor, window, cx| {
12441        editor.undo(&Default::default(), window, cx);
12442        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12443    });
12444
12445    // Perform a manual edit while waiting for an LSP command
12446    // that's being run as part of a formatting code action.
12447    let lock_guard = command_lock.lock().await;
12448    let format = editor
12449        .update_in(cx, |editor, window, cx| {
12450            editor.perform_format(
12451                project.clone(),
12452                FormatTrigger::Manual,
12453                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12454                window,
12455                cx,
12456            )
12457        })
12458        .unwrap();
12459    cx.run_until_parked();
12460    editor.update(cx, |editor, cx| {
12461        assert_eq!(
12462            editor.text(cx),
12463            r#"
12464                applied-code-action-1-edit
12465                applied-formatting
12466                one
12467                two
12468                three
12469            "#
12470            .unindent()
12471        );
12472
12473        editor.buffer.update(cx, |buffer, cx| {
12474            let ix = buffer.len(cx);
12475            buffer.edit([(ix..ix, "edited\n")], None, cx);
12476        });
12477    });
12478
12479    // Allow the LSP command to proceed. Because the buffer was edited,
12480    // the second code action will not be run.
12481    drop(lock_guard);
12482    format.await;
12483    editor.update_in(cx, |editor, window, cx| {
12484        assert_eq!(
12485            editor.text(cx),
12486            r#"
12487                applied-code-action-1-command
12488                applied-code-action-1-edit
12489                applied-formatting
12490                one
12491                two
12492                three
12493                edited
12494            "#
12495            .unindent()
12496        );
12497
12498        // The manual edit is undone first, because it is the last thing the user did
12499        // (even though the command completed afterwards).
12500        editor.undo(&Default::default(), window, cx);
12501        assert_eq!(
12502            editor.text(cx),
12503            r#"
12504                applied-code-action-1-command
12505                applied-code-action-1-edit
12506                applied-formatting
12507                one
12508                two
12509                three
12510            "#
12511            .unindent()
12512        );
12513
12514        // All the formatting (including the command, which completed after the manual edit)
12515        // is undone together.
12516        editor.undo(&Default::default(), window, cx);
12517        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12518    });
12519}
12520
12521#[gpui::test]
12522async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12523    init_test(cx, |settings| {
12524        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12525            settings::LanguageServerFormatterSpecifier::Current,
12526        )]))
12527    });
12528
12529    let fs = FakeFs::new(cx.executor());
12530    fs.insert_file(path!("/file.ts"), Default::default()).await;
12531
12532    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12533
12534    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12535    language_registry.add(Arc::new(Language::new(
12536        LanguageConfig {
12537            name: "TypeScript".into(),
12538            matcher: LanguageMatcher {
12539                path_suffixes: vec!["ts".to_string()],
12540                ..Default::default()
12541            },
12542            ..LanguageConfig::default()
12543        },
12544        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12545    )));
12546    update_test_language_settings(cx, |settings| {
12547        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12548    });
12549    let mut fake_servers = language_registry.register_fake_lsp(
12550        "TypeScript",
12551        FakeLspAdapter {
12552            capabilities: lsp::ServerCapabilities {
12553                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12554                ..Default::default()
12555            },
12556            ..Default::default()
12557        },
12558    );
12559
12560    let buffer = project
12561        .update(cx, |project, cx| {
12562            project.open_local_buffer(path!("/file.ts"), cx)
12563        })
12564        .await
12565        .unwrap();
12566
12567    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12568    let (editor, cx) = cx.add_window_view(|window, cx| {
12569        build_editor_with_project(project.clone(), buffer, window, cx)
12570    });
12571    editor.update_in(cx, |editor, window, cx| {
12572        editor.set_text(
12573            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12574            window,
12575            cx,
12576        )
12577    });
12578
12579    cx.executor().start_waiting();
12580    let fake_server = fake_servers.next().await.unwrap();
12581
12582    let format = editor
12583        .update_in(cx, |editor, window, cx| {
12584            editor.perform_code_action_kind(
12585                project.clone(),
12586                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12587                window,
12588                cx,
12589            )
12590        })
12591        .unwrap();
12592    fake_server
12593        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12594            assert_eq!(
12595                params.text_document.uri,
12596                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12597            );
12598            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12599                lsp::CodeAction {
12600                    title: "Organize Imports".to_string(),
12601                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12602                    edit: Some(lsp::WorkspaceEdit {
12603                        changes: Some(
12604                            [(
12605                                params.text_document.uri.clone(),
12606                                vec![lsp::TextEdit::new(
12607                                    lsp::Range::new(
12608                                        lsp::Position::new(1, 0),
12609                                        lsp::Position::new(2, 0),
12610                                    ),
12611                                    "".to_string(),
12612                                )],
12613                            )]
12614                            .into_iter()
12615                            .collect(),
12616                        ),
12617                        ..Default::default()
12618                    }),
12619                    ..Default::default()
12620                },
12621            )]))
12622        })
12623        .next()
12624        .await;
12625    cx.executor().start_waiting();
12626    format.await;
12627    assert_eq!(
12628        editor.update(cx, |editor, cx| editor.text(cx)),
12629        "import { a } from 'module';\n\nconst x = a;\n"
12630    );
12631
12632    editor.update_in(cx, |editor, window, cx| {
12633        editor.set_text(
12634            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12635            window,
12636            cx,
12637        )
12638    });
12639    // Ensure we don't lock if code action hangs.
12640    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12641        move |params, _| async move {
12642            assert_eq!(
12643                params.text_document.uri,
12644                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12645            );
12646            futures::future::pending::<()>().await;
12647            unreachable!()
12648        },
12649    );
12650    let format = editor
12651        .update_in(cx, |editor, window, cx| {
12652            editor.perform_code_action_kind(
12653                project,
12654                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12655                window,
12656                cx,
12657            )
12658        })
12659        .unwrap();
12660    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12661    cx.executor().start_waiting();
12662    format.await;
12663    assert_eq!(
12664        editor.update(cx, |editor, cx| editor.text(cx)),
12665        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12666    );
12667}
12668
12669#[gpui::test]
12670async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12671    init_test(cx, |_| {});
12672
12673    let mut cx = EditorLspTestContext::new_rust(
12674        lsp::ServerCapabilities {
12675            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12676            ..Default::default()
12677        },
12678        cx,
12679    )
12680    .await;
12681
12682    cx.set_state(indoc! {"
12683        one.twoˇ
12684    "});
12685
12686    // The format request takes a long time. When it completes, it inserts
12687    // a newline and an indent before the `.`
12688    cx.lsp
12689        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12690            let executor = cx.background_executor().clone();
12691            async move {
12692                executor.timer(Duration::from_millis(100)).await;
12693                Ok(Some(vec![lsp::TextEdit {
12694                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12695                    new_text: "\n    ".into(),
12696                }]))
12697            }
12698        });
12699
12700    // Submit a format request.
12701    let format_1 = cx
12702        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12703        .unwrap();
12704    cx.executor().run_until_parked();
12705
12706    // Submit a second format request.
12707    let format_2 = cx
12708        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12709        .unwrap();
12710    cx.executor().run_until_parked();
12711
12712    // Wait for both format requests to complete
12713    cx.executor().advance_clock(Duration::from_millis(200));
12714    cx.executor().start_waiting();
12715    format_1.await.unwrap();
12716    cx.executor().start_waiting();
12717    format_2.await.unwrap();
12718
12719    // The formatting edits only happens once.
12720    cx.assert_editor_state(indoc! {"
12721        one
12722            .twoˇ
12723    "});
12724}
12725
12726#[gpui::test]
12727async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12728    init_test(cx, |settings| {
12729        settings.defaults.formatter = Some(FormatterList::default())
12730    });
12731
12732    let mut cx = EditorLspTestContext::new_rust(
12733        lsp::ServerCapabilities {
12734            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12735            ..Default::default()
12736        },
12737        cx,
12738    )
12739    .await;
12740
12741    // Record which buffer changes have been sent to the language server
12742    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12743    cx.lsp
12744        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12745            let buffer_changes = buffer_changes.clone();
12746            move |params, _| {
12747                buffer_changes.lock().extend(
12748                    params
12749                        .content_changes
12750                        .into_iter()
12751                        .map(|e| (e.range.unwrap(), e.text)),
12752                );
12753            }
12754        });
12755    // Handle formatting requests to the language server.
12756    cx.lsp
12757        .set_request_handler::<lsp::request::Formatting, _, _>({
12758            let buffer_changes = buffer_changes.clone();
12759            move |_, _| {
12760                let buffer_changes = buffer_changes.clone();
12761                // Insert blank lines between each line of the buffer.
12762                async move {
12763                    // When formatting is requested, trailing whitespace has already been stripped,
12764                    // and the trailing newline has already been added.
12765                    assert_eq!(
12766                        &buffer_changes.lock()[1..],
12767                        &[
12768                            (
12769                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12770                                "".into()
12771                            ),
12772                            (
12773                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12774                                "".into()
12775                            ),
12776                            (
12777                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12778                                "\n".into()
12779                            ),
12780                        ]
12781                    );
12782
12783                    Ok(Some(vec![
12784                        lsp::TextEdit {
12785                            range: lsp::Range::new(
12786                                lsp::Position::new(1, 0),
12787                                lsp::Position::new(1, 0),
12788                            ),
12789                            new_text: "\n".into(),
12790                        },
12791                        lsp::TextEdit {
12792                            range: lsp::Range::new(
12793                                lsp::Position::new(2, 0),
12794                                lsp::Position::new(2, 0),
12795                            ),
12796                            new_text: "\n".into(),
12797                        },
12798                    ]))
12799                }
12800            }
12801        });
12802
12803    // Set up a buffer white some trailing whitespace and no trailing newline.
12804    cx.set_state(
12805        &[
12806            "one ",   //
12807            "twoˇ",   //
12808            "three ", //
12809            "four",   //
12810        ]
12811        .join("\n"),
12812    );
12813    cx.run_until_parked();
12814
12815    // Submit a format request.
12816    let format = cx
12817        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12818        .unwrap();
12819
12820    cx.run_until_parked();
12821    // After formatting the buffer, the trailing whitespace is stripped,
12822    // a newline is appended, and the edits provided by the language server
12823    // have been applied.
12824    format.await.unwrap();
12825
12826    cx.assert_editor_state(
12827        &[
12828            "one",   //
12829            "",      //
12830            "twoˇ",  //
12831            "",      //
12832            "three", //
12833            "four",  //
12834            "",      //
12835        ]
12836        .join("\n"),
12837    );
12838
12839    // Undoing the formatting undoes the trailing whitespace removal, the
12840    // trailing newline, and the LSP edits.
12841    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12842    cx.assert_editor_state(
12843        &[
12844            "one ",   //
12845            "twoˇ",   //
12846            "three ", //
12847            "four",   //
12848        ]
12849        .join("\n"),
12850    );
12851}
12852
12853#[gpui::test]
12854async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12855    cx: &mut TestAppContext,
12856) {
12857    init_test(cx, |_| {});
12858
12859    cx.update(|cx| {
12860        cx.update_global::<SettingsStore, _>(|settings, cx| {
12861            settings.update_user_settings(cx, |settings| {
12862                settings.editor.auto_signature_help = Some(true);
12863            });
12864        });
12865    });
12866
12867    let mut cx = EditorLspTestContext::new_rust(
12868        lsp::ServerCapabilities {
12869            signature_help_provider: Some(lsp::SignatureHelpOptions {
12870                ..Default::default()
12871            }),
12872            ..Default::default()
12873        },
12874        cx,
12875    )
12876    .await;
12877
12878    let language = Language::new(
12879        LanguageConfig {
12880            name: "Rust".into(),
12881            brackets: BracketPairConfig {
12882                pairs: vec![
12883                    BracketPair {
12884                        start: "{".to_string(),
12885                        end: "}".to_string(),
12886                        close: true,
12887                        surround: true,
12888                        newline: true,
12889                    },
12890                    BracketPair {
12891                        start: "(".to_string(),
12892                        end: ")".to_string(),
12893                        close: true,
12894                        surround: true,
12895                        newline: true,
12896                    },
12897                    BracketPair {
12898                        start: "/*".to_string(),
12899                        end: " */".to_string(),
12900                        close: true,
12901                        surround: true,
12902                        newline: true,
12903                    },
12904                    BracketPair {
12905                        start: "[".to_string(),
12906                        end: "]".to_string(),
12907                        close: false,
12908                        surround: false,
12909                        newline: true,
12910                    },
12911                    BracketPair {
12912                        start: "\"".to_string(),
12913                        end: "\"".to_string(),
12914                        close: true,
12915                        surround: true,
12916                        newline: false,
12917                    },
12918                    BracketPair {
12919                        start: "<".to_string(),
12920                        end: ">".to_string(),
12921                        close: false,
12922                        surround: true,
12923                        newline: true,
12924                    },
12925                ],
12926                ..Default::default()
12927            },
12928            autoclose_before: "})]".to_string(),
12929            ..Default::default()
12930        },
12931        Some(tree_sitter_rust::LANGUAGE.into()),
12932    );
12933    let language = Arc::new(language);
12934
12935    cx.language_registry().add(language.clone());
12936    cx.update_buffer(|buffer, cx| {
12937        buffer.set_language(Some(language), cx);
12938    });
12939
12940    cx.set_state(
12941        &r#"
12942            fn main() {
12943                sampleˇ
12944            }
12945        "#
12946        .unindent(),
12947    );
12948
12949    cx.update_editor(|editor, window, cx| {
12950        editor.handle_input("(", window, cx);
12951    });
12952    cx.assert_editor_state(
12953        &"
12954            fn main() {
12955                sample(ˇ)
12956            }
12957        "
12958        .unindent(),
12959    );
12960
12961    let mocked_response = lsp::SignatureHelp {
12962        signatures: vec![lsp::SignatureInformation {
12963            label: "fn sample(param1: u8, param2: u8)".to_string(),
12964            documentation: None,
12965            parameters: Some(vec![
12966                lsp::ParameterInformation {
12967                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12968                    documentation: None,
12969                },
12970                lsp::ParameterInformation {
12971                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12972                    documentation: None,
12973                },
12974            ]),
12975            active_parameter: None,
12976        }],
12977        active_signature: Some(0),
12978        active_parameter: Some(0),
12979    };
12980    handle_signature_help_request(&mut cx, mocked_response).await;
12981
12982    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12983        .await;
12984
12985    cx.editor(|editor, _, _| {
12986        let signature_help_state = editor.signature_help_state.popover().cloned();
12987        let signature = signature_help_state.unwrap();
12988        assert_eq!(
12989            signature.signatures[signature.current_signature].label,
12990            "fn sample(param1: u8, param2: u8)"
12991        );
12992    });
12993}
12994
12995#[gpui::test]
12996async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12997    init_test(cx, |_| {});
12998
12999    cx.update(|cx| {
13000        cx.update_global::<SettingsStore, _>(|settings, cx| {
13001            settings.update_user_settings(cx, |settings| {
13002                settings.editor.auto_signature_help = Some(false);
13003                settings.editor.show_signature_help_after_edits = Some(false);
13004            });
13005        });
13006    });
13007
13008    let mut cx = EditorLspTestContext::new_rust(
13009        lsp::ServerCapabilities {
13010            signature_help_provider: Some(lsp::SignatureHelpOptions {
13011                ..Default::default()
13012            }),
13013            ..Default::default()
13014        },
13015        cx,
13016    )
13017    .await;
13018
13019    let language = Language::new(
13020        LanguageConfig {
13021            name: "Rust".into(),
13022            brackets: BracketPairConfig {
13023                pairs: vec![
13024                    BracketPair {
13025                        start: "{".to_string(),
13026                        end: "}".to_string(),
13027                        close: true,
13028                        surround: true,
13029                        newline: true,
13030                    },
13031                    BracketPair {
13032                        start: "(".to_string(),
13033                        end: ")".to_string(),
13034                        close: true,
13035                        surround: true,
13036                        newline: true,
13037                    },
13038                    BracketPair {
13039                        start: "/*".to_string(),
13040                        end: " */".to_string(),
13041                        close: true,
13042                        surround: true,
13043                        newline: true,
13044                    },
13045                    BracketPair {
13046                        start: "[".to_string(),
13047                        end: "]".to_string(),
13048                        close: false,
13049                        surround: false,
13050                        newline: true,
13051                    },
13052                    BracketPair {
13053                        start: "\"".to_string(),
13054                        end: "\"".to_string(),
13055                        close: true,
13056                        surround: true,
13057                        newline: false,
13058                    },
13059                    BracketPair {
13060                        start: "<".to_string(),
13061                        end: ">".to_string(),
13062                        close: false,
13063                        surround: true,
13064                        newline: true,
13065                    },
13066                ],
13067                ..Default::default()
13068            },
13069            autoclose_before: "})]".to_string(),
13070            ..Default::default()
13071        },
13072        Some(tree_sitter_rust::LANGUAGE.into()),
13073    );
13074    let language = Arc::new(language);
13075
13076    cx.language_registry().add(language.clone());
13077    cx.update_buffer(|buffer, cx| {
13078        buffer.set_language(Some(language), cx);
13079    });
13080
13081    // Ensure that signature_help is not called when no signature help is enabled.
13082    cx.set_state(
13083        &r#"
13084            fn main() {
13085                sampleˇ
13086            }
13087        "#
13088        .unindent(),
13089    );
13090    cx.update_editor(|editor, window, cx| {
13091        editor.handle_input("(", window, cx);
13092    });
13093    cx.assert_editor_state(
13094        &"
13095            fn main() {
13096                sample(ˇ)
13097            }
13098        "
13099        .unindent(),
13100    );
13101    cx.editor(|editor, _, _| {
13102        assert!(editor.signature_help_state.task().is_none());
13103    });
13104
13105    let mocked_response = lsp::SignatureHelp {
13106        signatures: vec![lsp::SignatureInformation {
13107            label: "fn sample(param1: u8, param2: u8)".to_string(),
13108            documentation: None,
13109            parameters: Some(vec![
13110                lsp::ParameterInformation {
13111                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13112                    documentation: None,
13113                },
13114                lsp::ParameterInformation {
13115                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13116                    documentation: None,
13117                },
13118            ]),
13119            active_parameter: None,
13120        }],
13121        active_signature: Some(0),
13122        active_parameter: Some(0),
13123    };
13124
13125    // Ensure that signature_help is called when enabled afte edits
13126    cx.update(|_, cx| {
13127        cx.update_global::<SettingsStore, _>(|settings, cx| {
13128            settings.update_user_settings(cx, |settings| {
13129                settings.editor.auto_signature_help = Some(false);
13130                settings.editor.show_signature_help_after_edits = Some(true);
13131            });
13132        });
13133    });
13134    cx.set_state(
13135        &r#"
13136            fn main() {
13137                sampleˇ
13138            }
13139        "#
13140        .unindent(),
13141    );
13142    cx.update_editor(|editor, window, cx| {
13143        editor.handle_input("(", window, cx);
13144    });
13145    cx.assert_editor_state(
13146        &"
13147            fn main() {
13148                sample(ˇ)
13149            }
13150        "
13151        .unindent(),
13152    );
13153    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13154    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13155        .await;
13156    cx.update_editor(|editor, _, _| {
13157        let signature_help_state = editor.signature_help_state.popover().cloned();
13158        assert!(signature_help_state.is_some());
13159        let signature = signature_help_state.unwrap();
13160        assert_eq!(
13161            signature.signatures[signature.current_signature].label,
13162            "fn sample(param1: u8, param2: u8)"
13163        );
13164        editor.signature_help_state = SignatureHelpState::default();
13165    });
13166
13167    // Ensure that signature_help is called when auto signature help override is enabled
13168    cx.update(|_, cx| {
13169        cx.update_global::<SettingsStore, _>(|settings, cx| {
13170            settings.update_user_settings(cx, |settings| {
13171                settings.editor.auto_signature_help = Some(true);
13172                settings.editor.show_signature_help_after_edits = Some(false);
13173            });
13174        });
13175    });
13176    cx.set_state(
13177        &r#"
13178            fn main() {
13179                sampleˇ
13180            }
13181        "#
13182        .unindent(),
13183    );
13184    cx.update_editor(|editor, window, cx| {
13185        editor.handle_input("(", window, cx);
13186    });
13187    cx.assert_editor_state(
13188        &"
13189            fn main() {
13190                sample(ˇ)
13191            }
13192        "
13193        .unindent(),
13194    );
13195    handle_signature_help_request(&mut cx, mocked_response).await;
13196    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13197        .await;
13198    cx.editor(|editor, _, _| {
13199        let signature_help_state = editor.signature_help_state.popover().cloned();
13200        assert!(signature_help_state.is_some());
13201        let signature = signature_help_state.unwrap();
13202        assert_eq!(
13203            signature.signatures[signature.current_signature].label,
13204            "fn sample(param1: u8, param2: u8)"
13205        );
13206    });
13207}
13208
13209#[gpui::test]
13210async fn test_signature_help(cx: &mut TestAppContext) {
13211    init_test(cx, |_| {});
13212    cx.update(|cx| {
13213        cx.update_global::<SettingsStore, _>(|settings, cx| {
13214            settings.update_user_settings(cx, |settings| {
13215                settings.editor.auto_signature_help = Some(true);
13216            });
13217        });
13218    });
13219
13220    let mut cx = EditorLspTestContext::new_rust(
13221        lsp::ServerCapabilities {
13222            signature_help_provider: Some(lsp::SignatureHelpOptions {
13223                ..Default::default()
13224            }),
13225            ..Default::default()
13226        },
13227        cx,
13228    )
13229    .await;
13230
13231    // A test that directly calls `show_signature_help`
13232    cx.update_editor(|editor, window, cx| {
13233        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13234    });
13235
13236    let mocked_response = lsp::SignatureHelp {
13237        signatures: vec![lsp::SignatureInformation {
13238            label: "fn sample(param1: u8, param2: u8)".to_string(),
13239            documentation: None,
13240            parameters: Some(vec![
13241                lsp::ParameterInformation {
13242                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13243                    documentation: None,
13244                },
13245                lsp::ParameterInformation {
13246                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13247                    documentation: None,
13248                },
13249            ]),
13250            active_parameter: None,
13251        }],
13252        active_signature: Some(0),
13253        active_parameter: Some(0),
13254    };
13255    handle_signature_help_request(&mut cx, mocked_response).await;
13256
13257    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13258        .await;
13259
13260    cx.editor(|editor, _, _| {
13261        let signature_help_state = editor.signature_help_state.popover().cloned();
13262        assert!(signature_help_state.is_some());
13263        let signature = signature_help_state.unwrap();
13264        assert_eq!(
13265            signature.signatures[signature.current_signature].label,
13266            "fn sample(param1: u8, param2: u8)"
13267        );
13268    });
13269
13270    // When exiting outside from inside the brackets, `signature_help` is closed.
13271    cx.set_state(indoc! {"
13272        fn main() {
13273            sample(ˇ);
13274        }
13275
13276        fn sample(param1: u8, param2: u8) {}
13277    "});
13278
13279    cx.update_editor(|editor, window, cx| {
13280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13281            s.select_ranges([0..0])
13282        });
13283    });
13284
13285    let mocked_response = lsp::SignatureHelp {
13286        signatures: Vec::new(),
13287        active_signature: None,
13288        active_parameter: None,
13289    };
13290    handle_signature_help_request(&mut cx, mocked_response).await;
13291
13292    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13293        .await;
13294
13295    cx.editor(|editor, _, _| {
13296        assert!(!editor.signature_help_state.is_shown());
13297    });
13298
13299    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13300    cx.set_state(indoc! {"
13301        fn main() {
13302            sample(ˇ);
13303        }
13304
13305        fn sample(param1: u8, param2: u8) {}
13306    "});
13307
13308    let mocked_response = lsp::SignatureHelp {
13309        signatures: vec![lsp::SignatureInformation {
13310            label: "fn sample(param1: u8, param2: u8)".to_string(),
13311            documentation: None,
13312            parameters: Some(vec![
13313                lsp::ParameterInformation {
13314                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13315                    documentation: None,
13316                },
13317                lsp::ParameterInformation {
13318                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13319                    documentation: None,
13320                },
13321            ]),
13322            active_parameter: None,
13323        }],
13324        active_signature: Some(0),
13325        active_parameter: Some(0),
13326    };
13327    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13328    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13329        .await;
13330    cx.editor(|editor, _, _| {
13331        assert!(editor.signature_help_state.is_shown());
13332    });
13333
13334    // Restore the popover with more parameter input
13335    cx.set_state(indoc! {"
13336        fn main() {
13337            sample(param1, param2ˇ);
13338        }
13339
13340        fn sample(param1: u8, param2: u8) {}
13341    "});
13342
13343    let mocked_response = lsp::SignatureHelp {
13344        signatures: vec![lsp::SignatureInformation {
13345            label: "fn sample(param1: u8, param2: u8)".to_string(),
13346            documentation: None,
13347            parameters: Some(vec![
13348                lsp::ParameterInformation {
13349                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13350                    documentation: None,
13351                },
13352                lsp::ParameterInformation {
13353                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13354                    documentation: None,
13355                },
13356            ]),
13357            active_parameter: None,
13358        }],
13359        active_signature: Some(0),
13360        active_parameter: Some(1),
13361    };
13362    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13363    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13364        .await;
13365
13366    // When selecting a range, the popover is gone.
13367    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13368    cx.update_editor(|editor, window, cx| {
13369        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13370            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13371        })
13372    });
13373    cx.assert_editor_state(indoc! {"
13374        fn main() {
13375            sample(param1, «ˇparam2»);
13376        }
13377
13378        fn sample(param1: u8, param2: u8) {}
13379    "});
13380    cx.editor(|editor, _, _| {
13381        assert!(!editor.signature_help_state.is_shown());
13382    });
13383
13384    // When unselecting again, the popover is back if within the brackets.
13385    cx.update_editor(|editor, window, cx| {
13386        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13387            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13388        })
13389    });
13390    cx.assert_editor_state(indoc! {"
13391        fn main() {
13392            sample(param1, ˇparam2);
13393        }
13394
13395        fn sample(param1: u8, param2: u8) {}
13396    "});
13397    handle_signature_help_request(&mut cx, mocked_response).await;
13398    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13399        .await;
13400    cx.editor(|editor, _, _| {
13401        assert!(editor.signature_help_state.is_shown());
13402    });
13403
13404    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13405    cx.update_editor(|editor, window, cx| {
13406        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13407            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13408            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13409        })
13410    });
13411    cx.assert_editor_state(indoc! {"
13412        fn main() {
13413            sample(param1, ˇparam2);
13414        }
13415
13416        fn sample(param1: u8, param2: u8) {}
13417    "});
13418
13419    let mocked_response = lsp::SignatureHelp {
13420        signatures: vec![lsp::SignatureInformation {
13421            label: "fn sample(param1: u8, param2: u8)".to_string(),
13422            documentation: None,
13423            parameters: Some(vec![
13424                lsp::ParameterInformation {
13425                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13426                    documentation: None,
13427                },
13428                lsp::ParameterInformation {
13429                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13430                    documentation: None,
13431                },
13432            ]),
13433            active_parameter: None,
13434        }],
13435        active_signature: Some(0),
13436        active_parameter: Some(1),
13437    };
13438    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13439    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13440        .await;
13441    cx.update_editor(|editor, _, cx| {
13442        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13443    });
13444    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13445        .await;
13446    cx.update_editor(|editor, window, cx| {
13447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13448            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13449        })
13450    });
13451    cx.assert_editor_state(indoc! {"
13452        fn main() {
13453            sample(param1, «ˇparam2»);
13454        }
13455
13456        fn sample(param1: u8, param2: u8) {}
13457    "});
13458    cx.update_editor(|editor, window, cx| {
13459        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13460            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13461        })
13462    });
13463    cx.assert_editor_state(indoc! {"
13464        fn main() {
13465            sample(param1, ˇparam2);
13466        }
13467
13468        fn sample(param1: u8, param2: u8) {}
13469    "});
13470    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13471        .await;
13472}
13473
13474#[gpui::test]
13475async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13476    init_test(cx, |_| {});
13477
13478    let mut cx = EditorLspTestContext::new_rust(
13479        lsp::ServerCapabilities {
13480            signature_help_provider: Some(lsp::SignatureHelpOptions {
13481                ..Default::default()
13482            }),
13483            ..Default::default()
13484        },
13485        cx,
13486    )
13487    .await;
13488
13489    cx.set_state(indoc! {"
13490        fn main() {
13491            overloadedˇ
13492        }
13493    "});
13494
13495    cx.update_editor(|editor, window, cx| {
13496        editor.handle_input("(", window, cx);
13497        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13498    });
13499
13500    // Mock response with 3 signatures
13501    let mocked_response = lsp::SignatureHelp {
13502        signatures: vec![
13503            lsp::SignatureInformation {
13504                label: "fn overloaded(x: i32)".to_string(),
13505                documentation: None,
13506                parameters: Some(vec![lsp::ParameterInformation {
13507                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13508                    documentation: None,
13509                }]),
13510                active_parameter: None,
13511            },
13512            lsp::SignatureInformation {
13513                label: "fn overloaded(x: i32, y: i32)".to_string(),
13514                documentation: None,
13515                parameters: Some(vec![
13516                    lsp::ParameterInformation {
13517                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13518                        documentation: None,
13519                    },
13520                    lsp::ParameterInformation {
13521                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13522                        documentation: None,
13523                    },
13524                ]),
13525                active_parameter: None,
13526            },
13527            lsp::SignatureInformation {
13528                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13529                documentation: None,
13530                parameters: Some(vec![
13531                    lsp::ParameterInformation {
13532                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13533                        documentation: None,
13534                    },
13535                    lsp::ParameterInformation {
13536                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13537                        documentation: None,
13538                    },
13539                    lsp::ParameterInformation {
13540                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13541                        documentation: None,
13542                    },
13543                ]),
13544                active_parameter: None,
13545            },
13546        ],
13547        active_signature: Some(1),
13548        active_parameter: Some(0),
13549    };
13550    handle_signature_help_request(&mut cx, mocked_response).await;
13551
13552    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13553        .await;
13554
13555    // Verify we have multiple signatures and the right one is selected
13556    cx.editor(|editor, _, _| {
13557        let popover = editor.signature_help_state.popover().cloned().unwrap();
13558        assert_eq!(popover.signatures.len(), 3);
13559        // active_signature was 1, so that should be the current
13560        assert_eq!(popover.current_signature, 1);
13561        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13562        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13563        assert_eq!(
13564            popover.signatures[2].label,
13565            "fn overloaded(x: i32, y: i32, z: i32)"
13566        );
13567    });
13568
13569    // Test navigation functionality
13570    cx.update_editor(|editor, window, cx| {
13571        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13572    });
13573
13574    cx.editor(|editor, _, _| {
13575        let popover = editor.signature_help_state.popover().cloned().unwrap();
13576        assert_eq!(popover.current_signature, 2);
13577    });
13578
13579    // Test wrap around
13580    cx.update_editor(|editor, window, cx| {
13581        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13582    });
13583
13584    cx.editor(|editor, _, _| {
13585        let popover = editor.signature_help_state.popover().cloned().unwrap();
13586        assert_eq!(popover.current_signature, 0);
13587    });
13588
13589    // Test previous navigation
13590    cx.update_editor(|editor, window, cx| {
13591        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13592    });
13593
13594    cx.editor(|editor, _, _| {
13595        let popover = editor.signature_help_state.popover().cloned().unwrap();
13596        assert_eq!(popover.current_signature, 2);
13597    });
13598}
13599
13600#[gpui::test]
13601async fn test_completion_mode(cx: &mut TestAppContext) {
13602    init_test(cx, |_| {});
13603    let mut cx = EditorLspTestContext::new_rust(
13604        lsp::ServerCapabilities {
13605            completion_provider: Some(lsp::CompletionOptions {
13606                resolve_provider: Some(true),
13607                ..Default::default()
13608            }),
13609            ..Default::default()
13610        },
13611        cx,
13612    )
13613    .await;
13614
13615    struct Run {
13616        run_description: &'static str,
13617        initial_state: String,
13618        buffer_marked_text: String,
13619        completion_label: &'static str,
13620        completion_text: &'static str,
13621        expected_with_insert_mode: String,
13622        expected_with_replace_mode: String,
13623        expected_with_replace_subsequence_mode: String,
13624        expected_with_replace_suffix_mode: String,
13625    }
13626
13627    let runs = [
13628        Run {
13629            run_description: "Start of word matches completion text",
13630            initial_state: "before ediˇ after".into(),
13631            buffer_marked_text: "before <edi|> after".into(),
13632            completion_label: "editor",
13633            completion_text: "editor",
13634            expected_with_insert_mode: "before editorˇ after".into(),
13635            expected_with_replace_mode: "before editorˇ after".into(),
13636            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13637            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13638        },
13639        Run {
13640            run_description: "Accept same text at the middle of the word",
13641            initial_state: "before ediˇtor after".into(),
13642            buffer_marked_text: "before <edi|tor> after".into(),
13643            completion_label: "editor",
13644            completion_text: "editor",
13645            expected_with_insert_mode: "before editorˇtor after".into(),
13646            expected_with_replace_mode: "before editorˇ after".into(),
13647            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13648            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13649        },
13650        Run {
13651            run_description: "End of word matches completion text -- cursor at end",
13652            initial_state: "before torˇ after".into(),
13653            buffer_marked_text: "before <tor|> after".into(),
13654            completion_label: "editor",
13655            completion_text: "editor",
13656            expected_with_insert_mode: "before editorˇ after".into(),
13657            expected_with_replace_mode: "before editorˇ after".into(),
13658            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13659            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13660        },
13661        Run {
13662            run_description: "End of word matches completion text -- cursor at start",
13663            initial_state: "before ˇtor after".into(),
13664            buffer_marked_text: "before <|tor> after".into(),
13665            completion_label: "editor",
13666            completion_text: "editor",
13667            expected_with_insert_mode: "before editorˇtor after".into(),
13668            expected_with_replace_mode: "before editorˇ after".into(),
13669            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13670            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13671        },
13672        Run {
13673            run_description: "Prepend text containing whitespace",
13674            initial_state: "pˇfield: bool".into(),
13675            buffer_marked_text: "<p|field>: bool".into(),
13676            completion_label: "pub ",
13677            completion_text: "pub ",
13678            expected_with_insert_mode: "pub ˇfield: bool".into(),
13679            expected_with_replace_mode: "pub ˇ: bool".into(),
13680            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13681            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13682        },
13683        Run {
13684            run_description: "Add element to start of list",
13685            initial_state: "[element_ˇelement_2]".into(),
13686            buffer_marked_text: "[<element_|element_2>]".into(),
13687            completion_label: "element_1",
13688            completion_text: "element_1",
13689            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13690            expected_with_replace_mode: "[element_1ˇ]".into(),
13691            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13692            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13693        },
13694        Run {
13695            run_description: "Add element to start of list -- first and second elements are equal",
13696            initial_state: "[elˇelement]".into(),
13697            buffer_marked_text: "[<el|element>]".into(),
13698            completion_label: "element",
13699            completion_text: "element",
13700            expected_with_insert_mode: "[elementˇelement]".into(),
13701            expected_with_replace_mode: "[elementˇ]".into(),
13702            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13703            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13704        },
13705        Run {
13706            run_description: "Ends with matching suffix",
13707            initial_state: "SubˇError".into(),
13708            buffer_marked_text: "<Sub|Error>".into(),
13709            completion_label: "SubscriptionError",
13710            completion_text: "SubscriptionError",
13711            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13712            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13713            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13714            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13715        },
13716        Run {
13717            run_description: "Suffix is a subsequence -- contiguous",
13718            initial_state: "SubˇErr".into(),
13719            buffer_marked_text: "<Sub|Err>".into(),
13720            completion_label: "SubscriptionError",
13721            completion_text: "SubscriptionError",
13722            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13723            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13724            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13725            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13726        },
13727        Run {
13728            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13729            initial_state: "Suˇscrirr".into(),
13730            buffer_marked_text: "<Su|scrirr>".into(),
13731            completion_label: "SubscriptionError",
13732            completion_text: "SubscriptionError",
13733            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13734            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13735            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13736            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13737        },
13738        Run {
13739            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13740            initial_state: "foo(indˇix)".into(),
13741            buffer_marked_text: "foo(<ind|ix>)".into(),
13742            completion_label: "node_index",
13743            completion_text: "node_index",
13744            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13745            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13746            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13747            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13748        },
13749        Run {
13750            run_description: "Replace range ends before cursor - should extend to cursor",
13751            initial_state: "before editˇo after".into(),
13752            buffer_marked_text: "before <{ed}>it|o after".into(),
13753            completion_label: "editor",
13754            completion_text: "editor",
13755            expected_with_insert_mode: "before editorˇo after".into(),
13756            expected_with_replace_mode: "before editorˇo after".into(),
13757            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13758            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13759        },
13760        Run {
13761            run_description: "Uses label for suffix matching",
13762            initial_state: "before ediˇtor after".into(),
13763            buffer_marked_text: "before <edi|tor> after".into(),
13764            completion_label: "editor",
13765            completion_text: "editor()",
13766            expected_with_insert_mode: "before editor()ˇtor after".into(),
13767            expected_with_replace_mode: "before editor()ˇ after".into(),
13768            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13769            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13770        },
13771        Run {
13772            run_description: "Case insensitive subsequence and suffix matching",
13773            initial_state: "before EDiˇtoR after".into(),
13774            buffer_marked_text: "before <EDi|toR> after".into(),
13775            completion_label: "editor",
13776            completion_text: "editor",
13777            expected_with_insert_mode: "before editorˇtoR after".into(),
13778            expected_with_replace_mode: "before editorˇ after".into(),
13779            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13780            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13781        },
13782    ];
13783
13784    for run in runs {
13785        let run_variations = [
13786            (LspInsertMode::Insert, run.expected_with_insert_mode),
13787            (LspInsertMode::Replace, run.expected_with_replace_mode),
13788            (
13789                LspInsertMode::ReplaceSubsequence,
13790                run.expected_with_replace_subsequence_mode,
13791            ),
13792            (
13793                LspInsertMode::ReplaceSuffix,
13794                run.expected_with_replace_suffix_mode,
13795            ),
13796        ];
13797
13798        for (lsp_insert_mode, expected_text) in run_variations {
13799            eprintln!(
13800                "run = {:?}, mode = {lsp_insert_mode:.?}",
13801                run.run_description,
13802            );
13803
13804            update_test_language_settings(&mut cx, |settings| {
13805                settings.defaults.completions = Some(CompletionSettingsContent {
13806                    lsp_insert_mode: Some(lsp_insert_mode),
13807                    words: Some(WordsCompletionMode::Disabled),
13808                    words_min_length: Some(0),
13809                    ..Default::default()
13810                });
13811            });
13812
13813            cx.set_state(&run.initial_state);
13814            cx.update_editor(|editor, window, cx| {
13815                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13816            });
13817
13818            let counter = Arc::new(AtomicUsize::new(0));
13819            handle_completion_request_with_insert_and_replace(
13820                &mut cx,
13821                &run.buffer_marked_text,
13822                vec![(run.completion_label, run.completion_text)],
13823                counter.clone(),
13824            )
13825            .await;
13826            cx.condition(|editor, _| editor.context_menu_visible())
13827                .await;
13828            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13829
13830            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13831                editor
13832                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13833                    .unwrap()
13834            });
13835            cx.assert_editor_state(&expected_text);
13836            handle_resolve_completion_request(&mut cx, None).await;
13837            apply_additional_edits.await.unwrap();
13838        }
13839    }
13840}
13841
13842#[gpui::test]
13843async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13844    init_test(cx, |_| {});
13845    let mut cx = EditorLspTestContext::new_rust(
13846        lsp::ServerCapabilities {
13847            completion_provider: Some(lsp::CompletionOptions {
13848                resolve_provider: Some(true),
13849                ..Default::default()
13850            }),
13851            ..Default::default()
13852        },
13853        cx,
13854    )
13855    .await;
13856
13857    let initial_state = "SubˇError";
13858    let buffer_marked_text = "<Sub|Error>";
13859    let completion_text = "SubscriptionError";
13860    let expected_with_insert_mode = "SubscriptionErrorˇError";
13861    let expected_with_replace_mode = "SubscriptionErrorˇ";
13862
13863    update_test_language_settings(&mut cx, |settings| {
13864        settings.defaults.completions = Some(CompletionSettingsContent {
13865            words: Some(WordsCompletionMode::Disabled),
13866            words_min_length: Some(0),
13867            // set the opposite here to ensure that the action is overriding the default behavior
13868            lsp_insert_mode: Some(LspInsertMode::Insert),
13869            ..Default::default()
13870        });
13871    });
13872
13873    cx.set_state(initial_state);
13874    cx.update_editor(|editor, window, cx| {
13875        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13876    });
13877
13878    let counter = Arc::new(AtomicUsize::new(0));
13879    handle_completion_request_with_insert_and_replace(
13880        &mut cx,
13881        buffer_marked_text,
13882        vec![(completion_text, completion_text)],
13883        counter.clone(),
13884    )
13885    .await;
13886    cx.condition(|editor, _| editor.context_menu_visible())
13887        .await;
13888    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13889
13890    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13891        editor
13892            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13893            .unwrap()
13894    });
13895    cx.assert_editor_state(expected_with_replace_mode);
13896    handle_resolve_completion_request(&mut cx, None).await;
13897    apply_additional_edits.await.unwrap();
13898
13899    update_test_language_settings(&mut cx, |settings| {
13900        settings.defaults.completions = Some(CompletionSettingsContent {
13901            words: Some(WordsCompletionMode::Disabled),
13902            words_min_length: Some(0),
13903            // set the opposite here to ensure that the action is overriding the default behavior
13904            lsp_insert_mode: Some(LspInsertMode::Replace),
13905            ..Default::default()
13906        });
13907    });
13908
13909    cx.set_state(initial_state);
13910    cx.update_editor(|editor, window, cx| {
13911        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13912    });
13913    handle_completion_request_with_insert_and_replace(
13914        &mut cx,
13915        buffer_marked_text,
13916        vec![(completion_text, completion_text)],
13917        counter.clone(),
13918    )
13919    .await;
13920    cx.condition(|editor, _| editor.context_menu_visible())
13921        .await;
13922    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13923
13924    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13925        editor
13926            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13927            .unwrap()
13928    });
13929    cx.assert_editor_state(expected_with_insert_mode);
13930    handle_resolve_completion_request(&mut cx, None).await;
13931    apply_additional_edits.await.unwrap();
13932}
13933
13934#[gpui::test]
13935async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13936    init_test(cx, |_| {});
13937    let mut cx = EditorLspTestContext::new_rust(
13938        lsp::ServerCapabilities {
13939            completion_provider: Some(lsp::CompletionOptions {
13940                resolve_provider: Some(true),
13941                ..Default::default()
13942            }),
13943            ..Default::default()
13944        },
13945        cx,
13946    )
13947    .await;
13948
13949    // scenario: surrounding text matches completion text
13950    let completion_text = "to_offset";
13951    let initial_state = indoc! {"
13952        1. buf.to_offˇsuffix
13953        2. buf.to_offˇsuf
13954        3. buf.to_offˇfix
13955        4. buf.to_offˇ
13956        5. into_offˇensive
13957        6. ˇsuffix
13958        7. let ˇ //
13959        8. aaˇzz
13960        9. buf.to_off«zzzzzˇ»suffix
13961        10. buf.«ˇzzzzz»suffix
13962        11. to_off«ˇzzzzz»
13963
13964        buf.to_offˇsuffix  // newest cursor
13965    "};
13966    let completion_marked_buffer = indoc! {"
13967        1. buf.to_offsuffix
13968        2. buf.to_offsuf
13969        3. buf.to_offfix
13970        4. buf.to_off
13971        5. into_offensive
13972        6. suffix
13973        7. let  //
13974        8. aazz
13975        9. buf.to_offzzzzzsuffix
13976        10. buf.zzzzzsuffix
13977        11. to_offzzzzz
13978
13979        buf.<to_off|suffix>  // newest cursor
13980    "};
13981    let expected = indoc! {"
13982        1. buf.to_offsetˇ
13983        2. buf.to_offsetˇsuf
13984        3. buf.to_offsetˇfix
13985        4. buf.to_offsetˇ
13986        5. into_offsetˇensive
13987        6. to_offsetˇsuffix
13988        7. let to_offsetˇ //
13989        8. aato_offsetˇzz
13990        9. buf.to_offsetˇ
13991        10. buf.to_offsetˇsuffix
13992        11. to_offsetˇ
13993
13994        buf.to_offsetˇ  // newest cursor
13995    "};
13996    cx.set_state(initial_state);
13997    cx.update_editor(|editor, window, cx| {
13998        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13999    });
14000    handle_completion_request_with_insert_and_replace(
14001        &mut cx,
14002        completion_marked_buffer,
14003        vec![(completion_text, completion_text)],
14004        Arc::new(AtomicUsize::new(0)),
14005    )
14006    .await;
14007    cx.condition(|editor, _| editor.context_menu_visible())
14008        .await;
14009    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14010        editor
14011            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14012            .unwrap()
14013    });
14014    cx.assert_editor_state(expected);
14015    handle_resolve_completion_request(&mut cx, None).await;
14016    apply_additional_edits.await.unwrap();
14017
14018    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14019    let completion_text = "foo_and_bar";
14020    let initial_state = indoc! {"
14021        1. ooanbˇ
14022        2. zooanbˇ
14023        3. ooanbˇz
14024        4. zooanbˇz
14025        5. ooanˇ
14026        6. oanbˇ
14027
14028        ooanbˇ
14029    "};
14030    let completion_marked_buffer = indoc! {"
14031        1. ooanb
14032        2. zooanb
14033        3. ooanbz
14034        4. zooanbz
14035        5. ooan
14036        6. oanb
14037
14038        <ooanb|>
14039    "};
14040    let expected = indoc! {"
14041        1. foo_and_barˇ
14042        2. zfoo_and_barˇ
14043        3. foo_and_barˇz
14044        4. zfoo_and_barˇz
14045        5. ooanfoo_and_barˇ
14046        6. oanbfoo_and_barˇ
14047
14048        foo_and_barˇ
14049    "};
14050    cx.set_state(initial_state);
14051    cx.update_editor(|editor, window, cx| {
14052        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14053    });
14054    handle_completion_request_with_insert_and_replace(
14055        &mut cx,
14056        completion_marked_buffer,
14057        vec![(completion_text, completion_text)],
14058        Arc::new(AtomicUsize::new(0)),
14059    )
14060    .await;
14061    cx.condition(|editor, _| editor.context_menu_visible())
14062        .await;
14063    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14064        editor
14065            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14066            .unwrap()
14067    });
14068    cx.assert_editor_state(expected);
14069    handle_resolve_completion_request(&mut cx, None).await;
14070    apply_additional_edits.await.unwrap();
14071
14072    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14073    // (expects the same as if it was inserted at the end)
14074    let completion_text = "foo_and_bar";
14075    let initial_state = indoc! {"
14076        1. ooˇanb
14077        2. zooˇanb
14078        3. ooˇanbz
14079        4. zooˇanbz
14080
14081        ooˇanb
14082    "};
14083    let completion_marked_buffer = indoc! {"
14084        1. ooanb
14085        2. zooanb
14086        3. ooanbz
14087        4. zooanbz
14088
14089        <oo|anb>
14090    "};
14091    let expected = indoc! {"
14092        1. foo_and_barˇ
14093        2. zfoo_and_barˇ
14094        3. foo_and_barˇz
14095        4. zfoo_and_barˇz
14096
14097        foo_and_barˇ
14098    "};
14099    cx.set_state(initial_state);
14100    cx.update_editor(|editor, window, cx| {
14101        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14102    });
14103    handle_completion_request_with_insert_and_replace(
14104        &mut cx,
14105        completion_marked_buffer,
14106        vec![(completion_text, completion_text)],
14107        Arc::new(AtomicUsize::new(0)),
14108    )
14109    .await;
14110    cx.condition(|editor, _| editor.context_menu_visible())
14111        .await;
14112    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14113        editor
14114            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14115            .unwrap()
14116    });
14117    cx.assert_editor_state(expected);
14118    handle_resolve_completion_request(&mut cx, None).await;
14119    apply_additional_edits.await.unwrap();
14120}
14121
14122// This used to crash
14123#[gpui::test]
14124async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14125    init_test(cx, |_| {});
14126
14127    let buffer_text = indoc! {"
14128        fn main() {
14129            10.satu;
14130
14131            //
14132            // separate cursors so they open in different excerpts (manually reproducible)
14133            //
14134
14135            10.satu20;
14136        }
14137    "};
14138    let multibuffer_text_with_selections = indoc! {"
14139        fn main() {
14140            10.satuˇ;
14141
14142            //
14143
14144            //
14145
14146            10.satuˇ20;
14147        }
14148    "};
14149    let expected_multibuffer = indoc! {"
14150        fn main() {
14151            10.saturating_sub()ˇ;
14152
14153            //
14154
14155            //
14156
14157            10.saturating_sub()ˇ;
14158        }
14159    "};
14160
14161    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14162    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14163
14164    let fs = FakeFs::new(cx.executor());
14165    fs.insert_tree(
14166        path!("/a"),
14167        json!({
14168            "main.rs": buffer_text,
14169        }),
14170    )
14171    .await;
14172
14173    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14174    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14175    language_registry.add(rust_lang());
14176    let mut fake_servers = language_registry.register_fake_lsp(
14177        "Rust",
14178        FakeLspAdapter {
14179            capabilities: lsp::ServerCapabilities {
14180                completion_provider: Some(lsp::CompletionOptions {
14181                    resolve_provider: None,
14182                    ..lsp::CompletionOptions::default()
14183                }),
14184                ..lsp::ServerCapabilities::default()
14185            },
14186            ..FakeLspAdapter::default()
14187        },
14188    );
14189    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14190    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14191    let buffer = project
14192        .update(cx, |project, cx| {
14193            project.open_local_buffer(path!("/a/main.rs"), cx)
14194        })
14195        .await
14196        .unwrap();
14197
14198    let multi_buffer = cx.new(|cx| {
14199        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14200        multi_buffer.push_excerpts(
14201            buffer.clone(),
14202            [ExcerptRange::new(0..first_excerpt_end)],
14203            cx,
14204        );
14205        multi_buffer.push_excerpts(
14206            buffer.clone(),
14207            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14208            cx,
14209        );
14210        multi_buffer
14211    });
14212
14213    let editor = workspace
14214        .update(cx, |_, window, cx| {
14215            cx.new(|cx| {
14216                Editor::new(
14217                    EditorMode::Full {
14218                        scale_ui_elements_with_buffer_font_size: false,
14219                        show_active_line_background: false,
14220                        sizing_behavior: SizingBehavior::Default,
14221                    },
14222                    multi_buffer.clone(),
14223                    Some(project.clone()),
14224                    window,
14225                    cx,
14226                )
14227            })
14228        })
14229        .unwrap();
14230
14231    let pane = workspace
14232        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14233        .unwrap();
14234    pane.update_in(cx, |pane, window, cx| {
14235        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14236    });
14237
14238    let fake_server = fake_servers.next().await.unwrap();
14239
14240    editor.update_in(cx, |editor, window, cx| {
14241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14242            s.select_ranges([
14243                Point::new(1, 11)..Point::new(1, 11),
14244                Point::new(7, 11)..Point::new(7, 11),
14245            ])
14246        });
14247
14248        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14249    });
14250
14251    editor.update_in(cx, |editor, window, cx| {
14252        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14253    });
14254
14255    fake_server
14256        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14257            let completion_item = lsp::CompletionItem {
14258                label: "saturating_sub()".into(),
14259                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14260                    lsp::InsertReplaceEdit {
14261                        new_text: "saturating_sub()".to_owned(),
14262                        insert: lsp::Range::new(
14263                            lsp::Position::new(7, 7),
14264                            lsp::Position::new(7, 11),
14265                        ),
14266                        replace: lsp::Range::new(
14267                            lsp::Position::new(7, 7),
14268                            lsp::Position::new(7, 13),
14269                        ),
14270                    },
14271                )),
14272                ..lsp::CompletionItem::default()
14273            };
14274
14275            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14276        })
14277        .next()
14278        .await
14279        .unwrap();
14280
14281    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14282        .await;
14283
14284    editor
14285        .update_in(cx, |editor, window, cx| {
14286            editor
14287                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14288                .unwrap()
14289        })
14290        .await
14291        .unwrap();
14292
14293    editor.update(cx, |editor, cx| {
14294        assert_text_with_selections(editor, expected_multibuffer, cx);
14295    })
14296}
14297
14298#[gpui::test]
14299async fn test_completion(cx: &mut TestAppContext) {
14300    init_test(cx, |_| {});
14301
14302    let mut cx = EditorLspTestContext::new_rust(
14303        lsp::ServerCapabilities {
14304            completion_provider: Some(lsp::CompletionOptions {
14305                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14306                resolve_provider: Some(true),
14307                ..Default::default()
14308            }),
14309            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14310            ..Default::default()
14311        },
14312        cx,
14313    )
14314    .await;
14315    let counter = Arc::new(AtomicUsize::new(0));
14316
14317    cx.set_state(indoc! {"
14318        oneˇ
14319        two
14320        three
14321    "});
14322    cx.simulate_keystroke(".");
14323    handle_completion_request(
14324        indoc! {"
14325            one.|<>
14326            two
14327            three
14328        "},
14329        vec!["first_completion", "second_completion"],
14330        true,
14331        counter.clone(),
14332        &mut cx,
14333    )
14334    .await;
14335    cx.condition(|editor, _| editor.context_menu_visible())
14336        .await;
14337    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14338
14339    let _handler = handle_signature_help_request(
14340        &mut cx,
14341        lsp::SignatureHelp {
14342            signatures: vec![lsp::SignatureInformation {
14343                label: "test signature".to_string(),
14344                documentation: None,
14345                parameters: Some(vec![lsp::ParameterInformation {
14346                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14347                    documentation: None,
14348                }]),
14349                active_parameter: None,
14350            }],
14351            active_signature: None,
14352            active_parameter: None,
14353        },
14354    );
14355    cx.update_editor(|editor, window, cx| {
14356        assert!(
14357            !editor.signature_help_state.is_shown(),
14358            "No signature help was called for"
14359        );
14360        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14361    });
14362    cx.run_until_parked();
14363    cx.update_editor(|editor, _, _| {
14364        assert!(
14365            !editor.signature_help_state.is_shown(),
14366            "No signature help should be shown when completions menu is open"
14367        );
14368    });
14369
14370    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14371        editor.context_menu_next(&Default::default(), window, cx);
14372        editor
14373            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14374            .unwrap()
14375    });
14376    cx.assert_editor_state(indoc! {"
14377        one.second_completionˇ
14378        two
14379        three
14380    "});
14381
14382    handle_resolve_completion_request(
14383        &mut cx,
14384        Some(vec![
14385            (
14386                //This overlaps with the primary completion edit which is
14387                //misbehavior from the LSP spec, test that we filter it out
14388                indoc! {"
14389                    one.second_ˇcompletion
14390                    two
14391                    threeˇ
14392                "},
14393                "overlapping additional edit",
14394            ),
14395            (
14396                indoc! {"
14397                    one.second_completion
14398                    two
14399                    threeˇ
14400                "},
14401                "\nadditional edit",
14402            ),
14403        ]),
14404    )
14405    .await;
14406    apply_additional_edits.await.unwrap();
14407    cx.assert_editor_state(indoc! {"
14408        one.second_completionˇ
14409        two
14410        three
14411        additional edit
14412    "});
14413
14414    cx.set_state(indoc! {"
14415        one.second_completion
14416        twoˇ
14417        threeˇ
14418        additional edit
14419    "});
14420    cx.simulate_keystroke(" ");
14421    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14422    cx.simulate_keystroke("s");
14423    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14424
14425    cx.assert_editor_state(indoc! {"
14426        one.second_completion
14427        two sˇ
14428        three sˇ
14429        additional edit
14430    "});
14431    handle_completion_request(
14432        indoc! {"
14433            one.second_completion
14434            two s
14435            three <s|>
14436            additional edit
14437        "},
14438        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14439        true,
14440        counter.clone(),
14441        &mut cx,
14442    )
14443    .await;
14444    cx.condition(|editor, _| editor.context_menu_visible())
14445        .await;
14446    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14447
14448    cx.simulate_keystroke("i");
14449
14450    handle_completion_request(
14451        indoc! {"
14452            one.second_completion
14453            two si
14454            three <si|>
14455            additional edit
14456        "},
14457        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14458        true,
14459        counter.clone(),
14460        &mut cx,
14461    )
14462    .await;
14463    cx.condition(|editor, _| editor.context_menu_visible())
14464        .await;
14465    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14466
14467    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14468        editor
14469            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14470            .unwrap()
14471    });
14472    cx.assert_editor_state(indoc! {"
14473        one.second_completion
14474        two sixth_completionˇ
14475        three sixth_completionˇ
14476        additional edit
14477    "});
14478
14479    apply_additional_edits.await.unwrap();
14480
14481    update_test_language_settings(&mut cx, |settings| {
14482        settings.defaults.show_completions_on_input = Some(false);
14483    });
14484    cx.set_state("editorˇ");
14485    cx.simulate_keystroke(".");
14486    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14487    cx.simulate_keystrokes("c l o");
14488    cx.assert_editor_state("editor.cloˇ");
14489    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14490    cx.update_editor(|editor, window, cx| {
14491        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14492    });
14493    handle_completion_request(
14494        "editor.<clo|>",
14495        vec!["close", "clobber"],
14496        true,
14497        counter.clone(),
14498        &mut cx,
14499    )
14500    .await;
14501    cx.condition(|editor, _| editor.context_menu_visible())
14502        .await;
14503    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14504
14505    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14506        editor
14507            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14508            .unwrap()
14509    });
14510    cx.assert_editor_state("editor.clobberˇ");
14511    handle_resolve_completion_request(&mut cx, None).await;
14512    apply_additional_edits.await.unwrap();
14513}
14514
14515#[gpui::test]
14516async fn test_completion_reuse(cx: &mut TestAppContext) {
14517    init_test(cx, |_| {});
14518
14519    let mut cx = EditorLspTestContext::new_rust(
14520        lsp::ServerCapabilities {
14521            completion_provider: Some(lsp::CompletionOptions {
14522                trigger_characters: Some(vec![".".to_string()]),
14523                ..Default::default()
14524            }),
14525            ..Default::default()
14526        },
14527        cx,
14528    )
14529    .await;
14530
14531    let counter = Arc::new(AtomicUsize::new(0));
14532    cx.set_state("objˇ");
14533    cx.simulate_keystroke(".");
14534
14535    // Initial completion request returns complete results
14536    let is_incomplete = false;
14537    handle_completion_request(
14538        "obj.|<>",
14539        vec!["a", "ab", "abc"],
14540        is_incomplete,
14541        counter.clone(),
14542        &mut cx,
14543    )
14544    .await;
14545    cx.run_until_parked();
14546    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14547    cx.assert_editor_state("obj.ˇ");
14548    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14549
14550    // Type "a" - filters existing completions
14551    cx.simulate_keystroke("a");
14552    cx.run_until_parked();
14553    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14554    cx.assert_editor_state("obj.aˇ");
14555    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14556
14557    // Type "b" - filters existing completions
14558    cx.simulate_keystroke("b");
14559    cx.run_until_parked();
14560    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14561    cx.assert_editor_state("obj.abˇ");
14562    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14563
14564    // Type "c" - filters existing completions
14565    cx.simulate_keystroke("c");
14566    cx.run_until_parked();
14567    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14568    cx.assert_editor_state("obj.abcˇ");
14569    check_displayed_completions(vec!["abc"], &mut cx);
14570
14571    // Backspace to delete "c" - filters existing completions
14572    cx.update_editor(|editor, window, cx| {
14573        editor.backspace(&Backspace, window, cx);
14574    });
14575    cx.run_until_parked();
14576    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14577    cx.assert_editor_state("obj.abˇ");
14578    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14579
14580    // Moving cursor to the left dismisses menu.
14581    cx.update_editor(|editor, window, cx| {
14582        editor.move_left(&MoveLeft, window, cx);
14583    });
14584    cx.run_until_parked();
14585    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14586    cx.assert_editor_state("obj.aˇb");
14587    cx.update_editor(|editor, _, _| {
14588        assert_eq!(editor.context_menu_visible(), false);
14589    });
14590
14591    // Type "b" - new request
14592    cx.simulate_keystroke("b");
14593    let is_incomplete = false;
14594    handle_completion_request(
14595        "obj.<ab|>a",
14596        vec!["ab", "abc"],
14597        is_incomplete,
14598        counter.clone(),
14599        &mut cx,
14600    )
14601    .await;
14602    cx.run_until_parked();
14603    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14604    cx.assert_editor_state("obj.abˇb");
14605    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14606
14607    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14608    cx.update_editor(|editor, window, cx| {
14609        editor.backspace(&Backspace, window, cx);
14610    });
14611    let is_incomplete = false;
14612    handle_completion_request(
14613        "obj.<a|>b",
14614        vec!["a", "ab", "abc"],
14615        is_incomplete,
14616        counter.clone(),
14617        &mut cx,
14618    )
14619    .await;
14620    cx.run_until_parked();
14621    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14622    cx.assert_editor_state("obj.aˇb");
14623    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14624
14625    // Backspace to delete "a" - dismisses menu.
14626    cx.update_editor(|editor, window, cx| {
14627        editor.backspace(&Backspace, window, cx);
14628    });
14629    cx.run_until_parked();
14630    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14631    cx.assert_editor_state("obj.ˇb");
14632    cx.update_editor(|editor, _, _| {
14633        assert_eq!(editor.context_menu_visible(), false);
14634    });
14635}
14636
14637#[gpui::test]
14638async fn test_word_completion(cx: &mut TestAppContext) {
14639    let lsp_fetch_timeout_ms = 10;
14640    init_test(cx, |language_settings| {
14641        language_settings.defaults.completions = Some(CompletionSettingsContent {
14642            words_min_length: Some(0),
14643            lsp_fetch_timeout_ms: Some(10),
14644            lsp_insert_mode: Some(LspInsertMode::Insert),
14645            ..Default::default()
14646        });
14647    });
14648
14649    let mut cx = EditorLspTestContext::new_rust(
14650        lsp::ServerCapabilities {
14651            completion_provider: Some(lsp::CompletionOptions {
14652                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14653                ..lsp::CompletionOptions::default()
14654            }),
14655            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14656            ..lsp::ServerCapabilities::default()
14657        },
14658        cx,
14659    )
14660    .await;
14661
14662    let throttle_completions = Arc::new(AtomicBool::new(false));
14663
14664    let lsp_throttle_completions = throttle_completions.clone();
14665    let _completion_requests_handler =
14666        cx.lsp
14667            .server
14668            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14669                let lsp_throttle_completions = lsp_throttle_completions.clone();
14670                let cx = cx.clone();
14671                async move {
14672                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14673                        cx.background_executor()
14674                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14675                            .await;
14676                    }
14677                    Ok(Some(lsp::CompletionResponse::Array(vec![
14678                        lsp::CompletionItem {
14679                            label: "first".into(),
14680                            ..lsp::CompletionItem::default()
14681                        },
14682                        lsp::CompletionItem {
14683                            label: "last".into(),
14684                            ..lsp::CompletionItem::default()
14685                        },
14686                    ])))
14687                }
14688            });
14689
14690    cx.set_state(indoc! {"
14691        oneˇ
14692        two
14693        three
14694    "});
14695    cx.simulate_keystroke(".");
14696    cx.executor().run_until_parked();
14697    cx.condition(|editor, _| editor.context_menu_visible())
14698        .await;
14699    cx.update_editor(|editor, window, cx| {
14700        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14701        {
14702            assert_eq!(
14703                completion_menu_entries(menu),
14704                &["first", "last"],
14705                "When LSP server is fast to reply, no fallback word completions are used"
14706            );
14707        } else {
14708            panic!("expected completion menu to be open");
14709        }
14710        editor.cancel(&Cancel, window, cx);
14711    });
14712    cx.executor().run_until_parked();
14713    cx.condition(|editor, _| !editor.context_menu_visible())
14714        .await;
14715
14716    throttle_completions.store(true, atomic::Ordering::Release);
14717    cx.simulate_keystroke(".");
14718    cx.executor()
14719        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14720    cx.executor().run_until_parked();
14721    cx.condition(|editor, _| editor.context_menu_visible())
14722        .await;
14723    cx.update_editor(|editor, _, _| {
14724        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14725        {
14726            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14727                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14728        } else {
14729            panic!("expected completion menu to be open");
14730        }
14731    });
14732}
14733
14734#[gpui::test]
14735async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14736    init_test(cx, |language_settings| {
14737        language_settings.defaults.completions = Some(CompletionSettingsContent {
14738            words: Some(WordsCompletionMode::Enabled),
14739            words_min_length: Some(0),
14740            lsp_insert_mode: Some(LspInsertMode::Insert),
14741            ..Default::default()
14742        });
14743    });
14744
14745    let mut cx = EditorLspTestContext::new_rust(
14746        lsp::ServerCapabilities {
14747            completion_provider: Some(lsp::CompletionOptions {
14748                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14749                ..lsp::CompletionOptions::default()
14750            }),
14751            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14752            ..lsp::ServerCapabilities::default()
14753        },
14754        cx,
14755    )
14756    .await;
14757
14758    let _completion_requests_handler =
14759        cx.lsp
14760            .server
14761            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14762                Ok(Some(lsp::CompletionResponse::Array(vec![
14763                    lsp::CompletionItem {
14764                        label: "first".into(),
14765                        ..lsp::CompletionItem::default()
14766                    },
14767                    lsp::CompletionItem {
14768                        label: "last".into(),
14769                        ..lsp::CompletionItem::default()
14770                    },
14771                ])))
14772            });
14773
14774    cx.set_state(indoc! {"ˇ
14775        first
14776        last
14777        second
14778    "});
14779    cx.simulate_keystroke(".");
14780    cx.executor().run_until_parked();
14781    cx.condition(|editor, _| editor.context_menu_visible())
14782        .await;
14783    cx.update_editor(|editor, _, _| {
14784        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14785        {
14786            assert_eq!(
14787                completion_menu_entries(menu),
14788                &["first", "last", "second"],
14789                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14790            );
14791        } else {
14792            panic!("expected completion menu to be open");
14793        }
14794    });
14795}
14796
14797#[gpui::test]
14798async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14799    init_test(cx, |language_settings| {
14800        language_settings.defaults.completions = Some(CompletionSettingsContent {
14801            words: Some(WordsCompletionMode::Disabled),
14802            words_min_length: Some(0),
14803            lsp_insert_mode: Some(LspInsertMode::Insert),
14804            ..Default::default()
14805        });
14806    });
14807
14808    let mut cx = EditorLspTestContext::new_rust(
14809        lsp::ServerCapabilities {
14810            completion_provider: Some(lsp::CompletionOptions {
14811                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14812                ..lsp::CompletionOptions::default()
14813            }),
14814            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14815            ..lsp::ServerCapabilities::default()
14816        },
14817        cx,
14818    )
14819    .await;
14820
14821    let _completion_requests_handler =
14822        cx.lsp
14823            .server
14824            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14825                panic!("LSP completions should not be queried when dealing with word completions")
14826            });
14827
14828    cx.set_state(indoc! {"ˇ
14829        first
14830        last
14831        second
14832    "});
14833    cx.update_editor(|editor, window, cx| {
14834        editor.show_word_completions(&ShowWordCompletions, window, cx);
14835    });
14836    cx.executor().run_until_parked();
14837    cx.condition(|editor, _| editor.context_menu_visible())
14838        .await;
14839    cx.update_editor(|editor, _, _| {
14840        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14841        {
14842            assert_eq!(
14843                completion_menu_entries(menu),
14844                &["first", "last", "second"],
14845                "`ShowWordCompletions` action should show word completions"
14846            );
14847        } else {
14848            panic!("expected completion menu to be open");
14849        }
14850    });
14851
14852    cx.simulate_keystroke("l");
14853    cx.executor().run_until_parked();
14854    cx.condition(|editor, _| editor.context_menu_visible())
14855        .await;
14856    cx.update_editor(|editor, _, _| {
14857        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14858        {
14859            assert_eq!(
14860                completion_menu_entries(menu),
14861                &["last"],
14862                "After showing word completions, further editing should filter them and not query the LSP"
14863            );
14864        } else {
14865            panic!("expected completion menu to be open");
14866        }
14867    });
14868}
14869
14870#[gpui::test]
14871async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14872    init_test(cx, |language_settings| {
14873        language_settings.defaults.completions = Some(CompletionSettingsContent {
14874            words_min_length: Some(0),
14875            lsp: Some(false),
14876            lsp_insert_mode: Some(LspInsertMode::Insert),
14877            ..Default::default()
14878        });
14879    });
14880
14881    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14882
14883    cx.set_state(indoc! {"ˇ
14884        0_usize
14885        let
14886        33
14887        4.5f32
14888    "});
14889    cx.update_editor(|editor, window, cx| {
14890        editor.show_completions(&ShowCompletions::default(), window, cx);
14891    });
14892    cx.executor().run_until_parked();
14893    cx.condition(|editor, _| editor.context_menu_visible())
14894        .await;
14895    cx.update_editor(|editor, window, cx| {
14896        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14897        {
14898            assert_eq!(
14899                completion_menu_entries(menu),
14900                &["let"],
14901                "With no digits in the completion query, no digits should be in the word completions"
14902            );
14903        } else {
14904            panic!("expected completion menu to be open");
14905        }
14906        editor.cancel(&Cancel, window, cx);
14907    });
14908
14909    cx.set_state(indoc! {"14910        0_usize
14911        let
14912        3
14913        33.35f32
14914    "});
14915    cx.update_editor(|editor, window, cx| {
14916        editor.show_completions(&ShowCompletions::default(), window, cx);
14917    });
14918    cx.executor().run_until_parked();
14919    cx.condition(|editor, _| editor.context_menu_visible())
14920        .await;
14921    cx.update_editor(|editor, _, _| {
14922        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14923        {
14924            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14925                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14926        } else {
14927            panic!("expected completion menu to be open");
14928        }
14929    });
14930}
14931
14932#[gpui::test]
14933async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14934    init_test(cx, |language_settings| {
14935        language_settings.defaults.completions = Some(CompletionSettingsContent {
14936            words: Some(WordsCompletionMode::Enabled),
14937            words_min_length: Some(3),
14938            lsp_insert_mode: Some(LspInsertMode::Insert),
14939            ..Default::default()
14940        });
14941    });
14942
14943    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14944    cx.set_state(indoc! {"ˇ
14945        wow
14946        wowen
14947        wowser
14948    "});
14949    cx.simulate_keystroke("w");
14950    cx.executor().run_until_parked();
14951    cx.update_editor(|editor, _, _| {
14952        if editor.context_menu.borrow_mut().is_some() {
14953            panic!(
14954                "expected completion menu to be hidden, as words completion threshold is not met"
14955            );
14956        }
14957    });
14958
14959    cx.update_editor(|editor, window, cx| {
14960        editor.show_word_completions(&ShowWordCompletions, window, cx);
14961    });
14962    cx.executor().run_until_parked();
14963    cx.update_editor(|editor, window, cx| {
14964        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14965        {
14966            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");
14967        } else {
14968            panic!("expected completion menu to be open after the word completions are called with an action");
14969        }
14970
14971        editor.cancel(&Cancel, window, cx);
14972    });
14973    cx.update_editor(|editor, _, _| {
14974        if editor.context_menu.borrow_mut().is_some() {
14975            panic!("expected completion menu to be hidden after canceling");
14976        }
14977    });
14978
14979    cx.simulate_keystroke("o");
14980    cx.executor().run_until_parked();
14981    cx.update_editor(|editor, _, _| {
14982        if editor.context_menu.borrow_mut().is_some() {
14983            panic!(
14984                "expected completion menu to be hidden, as words completion threshold is not met still"
14985            );
14986        }
14987    });
14988
14989    cx.simulate_keystroke("w");
14990    cx.executor().run_until_parked();
14991    cx.update_editor(|editor, _, _| {
14992        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14993        {
14994            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14995        } else {
14996            panic!("expected completion menu to be open after the word completions threshold is met");
14997        }
14998    });
14999}
15000
15001#[gpui::test]
15002async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15003    init_test(cx, |language_settings| {
15004        language_settings.defaults.completions = Some(CompletionSettingsContent {
15005            words: Some(WordsCompletionMode::Enabled),
15006            words_min_length: Some(0),
15007            lsp_insert_mode: Some(LspInsertMode::Insert),
15008            ..Default::default()
15009        });
15010    });
15011
15012    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15013    cx.update_editor(|editor, _, _| {
15014        editor.disable_word_completions();
15015    });
15016    cx.set_state(indoc! {"ˇ
15017        wow
15018        wowen
15019        wowser
15020    "});
15021    cx.simulate_keystroke("w");
15022    cx.executor().run_until_parked();
15023    cx.update_editor(|editor, _, _| {
15024        if editor.context_menu.borrow_mut().is_some() {
15025            panic!(
15026                "expected completion menu to be hidden, as words completion are disabled for this editor"
15027            );
15028        }
15029    });
15030
15031    cx.update_editor(|editor, window, cx| {
15032        editor.show_word_completions(&ShowWordCompletions, window, cx);
15033    });
15034    cx.executor().run_until_parked();
15035    cx.update_editor(|editor, _, _| {
15036        if editor.context_menu.borrow_mut().is_some() {
15037            panic!(
15038                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15039            );
15040        }
15041    });
15042}
15043
15044fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15045    let position = || lsp::Position {
15046        line: params.text_document_position.position.line,
15047        character: params.text_document_position.position.character,
15048    };
15049    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15050        range: lsp::Range {
15051            start: position(),
15052            end: position(),
15053        },
15054        new_text: text.to_string(),
15055    }))
15056}
15057
15058#[gpui::test]
15059async fn test_multiline_completion(cx: &mut TestAppContext) {
15060    init_test(cx, |_| {});
15061
15062    let fs = FakeFs::new(cx.executor());
15063    fs.insert_tree(
15064        path!("/a"),
15065        json!({
15066            "main.ts": "a",
15067        }),
15068    )
15069    .await;
15070
15071    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15072    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15073    let typescript_language = Arc::new(Language::new(
15074        LanguageConfig {
15075            name: "TypeScript".into(),
15076            matcher: LanguageMatcher {
15077                path_suffixes: vec!["ts".to_string()],
15078                ..LanguageMatcher::default()
15079            },
15080            line_comments: vec!["// ".into()],
15081            ..LanguageConfig::default()
15082        },
15083        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15084    ));
15085    language_registry.add(typescript_language.clone());
15086    let mut fake_servers = language_registry.register_fake_lsp(
15087        "TypeScript",
15088        FakeLspAdapter {
15089            capabilities: lsp::ServerCapabilities {
15090                completion_provider: Some(lsp::CompletionOptions {
15091                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15092                    ..lsp::CompletionOptions::default()
15093                }),
15094                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15095                ..lsp::ServerCapabilities::default()
15096            },
15097            // Emulate vtsls label generation
15098            label_for_completion: Some(Box::new(|item, _| {
15099                let text = if let Some(description) = item
15100                    .label_details
15101                    .as_ref()
15102                    .and_then(|label_details| label_details.description.as_ref())
15103                {
15104                    format!("{} {}", item.label, description)
15105                } else if let Some(detail) = &item.detail {
15106                    format!("{} {}", item.label, detail)
15107                } else {
15108                    item.label.clone()
15109                };
15110                Some(language::CodeLabel::plain(text, None))
15111            })),
15112            ..FakeLspAdapter::default()
15113        },
15114    );
15115    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15116    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15117    let worktree_id = workspace
15118        .update(cx, |workspace, _window, cx| {
15119            workspace.project().update(cx, |project, cx| {
15120                project.worktrees(cx).next().unwrap().read(cx).id()
15121            })
15122        })
15123        .unwrap();
15124    let _buffer = project
15125        .update(cx, |project, cx| {
15126            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15127        })
15128        .await
15129        .unwrap();
15130    let editor = workspace
15131        .update(cx, |workspace, window, cx| {
15132            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15133        })
15134        .unwrap()
15135        .await
15136        .unwrap()
15137        .downcast::<Editor>()
15138        .unwrap();
15139    let fake_server = fake_servers.next().await.unwrap();
15140
15141    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15142    let multiline_label_2 = "a\nb\nc\n";
15143    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15144    let multiline_description = "d\ne\nf\n";
15145    let multiline_detail_2 = "g\nh\ni\n";
15146
15147    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15148        move |params, _| async move {
15149            Ok(Some(lsp::CompletionResponse::Array(vec![
15150                lsp::CompletionItem {
15151                    label: multiline_label.to_string(),
15152                    text_edit: gen_text_edit(&params, "new_text_1"),
15153                    ..lsp::CompletionItem::default()
15154                },
15155                lsp::CompletionItem {
15156                    label: "single line label 1".to_string(),
15157                    detail: Some(multiline_detail.to_string()),
15158                    text_edit: gen_text_edit(&params, "new_text_2"),
15159                    ..lsp::CompletionItem::default()
15160                },
15161                lsp::CompletionItem {
15162                    label: "single line label 2".to_string(),
15163                    label_details: Some(lsp::CompletionItemLabelDetails {
15164                        description: Some(multiline_description.to_string()),
15165                        detail: None,
15166                    }),
15167                    text_edit: gen_text_edit(&params, "new_text_2"),
15168                    ..lsp::CompletionItem::default()
15169                },
15170                lsp::CompletionItem {
15171                    label: multiline_label_2.to_string(),
15172                    detail: Some(multiline_detail_2.to_string()),
15173                    text_edit: gen_text_edit(&params, "new_text_3"),
15174                    ..lsp::CompletionItem::default()
15175                },
15176                lsp::CompletionItem {
15177                    label: "Label with many     spaces and \t but without newlines".to_string(),
15178                    detail: Some(
15179                        "Details with many     spaces and \t but without newlines".to_string(),
15180                    ),
15181                    text_edit: gen_text_edit(&params, "new_text_4"),
15182                    ..lsp::CompletionItem::default()
15183                },
15184            ])))
15185        },
15186    );
15187
15188    editor.update_in(cx, |editor, window, cx| {
15189        cx.focus_self(window);
15190        editor.move_to_end(&MoveToEnd, window, cx);
15191        editor.handle_input(".", window, cx);
15192    });
15193    cx.run_until_parked();
15194    completion_handle.next().await.unwrap();
15195
15196    editor.update(cx, |editor, _| {
15197        assert!(editor.context_menu_visible());
15198        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15199        {
15200            let completion_labels = menu
15201                .completions
15202                .borrow()
15203                .iter()
15204                .map(|c| c.label.text.clone())
15205                .collect::<Vec<_>>();
15206            assert_eq!(
15207                completion_labels,
15208                &[
15209                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15210                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15211                    "single line label 2 d e f ",
15212                    "a b c g h i ",
15213                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15214                ],
15215                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15216            );
15217
15218            for completion in menu
15219                .completions
15220                .borrow()
15221                .iter() {
15222                    assert_eq!(
15223                        completion.label.filter_range,
15224                        0..completion.label.text.len(),
15225                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15226                    );
15227                }
15228        } else {
15229            panic!("expected completion menu to be open");
15230        }
15231    });
15232}
15233
15234#[gpui::test]
15235async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15236    init_test(cx, |_| {});
15237    let mut cx = EditorLspTestContext::new_rust(
15238        lsp::ServerCapabilities {
15239            completion_provider: Some(lsp::CompletionOptions {
15240                trigger_characters: Some(vec![".".to_string()]),
15241                ..Default::default()
15242            }),
15243            ..Default::default()
15244        },
15245        cx,
15246    )
15247    .await;
15248    cx.lsp
15249        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15250            Ok(Some(lsp::CompletionResponse::Array(vec![
15251                lsp::CompletionItem {
15252                    label: "first".into(),
15253                    ..Default::default()
15254                },
15255                lsp::CompletionItem {
15256                    label: "last".into(),
15257                    ..Default::default()
15258                },
15259            ])))
15260        });
15261    cx.set_state("variableˇ");
15262    cx.simulate_keystroke(".");
15263    cx.executor().run_until_parked();
15264
15265    cx.update_editor(|editor, _, _| {
15266        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15267        {
15268            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15269        } else {
15270            panic!("expected completion menu to be open");
15271        }
15272    });
15273
15274    cx.update_editor(|editor, window, cx| {
15275        editor.move_page_down(&MovePageDown::default(), window, cx);
15276        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15277        {
15278            assert!(
15279                menu.selected_item == 1,
15280                "expected PageDown to select the last item from the context menu"
15281            );
15282        } else {
15283            panic!("expected completion menu to stay open after PageDown");
15284        }
15285    });
15286
15287    cx.update_editor(|editor, window, cx| {
15288        editor.move_page_up(&MovePageUp::default(), window, cx);
15289        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15290        {
15291            assert!(
15292                menu.selected_item == 0,
15293                "expected PageUp to select the first item from the context menu"
15294            );
15295        } else {
15296            panic!("expected completion menu to stay open after PageUp");
15297        }
15298    });
15299}
15300
15301#[gpui::test]
15302async fn test_as_is_completions(cx: &mut TestAppContext) {
15303    init_test(cx, |_| {});
15304    let mut cx = EditorLspTestContext::new_rust(
15305        lsp::ServerCapabilities {
15306            completion_provider: Some(lsp::CompletionOptions {
15307                ..Default::default()
15308            }),
15309            ..Default::default()
15310        },
15311        cx,
15312    )
15313    .await;
15314    cx.lsp
15315        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15316            Ok(Some(lsp::CompletionResponse::Array(vec![
15317                lsp::CompletionItem {
15318                    label: "unsafe".into(),
15319                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15320                        range: lsp::Range {
15321                            start: lsp::Position {
15322                                line: 1,
15323                                character: 2,
15324                            },
15325                            end: lsp::Position {
15326                                line: 1,
15327                                character: 3,
15328                            },
15329                        },
15330                        new_text: "unsafe".to_string(),
15331                    })),
15332                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15333                    ..Default::default()
15334                },
15335            ])))
15336        });
15337    cx.set_state("fn a() {}\n");
15338    cx.executor().run_until_parked();
15339    cx.update_editor(|editor, window, cx| {
15340        editor.show_completions(
15341            &ShowCompletions {
15342                trigger: Some("\n".into()),
15343            },
15344            window,
15345            cx,
15346        );
15347    });
15348    cx.executor().run_until_parked();
15349
15350    cx.update_editor(|editor, window, cx| {
15351        editor.confirm_completion(&Default::default(), window, cx)
15352    });
15353    cx.executor().run_until_parked();
15354    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15355}
15356
15357#[gpui::test]
15358async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15359    init_test(cx, |_| {});
15360    let language =
15361        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15362    let mut cx = EditorLspTestContext::new(
15363        language,
15364        lsp::ServerCapabilities {
15365            completion_provider: Some(lsp::CompletionOptions {
15366                ..lsp::CompletionOptions::default()
15367            }),
15368            ..lsp::ServerCapabilities::default()
15369        },
15370        cx,
15371    )
15372    .await;
15373
15374    cx.set_state(
15375        "#ifndef BAR_H
15376#define BAR_H
15377
15378#include <stdbool.h>
15379
15380int fn_branch(bool do_branch1, bool do_branch2);
15381
15382#endif // BAR_H
15383ˇ",
15384    );
15385    cx.executor().run_until_parked();
15386    cx.update_editor(|editor, window, cx| {
15387        editor.handle_input("#", window, cx);
15388    });
15389    cx.executor().run_until_parked();
15390    cx.update_editor(|editor, window, cx| {
15391        editor.handle_input("i", window, cx);
15392    });
15393    cx.executor().run_until_parked();
15394    cx.update_editor(|editor, window, cx| {
15395        editor.handle_input("n", window, cx);
15396    });
15397    cx.executor().run_until_parked();
15398    cx.assert_editor_state(
15399        "#ifndef BAR_H
15400#define BAR_H
15401
15402#include <stdbool.h>
15403
15404int fn_branch(bool do_branch1, bool do_branch2);
15405
15406#endif // BAR_H
15407#inˇ",
15408    );
15409
15410    cx.lsp
15411        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15412            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15413                is_incomplete: false,
15414                item_defaults: None,
15415                items: vec![lsp::CompletionItem {
15416                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15417                    label_details: Some(lsp::CompletionItemLabelDetails {
15418                        detail: Some("header".to_string()),
15419                        description: None,
15420                    }),
15421                    label: " include".to_string(),
15422                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15423                        range: lsp::Range {
15424                            start: lsp::Position {
15425                                line: 8,
15426                                character: 1,
15427                            },
15428                            end: lsp::Position {
15429                                line: 8,
15430                                character: 1,
15431                            },
15432                        },
15433                        new_text: "include \"$0\"".to_string(),
15434                    })),
15435                    sort_text: Some("40b67681include".to_string()),
15436                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15437                    filter_text: Some("include".to_string()),
15438                    insert_text: Some("include \"$0\"".to_string()),
15439                    ..lsp::CompletionItem::default()
15440                }],
15441            })))
15442        });
15443    cx.update_editor(|editor, window, cx| {
15444        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15445    });
15446    cx.executor().run_until_parked();
15447    cx.update_editor(|editor, window, cx| {
15448        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15449    });
15450    cx.executor().run_until_parked();
15451    cx.assert_editor_state(
15452        "#ifndef BAR_H
15453#define BAR_H
15454
15455#include <stdbool.h>
15456
15457int fn_branch(bool do_branch1, bool do_branch2);
15458
15459#endif // BAR_H
15460#include \"ˇ\"",
15461    );
15462
15463    cx.lsp
15464        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15465            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15466                is_incomplete: true,
15467                item_defaults: None,
15468                items: vec![lsp::CompletionItem {
15469                    kind: Some(lsp::CompletionItemKind::FILE),
15470                    label: "AGL/".to_string(),
15471                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15472                        range: lsp::Range {
15473                            start: lsp::Position {
15474                                line: 8,
15475                                character: 10,
15476                            },
15477                            end: lsp::Position {
15478                                line: 8,
15479                                character: 11,
15480                            },
15481                        },
15482                        new_text: "AGL/".to_string(),
15483                    })),
15484                    sort_text: Some("40b67681AGL/".to_string()),
15485                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15486                    filter_text: Some("AGL/".to_string()),
15487                    insert_text: Some("AGL/".to_string()),
15488                    ..lsp::CompletionItem::default()
15489                }],
15490            })))
15491        });
15492    cx.update_editor(|editor, window, cx| {
15493        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15494    });
15495    cx.executor().run_until_parked();
15496    cx.update_editor(|editor, window, cx| {
15497        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15498    });
15499    cx.executor().run_until_parked();
15500    cx.assert_editor_state(
15501        r##"#ifndef BAR_H
15502#define BAR_H
15503
15504#include <stdbool.h>
15505
15506int fn_branch(bool do_branch1, bool do_branch2);
15507
15508#endif // BAR_H
15509#include "AGL/ˇ"##,
15510    );
15511
15512    cx.update_editor(|editor, window, cx| {
15513        editor.handle_input("\"", window, cx);
15514    });
15515    cx.executor().run_until_parked();
15516    cx.assert_editor_state(
15517        r##"#ifndef BAR_H
15518#define BAR_H
15519
15520#include <stdbool.h>
15521
15522int fn_branch(bool do_branch1, bool do_branch2);
15523
15524#endif // BAR_H
15525#include "AGL/"ˇ"##,
15526    );
15527}
15528
15529#[gpui::test]
15530async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15531    init_test(cx, |_| {});
15532
15533    let mut cx = EditorLspTestContext::new_rust(
15534        lsp::ServerCapabilities {
15535            completion_provider: Some(lsp::CompletionOptions {
15536                trigger_characters: Some(vec![".".to_string()]),
15537                resolve_provider: Some(true),
15538                ..Default::default()
15539            }),
15540            ..Default::default()
15541        },
15542        cx,
15543    )
15544    .await;
15545
15546    cx.set_state("fn main() { let a = 2ˇ; }");
15547    cx.simulate_keystroke(".");
15548    let completion_item = lsp::CompletionItem {
15549        label: "Some".into(),
15550        kind: Some(lsp::CompletionItemKind::SNIPPET),
15551        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15552        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15553            kind: lsp::MarkupKind::Markdown,
15554            value: "```rust\nSome(2)\n```".to_string(),
15555        })),
15556        deprecated: Some(false),
15557        sort_text: Some("Some".to_string()),
15558        filter_text: Some("Some".to_string()),
15559        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15560        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15561            range: lsp::Range {
15562                start: lsp::Position {
15563                    line: 0,
15564                    character: 22,
15565                },
15566                end: lsp::Position {
15567                    line: 0,
15568                    character: 22,
15569                },
15570            },
15571            new_text: "Some(2)".to_string(),
15572        })),
15573        additional_text_edits: Some(vec![lsp::TextEdit {
15574            range: lsp::Range {
15575                start: lsp::Position {
15576                    line: 0,
15577                    character: 20,
15578                },
15579                end: lsp::Position {
15580                    line: 0,
15581                    character: 22,
15582                },
15583            },
15584            new_text: "".to_string(),
15585        }]),
15586        ..Default::default()
15587    };
15588
15589    let closure_completion_item = completion_item.clone();
15590    let counter = Arc::new(AtomicUsize::new(0));
15591    let counter_clone = counter.clone();
15592    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15593        let task_completion_item = closure_completion_item.clone();
15594        counter_clone.fetch_add(1, atomic::Ordering::Release);
15595        async move {
15596            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15597                is_incomplete: true,
15598                item_defaults: None,
15599                items: vec![task_completion_item],
15600            })))
15601        }
15602    });
15603
15604    cx.condition(|editor, _| editor.context_menu_visible())
15605        .await;
15606    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15607    assert!(request.next().await.is_some());
15608    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15609
15610    cx.simulate_keystrokes("S o m");
15611    cx.condition(|editor, _| editor.context_menu_visible())
15612        .await;
15613    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15614    assert!(request.next().await.is_some());
15615    assert!(request.next().await.is_some());
15616    assert!(request.next().await.is_some());
15617    request.close();
15618    assert!(request.next().await.is_none());
15619    assert_eq!(
15620        counter.load(atomic::Ordering::Acquire),
15621        4,
15622        "With the completions menu open, only one LSP request should happen per input"
15623    );
15624}
15625
15626#[gpui::test]
15627async fn test_toggle_comment(cx: &mut TestAppContext) {
15628    init_test(cx, |_| {});
15629    let mut cx = EditorTestContext::new(cx).await;
15630    let language = Arc::new(Language::new(
15631        LanguageConfig {
15632            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15633            ..Default::default()
15634        },
15635        Some(tree_sitter_rust::LANGUAGE.into()),
15636    ));
15637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15638
15639    // If multiple selections intersect a line, the line is only toggled once.
15640    cx.set_state(indoc! {"
15641        fn a() {
15642            «//b();
15643            ˇ»// «c();
15644            //ˇ»  d();
15645        }
15646    "});
15647
15648    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15649
15650    cx.assert_editor_state(indoc! {"
15651        fn a() {
15652            «b();
15653            c();
15654            ˇ» d();
15655        }
15656    "});
15657
15658    // The comment prefix is inserted at the same column for every line in a
15659    // selection.
15660    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15661
15662    cx.assert_editor_state(indoc! {"
15663        fn a() {
15664            // «b();
15665            // c();
15666            ˇ»//  d();
15667        }
15668    "});
15669
15670    // If a selection ends at the beginning of a line, that line is not toggled.
15671    cx.set_selections_state(indoc! {"
15672        fn a() {
15673            // b();
15674            «// c();
15675        ˇ»    //  d();
15676        }
15677    "});
15678
15679    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15680
15681    cx.assert_editor_state(indoc! {"
15682        fn a() {
15683            // b();
15684            «c();
15685        ˇ»    //  d();
15686        }
15687    "});
15688
15689    // If a selection span a single line and is empty, the line is toggled.
15690    cx.set_state(indoc! {"
15691        fn a() {
15692            a();
15693            b();
15694        ˇ
15695        }
15696    "});
15697
15698    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15699
15700    cx.assert_editor_state(indoc! {"
15701        fn a() {
15702            a();
15703            b();
15704        //•ˇ
15705        }
15706    "});
15707
15708    // If a selection span multiple lines, empty lines are not toggled.
15709    cx.set_state(indoc! {"
15710        fn a() {
15711            «a();
15712
15713            c();ˇ»
15714        }
15715    "});
15716
15717    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15718
15719    cx.assert_editor_state(indoc! {"
15720        fn a() {
15721            // «a();
15722
15723            // c();ˇ»
15724        }
15725    "});
15726
15727    // If a selection includes multiple comment prefixes, all lines are uncommented.
15728    cx.set_state(indoc! {"
15729        fn a() {
15730            «// a();
15731            /// b();
15732            //! c();ˇ»
15733        }
15734    "});
15735
15736    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15737
15738    cx.assert_editor_state(indoc! {"
15739        fn a() {
15740            «a();
15741            b();
15742            c();ˇ»
15743        }
15744    "});
15745}
15746
15747#[gpui::test]
15748async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15749    init_test(cx, |_| {});
15750    let mut cx = EditorTestContext::new(cx).await;
15751    let language = Arc::new(Language::new(
15752        LanguageConfig {
15753            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15754            ..Default::default()
15755        },
15756        Some(tree_sitter_rust::LANGUAGE.into()),
15757    ));
15758    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15759
15760    let toggle_comments = &ToggleComments {
15761        advance_downwards: false,
15762        ignore_indent: true,
15763    };
15764
15765    // If multiple selections intersect a line, the line is only toggled once.
15766    cx.set_state(indoc! {"
15767        fn a() {
15768        //    «b();
15769        //    c();
15770        //    ˇ» d();
15771        }
15772    "});
15773
15774    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15775
15776    cx.assert_editor_state(indoc! {"
15777        fn a() {
15778            «b();
15779            c();
15780            ˇ» d();
15781        }
15782    "});
15783
15784    // The comment prefix is inserted at the beginning of each line
15785    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15786
15787    cx.assert_editor_state(indoc! {"
15788        fn a() {
15789        //    «b();
15790        //    c();
15791        //    ˇ» d();
15792        }
15793    "});
15794
15795    // If a selection ends at the beginning of a line, that line is not toggled.
15796    cx.set_selections_state(indoc! {"
15797        fn a() {
15798        //    b();
15799        //    «c();
15800        ˇ»//     d();
15801        }
15802    "});
15803
15804    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15805
15806    cx.assert_editor_state(indoc! {"
15807        fn a() {
15808        //    b();
15809            «c();
15810        ˇ»//     d();
15811        }
15812    "});
15813
15814    // If a selection span a single line and is empty, the line is toggled.
15815    cx.set_state(indoc! {"
15816        fn a() {
15817            a();
15818            b();
15819        ˇ
15820        }
15821    "});
15822
15823    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15824
15825    cx.assert_editor_state(indoc! {"
15826        fn a() {
15827            a();
15828            b();
15829        //ˇ
15830        }
15831    "});
15832
15833    // If a selection span multiple lines, empty lines are not toggled.
15834    cx.set_state(indoc! {"
15835        fn a() {
15836            «a();
15837
15838            c();ˇ»
15839        }
15840    "});
15841
15842    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15843
15844    cx.assert_editor_state(indoc! {"
15845        fn a() {
15846        //    «a();
15847
15848        //    c();ˇ»
15849        }
15850    "});
15851
15852    // If a selection includes multiple comment prefixes, all lines are uncommented.
15853    cx.set_state(indoc! {"
15854        fn a() {
15855        //    «a();
15856        ///    b();
15857        //!    c();ˇ»
15858        }
15859    "});
15860
15861    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15862
15863    cx.assert_editor_state(indoc! {"
15864        fn a() {
15865            «a();
15866            b();
15867            c();ˇ»
15868        }
15869    "});
15870}
15871
15872#[gpui::test]
15873async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15874    init_test(cx, |_| {});
15875
15876    let language = Arc::new(Language::new(
15877        LanguageConfig {
15878            line_comments: vec!["// ".into()],
15879            ..Default::default()
15880        },
15881        Some(tree_sitter_rust::LANGUAGE.into()),
15882    ));
15883
15884    let mut cx = EditorTestContext::new(cx).await;
15885
15886    cx.language_registry().add(language.clone());
15887    cx.update_buffer(|buffer, cx| {
15888        buffer.set_language(Some(language), cx);
15889    });
15890
15891    let toggle_comments = &ToggleComments {
15892        advance_downwards: true,
15893        ignore_indent: false,
15894    };
15895
15896    // Single cursor on one line -> advance
15897    // Cursor moves horizontally 3 characters as well on non-blank line
15898    cx.set_state(indoc!(
15899        "fn a() {
15900             ˇdog();
15901             cat();
15902        }"
15903    ));
15904    cx.update_editor(|editor, window, cx| {
15905        editor.toggle_comments(toggle_comments, window, cx);
15906    });
15907    cx.assert_editor_state(indoc!(
15908        "fn a() {
15909             // dog();
15910             catˇ();
15911        }"
15912    ));
15913
15914    // Single selection on one line -> don't advance
15915    cx.set_state(indoc!(
15916        "fn a() {
15917             «dog()ˇ»;
15918             cat();
15919        }"
15920    ));
15921    cx.update_editor(|editor, window, cx| {
15922        editor.toggle_comments(toggle_comments, window, cx);
15923    });
15924    cx.assert_editor_state(indoc!(
15925        "fn a() {
15926             // «dog()ˇ»;
15927             cat();
15928        }"
15929    ));
15930
15931    // Multiple cursors on one line -> advance
15932    cx.set_state(indoc!(
15933        "fn a() {
15934             ˇdˇog();
15935             cat();
15936        }"
15937    ));
15938    cx.update_editor(|editor, window, cx| {
15939        editor.toggle_comments(toggle_comments, window, cx);
15940    });
15941    cx.assert_editor_state(indoc!(
15942        "fn a() {
15943             // dog();
15944             catˇ(ˇ);
15945        }"
15946    ));
15947
15948    // Multiple cursors on one line, with selection -> don't advance
15949    cx.set_state(indoc!(
15950        "fn a() {
15951             ˇdˇog«()ˇ»;
15952             cat();
15953        }"
15954    ));
15955    cx.update_editor(|editor, window, cx| {
15956        editor.toggle_comments(toggle_comments, window, cx);
15957    });
15958    cx.assert_editor_state(indoc!(
15959        "fn a() {
15960             // ˇdˇog«()ˇ»;
15961             cat();
15962        }"
15963    ));
15964
15965    // Single cursor on one line -> advance
15966    // Cursor moves to column 0 on blank line
15967    cx.set_state(indoc!(
15968        "fn a() {
15969             ˇdog();
15970
15971             cat();
15972        }"
15973    ));
15974    cx.update_editor(|editor, window, cx| {
15975        editor.toggle_comments(toggle_comments, window, cx);
15976    });
15977    cx.assert_editor_state(indoc!(
15978        "fn a() {
15979             // dog();
15980        ˇ
15981             cat();
15982        }"
15983    ));
15984
15985    // Single cursor on one line -> advance
15986    // Cursor starts and ends at column 0
15987    cx.set_state(indoc!(
15988        "fn a() {
15989         ˇ    dog();
15990             cat();
15991        }"
15992    ));
15993    cx.update_editor(|editor, window, cx| {
15994        editor.toggle_comments(toggle_comments, window, cx);
15995    });
15996    cx.assert_editor_state(indoc!(
15997        "fn a() {
15998             // dog();
15999         ˇ    cat();
16000        }"
16001    ));
16002}
16003
16004#[gpui::test]
16005async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16006    init_test(cx, |_| {});
16007
16008    let mut cx = EditorTestContext::new(cx).await;
16009
16010    let html_language = Arc::new(
16011        Language::new(
16012            LanguageConfig {
16013                name: "HTML".into(),
16014                block_comment: Some(BlockCommentConfig {
16015                    start: "<!-- ".into(),
16016                    prefix: "".into(),
16017                    end: " -->".into(),
16018                    tab_size: 0,
16019                }),
16020                ..Default::default()
16021            },
16022            Some(tree_sitter_html::LANGUAGE.into()),
16023        )
16024        .with_injection_query(
16025            r#"
16026            (script_element
16027                (raw_text) @injection.content
16028                (#set! injection.language "javascript"))
16029            "#,
16030        )
16031        .unwrap(),
16032    );
16033
16034    let javascript_language = Arc::new(Language::new(
16035        LanguageConfig {
16036            name: "JavaScript".into(),
16037            line_comments: vec!["// ".into()],
16038            ..Default::default()
16039        },
16040        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16041    ));
16042
16043    cx.language_registry().add(html_language.clone());
16044    cx.language_registry().add(javascript_language);
16045    cx.update_buffer(|buffer, cx| {
16046        buffer.set_language(Some(html_language), cx);
16047    });
16048
16049    // Toggle comments for empty selections
16050    cx.set_state(
16051        &r#"
16052            <p>A</p>ˇ
16053            <p>B</p>ˇ
16054            <p>C</p>ˇ
16055        "#
16056        .unindent(),
16057    );
16058    cx.update_editor(|editor, window, cx| {
16059        editor.toggle_comments(&ToggleComments::default(), window, cx)
16060    });
16061    cx.assert_editor_state(
16062        &r#"
16063            <!-- <p>A</p>ˇ -->
16064            <!-- <p>B</p>ˇ -->
16065            <!-- <p>C</p>ˇ -->
16066        "#
16067        .unindent(),
16068    );
16069    cx.update_editor(|editor, window, cx| {
16070        editor.toggle_comments(&ToggleComments::default(), window, cx)
16071    });
16072    cx.assert_editor_state(
16073        &r#"
16074            <p>A</p>ˇ
16075            <p>B</p>ˇ
16076            <p>C</p>ˇ
16077        "#
16078        .unindent(),
16079    );
16080
16081    // Toggle comments for mixture of empty and non-empty selections, where
16082    // multiple selections occupy a given line.
16083    cx.set_state(
16084        &r#"
16085            <p>A«</p>
16086            <p>ˇ»B</p>ˇ
16087            <p>C«</p>
16088            <p>ˇ»D</p>ˇ
16089        "#
16090        .unindent(),
16091    );
16092
16093    cx.update_editor(|editor, window, cx| {
16094        editor.toggle_comments(&ToggleComments::default(), window, cx)
16095    });
16096    cx.assert_editor_state(
16097        &r#"
16098            <!-- <p>A«</p>
16099            <p>ˇ»B</p>ˇ -->
16100            <!-- <p>C«</p>
16101            <p>ˇ»D</p>ˇ -->
16102        "#
16103        .unindent(),
16104    );
16105    cx.update_editor(|editor, window, cx| {
16106        editor.toggle_comments(&ToggleComments::default(), window, cx)
16107    });
16108    cx.assert_editor_state(
16109        &r#"
16110            <p>A«</p>
16111            <p>ˇ»B</p>ˇ
16112            <p>C«</p>
16113            <p>ˇ»D</p>ˇ
16114        "#
16115        .unindent(),
16116    );
16117
16118    // Toggle comments when different languages are active for different
16119    // selections.
16120    cx.set_state(
16121        &r#"
16122            ˇ<script>
16123                ˇvar x = new Y();
16124            ˇ</script>
16125        "#
16126        .unindent(),
16127    );
16128    cx.executor().run_until_parked();
16129    cx.update_editor(|editor, window, cx| {
16130        editor.toggle_comments(&ToggleComments::default(), window, cx)
16131    });
16132    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16133    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16134    cx.assert_editor_state(
16135        &r#"
16136            <!-- ˇ<script> -->
16137                // ˇvar x = new Y();
16138            <!-- ˇ</script> -->
16139        "#
16140        .unindent(),
16141    );
16142}
16143
16144#[gpui::test]
16145fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16146    init_test(cx, |_| {});
16147
16148    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16149    let multibuffer = cx.new(|cx| {
16150        let mut multibuffer = MultiBuffer::new(ReadWrite);
16151        multibuffer.push_excerpts(
16152            buffer.clone(),
16153            [
16154                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16155                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16156            ],
16157            cx,
16158        );
16159        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16160        multibuffer
16161    });
16162
16163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16164    editor.update_in(cx, |editor, window, cx| {
16165        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16166        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16167            s.select_ranges([
16168                Point::new(0, 0)..Point::new(0, 0),
16169                Point::new(1, 0)..Point::new(1, 0),
16170            ])
16171        });
16172
16173        editor.handle_input("X", window, cx);
16174        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16175        assert_eq!(
16176            editor.selections.ranges(&editor.display_snapshot(cx)),
16177            [
16178                Point::new(0, 1)..Point::new(0, 1),
16179                Point::new(1, 1)..Point::new(1, 1),
16180            ]
16181        );
16182
16183        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16185            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16186        });
16187        editor.backspace(&Default::default(), window, cx);
16188        assert_eq!(editor.text(cx), "Xa\nbbb");
16189        assert_eq!(
16190            editor.selections.ranges(&editor.display_snapshot(cx)),
16191            [Point::new(1, 0)..Point::new(1, 0)]
16192        );
16193
16194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16195            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16196        });
16197        editor.backspace(&Default::default(), window, cx);
16198        assert_eq!(editor.text(cx), "X\nbb");
16199        assert_eq!(
16200            editor.selections.ranges(&editor.display_snapshot(cx)),
16201            [Point::new(0, 1)..Point::new(0, 1)]
16202        );
16203    });
16204}
16205
16206#[gpui::test]
16207fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16208    init_test(cx, |_| {});
16209
16210    let markers = vec![('[', ']').into(), ('(', ')').into()];
16211    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16212        indoc! {"
16213            [aaaa
16214            (bbbb]
16215            cccc)",
16216        },
16217        markers.clone(),
16218    );
16219    let excerpt_ranges = markers.into_iter().map(|marker| {
16220        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16221        ExcerptRange::new(context)
16222    });
16223    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16224    let multibuffer = cx.new(|cx| {
16225        let mut multibuffer = MultiBuffer::new(ReadWrite);
16226        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16227        multibuffer
16228    });
16229
16230    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16231    editor.update_in(cx, |editor, window, cx| {
16232        let (expected_text, selection_ranges) = marked_text_ranges(
16233            indoc! {"
16234                aaaa
16235                bˇbbb
16236                bˇbbˇb
16237                cccc"
16238            },
16239            true,
16240        );
16241        assert_eq!(editor.text(cx), expected_text);
16242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16243            s.select_ranges(selection_ranges)
16244        });
16245
16246        editor.handle_input("X", window, cx);
16247
16248        let (expected_text, expected_selections) = marked_text_ranges(
16249            indoc! {"
16250                aaaa
16251                bXˇbbXb
16252                bXˇbbXˇb
16253                cccc"
16254            },
16255            false,
16256        );
16257        assert_eq!(editor.text(cx), expected_text);
16258        assert_eq!(
16259            editor.selections.ranges(&editor.display_snapshot(cx)),
16260            expected_selections
16261        );
16262
16263        editor.newline(&Newline, window, cx);
16264        let (expected_text, expected_selections) = marked_text_ranges(
16265            indoc! {"
16266                aaaa
16267                bX
16268                ˇbbX
16269                b
16270                bX
16271                ˇbbX
16272                ˇb
16273                cccc"
16274            },
16275            false,
16276        );
16277        assert_eq!(editor.text(cx), expected_text);
16278        assert_eq!(
16279            editor.selections.ranges(&editor.display_snapshot(cx)),
16280            expected_selections
16281        );
16282    });
16283}
16284
16285#[gpui::test]
16286fn test_refresh_selections(cx: &mut TestAppContext) {
16287    init_test(cx, |_| {});
16288
16289    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16290    let mut excerpt1_id = None;
16291    let multibuffer = cx.new(|cx| {
16292        let mut multibuffer = MultiBuffer::new(ReadWrite);
16293        excerpt1_id = multibuffer
16294            .push_excerpts(
16295                buffer.clone(),
16296                [
16297                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16298                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16299                ],
16300                cx,
16301            )
16302            .into_iter()
16303            .next();
16304        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16305        multibuffer
16306    });
16307
16308    let editor = cx.add_window(|window, cx| {
16309        let mut editor = build_editor(multibuffer.clone(), window, cx);
16310        let snapshot = editor.snapshot(window, cx);
16311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16312            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16313        });
16314        editor.begin_selection(
16315            Point::new(2, 1).to_display_point(&snapshot),
16316            true,
16317            1,
16318            window,
16319            cx,
16320        );
16321        assert_eq!(
16322            editor.selections.ranges(&editor.display_snapshot(cx)),
16323            [
16324                Point::new(1, 3)..Point::new(1, 3),
16325                Point::new(2, 1)..Point::new(2, 1),
16326            ]
16327        );
16328        editor
16329    });
16330
16331    // Refreshing selections is a no-op when excerpts haven't changed.
16332    _ = editor.update(cx, |editor, window, cx| {
16333        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16334        assert_eq!(
16335            editor.selections.ranges(&editor.display_snapshot(cx)),
16336            [
16337                Point::new(1, 3)..Point::new(1, 3),
16338                Point::new(2, 1)..Point::new(2, 1),
16339            ]
16340        );
16341    });
16342
16343    multibuffer.update(cx, |multibuffer, cx| {
16344        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16345    });
16346    _ = editor.update(cx, |editor, window, cx| {
16347        // Removing an excerpt causes the first selection to become degenerate.
16348        assert_eq!(
16349            editor.selections.ranges(&editor.display_snapshot(cx)),
16350            [
16351                Point::new(0, 0)..Point::new(0, 0),
16352                Point::new(0, 1)..Point::new(0, 1)
16353            ]
16354        );
16355
16356        // Refreshing selections will relocate the first selection to the original buffer
16357        // location.
16358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16359        assert_eq!(
16360            editor.selections.ranges(&editor.display_snapshot(cx)),
16361            [
16362                Point::new(0, 1)..Point::new(0, 1),
16363                Point::new(0, 3)..Point::new(0, 3)
16364            ]
16365        );
16366        assert!(editor.selections.pending_anchor().is_some());
16367    });
16368}
16369
16370#[gpui::test]
16371fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16372    init_test(cx, |_| {});
16373
16374    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16375    let mut excerpt1_id = None;
16376    let multibuffer = cx.new(|cx| {
16377        let mut multibuffer = MultiBuffer::new(ReadWrite);
16378        excerpt1_id = multibuffer
16379            .push_excerpts(
16380                buffer.clone(),
16381                [
16382                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16383                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16384                ],
16385                cx,
16386            )
16387            .into_iter()
16388            .next();
16389        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16390        multibuffer
16391    });
16392
16393    let editor = cx.add_window(|window, cx| {
16394        let mut editor = build_editor(multibuffer.clone(), window, cx);
16395        let snapshot = editor.snapshot(window, cx);
16396        editor.begin_selection(
16397            Point::new(1, 3).to_display_point(&snapshot),
16398            false,
16399            1,
16400            window,
16401            cx,
16402        );
16403        assert_eq!(
16404            editor.selections.ranges(&editor.display_snapshot(cx)),
16405            [Point::new(1, 3)..Point::new(1, 3)]
16406        );
16407        editor
16408    });
16409
16410    multibuffer.update(cx, |multibuffer, cx| {
16411        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16412    });
16413    _ = editor.update(cx, |editor, window, cx| {
16414        assert_eq!(
16415            editor.selections.ranges(&editor.display_snapshot(cx)),
16416            [Point::new(0, 0)..Point::new(0, 0)]
16417        );
16418
16419        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16420        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16421        assert_eq!(
16422            editor.selections.ranges(&editor.display_snapshot(cx)),
16423            [Point::new(0, 3)..Point::new(0, 3)]
16424        );
16425        assert!(editor.selections.pending_anchor().is_some());
16426    });
16427}
16428
16429#[gpui::test]
16430async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16431    init_test(cx, |_| {});
16432
16433    let language = Arc::new(
16434        Language::new(
16435            LanguageConfig {
16436                brackets: BracketPairConfig {
16437                    pairs: vec![
16438                        BracketPair {
16439                            start: "{".to_string(),
16440                            end: "}".to_string(),
16441                            close: true,
16442                            surround: true,
16443                            newline: true,
16444                        },
16445                        BracketPair {
16446                            start: "/* ".to_string(),
16447                            end: " */".to_string(),
16448                            close: true,
16449                            surround: true,
16450                            newline: true,
16451                        },
16452                    ],
16453                    ..Default::default()
16454                },
16455                ..Default::default()
16456            },
16457            Some(tree_sitter_rust::LANGUAGE.into()),
16458        )
16459        .with_indents_query("")
16460        .unwrap(),
16461    );
16462
16463    let text = concat!(
16464        "{   }\n",     //
16465        "  x\n",       //
16466        "  /*   */\n", //
16467        "x\n",         //
16468        "{{} }\n",     //
16469    );
16470
16471    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16472    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16473    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16474    editor
16475        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16476        .await;
16477
16478    editor.update_in(cx, |editor, window, cx| {
16479        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16480            s.select_display_ranges([
16481                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16482                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16483                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16484            ])
16485        });
16486        editor.newline(&Newline, window, cx);
16487
16488        assert_eq!(
16489            editor.buffer().read(cx).read(cx).text(),
16490            concat!(
16491                "{ \n",    // Suppress rustfmt
16492                "\n",      //
16493                "}\n",     //
16494                "  x\n",   //
16495                "  /* \n", //
16496                "  \n",    //
16497                "  */\n",  //
16498                "x\n",     //
16499                "{{} \n",  //
16500                "}\n",     //
16501            )
16502        );
16503    });
16504}
16505
16506#[gpui::test]
16507fn test_highlighted_ranges(cx: &mut TestAppContext) {
16508    init_test(cx, |_| {});
16509
16510    let editor = cx.add_window(|window, cx| {
16511        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16512        build_editor(buffer, window, cx)
16513    });
16514
16515    _ = editor.update(cx, |editor, window, cx| {
16516        struct Type1;
16517        struct Type2;
16518
16519        let buffer = editor.buffer.read(cx).snapshot(cx);
16520
16521        let anchor_range =
16522            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16523
16524        editor.highlight_background::<Type1>(
16525            &[
16526                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16527                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16528                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16529                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16530            ],
16531            |_| Hsla::red(),
16532            cx,
16533        );
16534        editor.highlight_background::<Type2>(
16535            &[
16536                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16537                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16538                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16539                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16540            ],
16541            |_| Hsla::green(),
16542            cx,
16543        );
16544
16545        let snapshot = editor.snapshot(window, cx);
16546        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16547            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16548            &snapshot,
16549            cx.theme(),
16550        );
16551        assert_eq!(
16552            highlighted_ranges,
16553            &[
16554                (
16555                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16556                    Hsla::green(),
16557                ),
16558                (
16559                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16560                    Hsla::red(),
16561                ),
16562                (
16563                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16564                    Hsla::green(),
16565                ),
16566                (
16567                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16568                    Hsla::red(),
16569                ),
16570            ]
16571        );
16572        assert_eq!(
16573            editor.sorted_background_highlights_in_range(
16574                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16575                &snapshot,
16576                cx.theme(),
16577            ),
16578            &[(
16579                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16580                Hsla::red(),
16581            )]
16582        );
16583    });
16584}
16585
16586#[gpui::test]
16587async fn test_following(cx: &mut TestAppContext) {
16588    init_test(cx, |_| {});
16589
16590    let fs = FakeFs::new(cx.executor());
16591    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16592
16593    let buffer = project.update(cx, |project, cx| {
16594        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16595        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16596    });
16597    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16598    let follower = cx.update(|cx| {
16599        cx.open_window(
16600            WindowOptions {
16601                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16602                    gpui::Point::new(px(0.), px(0.)),
16603                    gpui::Point::new(px(10.), px(80.)),
16604                ))),
16605                ..Default::default()
16606            },
16607            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16608        )
16609        .unwrap()
16610    });
16611
16612    let is_still_following = Rc::new(RefCell::new(true));
16613    let follower_edit_event_count = Rc::new(RefCell::new(0));
16614    let pending_update = Rc::new(RefCell::new(None));
16615    let leader_entity = leader.root(cx).unwrap();
16616    let follower_entity = follower.root(cx).unwrap();
16617    _ = follower.update(cx, {
16618        let update = pending_update.clone();
16619        let is_still_following = is_still_following.clone();
16620        let follower_edit_event_count = follower_edit_event_count.clone();
16621        |_, window, cx| {
16622            cx.subscribe_in(
16623                &leader_entity,
16624                window,
16625                move |_, leader, event, window, cx| {
16626                    leader.read(cx).add_event_to_update_proto(
16627                        event,
16628                        &mut update.borrow_mut(),
16629                        window,
16630                        cx,
16631                    );
16632                },
16633            )
16634            .detach();
16635
16636            cx.subscribe_in(
16637                &follower_entity,
16638                window,
16639                move |_, _, event: &EditorEvent, _window, _cx| {
16640                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16641                        *is_still_following.borrow_mut() = false;
16642                    }
16643
16644                    if let EditorEvent::BufferEdited = event {
16645                        *follower_edit_event_count.borrow_mut() += 1;
16646                    }
16647                },
16648            )
16649            .detach();
16650        }
16651    });
16652
16653    // Update the selections only
16654    _ = leader.update(cx, |leader, window, cx| {
16655        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16656            s.select_ranges([1..1])
16657        });
16658    });
16659    follower
16660        .update(cx, |follower, window, cx| {
16661            follower.apply_update_proto(
16662                &project,
16663                pending_update.borrow_mut().take().unwrap(),
16664                window,
16665                cx,
16666            )
16667        })
16668        .unwrap()
16669        .await
16670        .unwrap();
16671    _ = follower.update(cx, |follower, _, cx| {
16672        assert_eq!(
16673            follower.selections.ranges(&follower.display_snapshot(cx)),
16674            vec![1..1]
16675        );
16676    });
16677    assert!(*is_still_following.borrow());
16678    assert_eq!(*follower_edit_event_count.borrow(), 0);
16679
16680    // Update the scroll position only
16681    _ = leader.update(cx, |leader, window, cx| {
16682        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16683    });
16684    follower
16685        .update(cx, |follower, window, cx| {
16686            follower.apply_update_proto(
16687                &project,
16688                pending_update.borrow_mut().take().unwrap(),
16689                window,
16690                cx,
16691            )
16692        })
16693        .unwrap()
16694        .await
16695        .unwrap();
16696    assert_eq!(
16697        follower
16698            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16699            .unwrap(),
16700        gpui::Point::new(1.5, 3.5)
16701    );
16702    assert!(*is_still_following.borrow());
16703    assert_eq!(*follower_edit_event_count.borrow(), 0);
16704
16705    // Update the selections and scroll position. The follower's scroll position is updated
16706    // via autoscroll, not via the leader's exact scroll position.
16707    _ = leader.update(cx, |leader, window, cx| {
16708        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16709            s.select_ranges([0..0])
16710        });
16711        leader.request_autoscroll(Autoscroll::newest(), cx);
16712        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16713    });
16714    follower
16715        .update(cx, |follower, window, cx| {
16716            follower.apply_update_proto(
16717                &project,
16718                pending_update.borrow_mut().take().unwrap(),
16719                window,
16720                cx,
16721            )
16722        })
16723        .unwrap()
16724        .await
16725        .unwrap();
16726    _ = follower.update(cx, |follower, _, cx| {
16727        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16728        assert_eq!(
16729            follower.selections.ranges(&follower.display_snapshot(cx)),
16730            vec![0..0]
16731        );
16732    });
16733    assert!(*is_still_following.borrow());
16734
16735    // Creating a pending selection that precedes another selection
16736    _ = leader.update(cx, |leader, window, cx| {
16737        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16738            s.select_ranges([1..1])
16739        });
16740        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16741    });
16742    follower
16743        .update(cx, |follower, window, cx| {
16744            follower.apply_update_proto(
16745                &project,
16746                pending_update.borrow_mut().take().unwrap(),
16747                window,
16748                cx,
16749            )
16750        })
16751        .unwrap()
16752        .await
16753        .unwrap();
16754    _ = follower.update(cx, |follower, _, cx| {
16755        assert_eq!(
16756            follower.selections.ranges(&follower.display_snapshot(cx)),
16757            vec![0..0, 1..1]
16758        );
16759    });
16760    assert!(*is_still_following.borrow());
16761
16762    // Extend the pending selection so that it surrounds another selection
16763    _ = leader.update(cx, |leader, window, cx| {
16764        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16765    });
16766    follower
16767        .update(cx, |follower, window, cx| {
16768            follower.apply_update_proto(
16769                &project,
16770                pending_update.borrow_mut().take().unwrap(),
16771                window,
16772                cx,
16773            )
16774        })
16775        .unwrap()
16776        .await
16777        .unwrap();
16778    _ = follower.update(cx, |follower, _, cx| {
16779        assert_eq!(
16780            follower.selections.ranges(&follower.display_snapshot(cx)),
16781            vec![0..2]
16782        );
16783    });
16784
16785    // Scrolling locally breaks the follow
16786    _ = follower.update(cx, |follower, window, cx| {
16787        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16788        follower.set_scroll_anchor(
16789            ScrollAnchor {
16790                anchor: top_anchor,
16791                offset: gpui::Point::new(0.0, 0.5),
16792            },
16793            window,
16794            cx,
16795        );
16796    });
16797    assert!(!(*is_still_following.borrow()));
16798}
16799
16800#[gpui::test]
16801async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16802    init_test(cx, |_| {});
16803
16804    let fs = FakeFs::new(cx.executor());
16805    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16806    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16807    let pane = workspace
16808        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16809        .unwrap();
16810
16811    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16812
16813    let leader = pane.update_in(cx, |_, window, cx| {
16814        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16815        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16816    });
16817
16818    // Start following the editor when it has no excerpts.
16819    let mut state_message =
16820        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16821    let workspace_entity = workspace.root(cx).unwrap();
16822    let follower_1 = cx
16823        .update_window(*workspace.deref(), |_, window, cx| {
16824            Editor::from_state_proto(
16825                workspace_entity,
16826                ViewId {
16827                    creator: CollaboratorId::PeerId(PeerId::default()),
16828                    id: 0,
16829                },
16830                &mut state_message,
16831                window,
16832                cx,
16833            )
16834        })
16835        .unwrap()
16836        .unwrap()
16837        .await
16838        .unwrap();
16839
16840    let update_message = Rc::new(RefCell::new(None));
16841    follower_1.update_in(cx, {
16842        let update = update_message.clone();
16843        |_, window, cx| {
16844            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16845                leader.read(cx).add_event_to_update_proto(
16846                    event,
16847                    &mut update.borrow_mut(),
16848                    window,
16849                    cx,
16850                );
16851            })
16852            .detach();
16853        }
16854    });
16855
16856    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16857        (
16858            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16859            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16860        )
16861    });
16862
16863    // Insert some excerpts.
16864    leader.update(cx, |leader, cx| {
16865        leader.buffer.update(cx, |multibuffer, cx| {
16866            multibuffer.set_excerpts_for_path(
16867                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16868                buffer_1.clone(),
16869                vec![
16870                    Point::row_range(0..3),
16871                    Point::row_range(1..6),
16872                    Point::row_range(12..15),
16873                ],
16874                0,
16875                cx,
16876            );
16877            multibuffer.set_excerpts_for_path(
16878                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16879                buffer_2.clone(),
16880                vec![Point::row_range(0..6), Point::row_range(8..12)],
16881                0,
16882                cx,
16883            );
16884        });
16885    });
16886
16887    // Apply the update of adding the excerpts.
16888    follower_1
16889        .update_in(cx, |follower, window, cx| {
16890            follower.apply_update_proto(
16891                &project,
16892                update_message.borrow().clone().unwrap(),
16893                window,
16894                cx,
16895            )
16896        })
16897        .await
16898        .unwrap();
16899    assert_eq!(
16900        follower_1.update(cx, |editor, cx| editor.text(cx)),
16901        leader.update(cx, |editor, cx| editor.text(cx))
16902    );
16903    update_message.borrow_mut().take();
16904
16905    // Start following separately after it already has excerpts.
16906    let mut state_message =
16907        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16908    let workspace_entity = workspace.root(cx).unwrap();
16909    let follower_2 = cx
16910        .update_window(*workspace.deref(), |_, window, cx| {
16911            Editor::from_state_proto(
16912                workspace_entity,
16913                ViewId {
16914                    creator: CollaboratorId::PeerId(PeerId::default()),
16915                    id: 0,
16916                },
16917                &mut state_message,
16918                window,
16919                cx,
16920            )
16921        })
16922        .unwrap()
16923        .unwrap()
16924        .await
16925        .unwrap();
16926    assert_eq!(
16927        follower_2.update(cx, |editor, cx| editor.text(cx)),
16928        leader.update(cx, |editor, cx| editor.text(cx))
16929    );
16930
16931    // Remove some excerpts.
16932    leader.update(cx, |leader, cx| {
16933        leader.buffer.update(cx, |multibuffer, cx| {
16934            let excerpt_ids = multibuffer.excerpt_ids();
16935            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16936            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16937        });
16938    });
16939
16940    // Apply the update of removing the excerpts.
16941    follower_1
16942        .update_in(cx, |follower, window, cx| {
16943            follower.apply_update_proto(
16944                &project,
16945                update_message.borrow().clone().unwrap(),
16946                window,
16947                cx,
16948            )
16949        })
16950        .await
16951        .unwrap();
16952    follower_2
16953        .update_in(cx, |follower, window, cx| {
16954            follower.apply_update_proto(
16955                &project,
16956                update_message.borrow().clone().unwrap(),
16957                window,
16958                cx,
16959            )
16960        })
16961        .await
16962        .unwrap();
16963    update_message.borrow_mut().take();
16964    assert_eq!(
16965        follower_1.update(cx, |editor, cx| editor.text(cx)),
16966        leader.update(cx, |editor, cx| editor.text(cx))
16967    );
16968}
16969
16970#[gpui::test]
16971async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16972    init_test(cx, |_| {});
16973
16974    let mut cx = EditorTestContext::new(cx).await;
16975    let lsp_store =
16976        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16977
16978    cx.set_state(indoc! {"
16979        ˇfn func(abc def: i32) -> u32 {
16980        }
16981    "});
16982
16983    cx.update(|_, cx| {
16984        lsp_store.update(cx, |lsp_store, cx| {
16985            lsp_store
16986                .update_diagnostics(
16987                    LanguageServerId(0),
16988                    lsp::PublishDiagnosticsParams {
16989                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16990                        version: None,
16991                        diagnostics: vec![
16992                            lsp::Diagnostic {
16993                                range: lsp::Range::new(
16994                                    lsp::Position::new(0, 11),
16995                                    lsp::Position::new(0, 12),
16996                                ),
16997                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16998                                ..Default::default()
16999                            },
17000                            lsp::Diagnostic {
17001                                range: lsp::Range::new(
17002                                    lsp::Position::new(0, 12),
17003                                    lsp::Position::new(0, 15),
17004                                ),
17005                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17006                                ..Default::default()
17007                            },
17008                            lsp::Diagnostic {
17009                                range: lsp::Range::new(
17010                                    lsp::Position::new(0, 25),
17011                                    lsp::Position::new(0, 28),
17012                                ),
17013                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17014                                ..Default::default()
17015                            },
17016                        ],
17017                    },
17018                    None,
17019                    DiagnosticSourceKind::Pushed,
17020                    &[],
17021                    cx,
17022                )
17023                .unwrap()
17024        });
17025    });
17026
17027    executor.run_until_parked();
17028
17029    cx.update_editor(|editor, window, cx| {
17030        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17031    });
17032
17033    cx.assert_editor_state(indoc! {"
17034        fn func(abc def: i32) -> ˇu32 {
17035        }
17036    "});
17037
17038    cx.update_editor(|editor, window, cx| {
17039        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17040    });
17041
17042    cx.assert_editor_state(indoc! {"
17043        fn func(abc ˇdef: i32) -> u32 {
17044        }
17045    "});
17046
17047    cx.update_editor(|editor, window, cx| {
17048        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17049    });
17050
17051    cx.assert_editor_state(indoc! {"
17052        fn func(abcˇ def: i32) -> u32 {
17053        }
17054    "});
17055
17056    cx.update_editor(|editor, window, cx| {
17057        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17058    });
17059
17060    cx.assert_editor_state(indoc! {"
17061        fn func(abc def: i32) -> ˇu32 {
17062        }
17063    "});
17064}
17065
17066#[gpui::test]
17067async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17068    init_test(cx, |_| {});
17069
17070    let mut cx = EditorTestContext::new(cx).await;
17071
17072    let diff_base = r#"
17073        use some::mod;
17074
17075        const A: u32 = 42;
17076
17077        fn main() {
17078            println!("hello");
17079
17080            println!("world");
17081        }
17082        "#
17083    .unindent();
17084
17085    // Edits are modified, removed, modified, added
17086    cx.set_state(
17087        &r#"
17088        use some::modified;
17089
17090        ˇ
17091        fn main() {
17092            println!("hello there");
17093
17094            println!("around the");
17095            println!("world");
17096        }
17097        "#
17098        .unindent(),
17099    );
17100
17101    cx.set_head_text(&diff_base);
17102    executor.run_until_parked();
17103
17104    cx.update_editor(|editor, window, cx| {
17105        //Wrap around the bottom of the buffer
17106        for _ in 0..3 {
17107            editor.go_to_next_hunk(&GoToHunk, window, cx);
17108        }
17109    });
17110
17111    cx.assert_editor_state(
17112        &r#"
17113        ˇuse some::modified;
17114
17115
17116        fn main() {
17117            println!("hello there");
17118
17119            println!("around the");
17120            println!("world");
17121        }
17122        "#
17123        .unindent(),
17124    );
17125
17126    cx.update_editor(|editor, window, cx| {
17127        //Wrap around the top of the buffer
17128        for _ in 0..2 {
17129            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17130        }
17131    });
17132
17133    cx.assert_editor_state(
17134        &r#"
17135        use some::modified;
17136
17137
17138        fn main() {
17139        ˇ    println!("hello there");
17140
17141            println!("around the");
17142            println!("world");
17143        }
17144        "#
17145        .unindent(),
17146    );
17147
17148    cx.update_editor(|editor, window, cx| {
17149        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17150    });
17151
17152    cx.assert_editor_state(
17153        &r#"
17154        use some::modified;
17155
17156        ˇ
17157        fn main() {
17158            println!("hello there");
17159
17160            println!("around the");
17161            println!("world");
17162        }
17163        "#
17164        .unindent(),
17165    );
17166
17167    cx.update_editor(|editor, window, cx| {
17168        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17169    });
17170
17171    cx.assert_editor_state(
17172        &r#"
17173        ˇuse some::modified;
17174
17175
17176        fn main() {
17177            println!("hello there");
17178
17179            println!("around the");
17180            println!("world");
17181        }
17182        "#
17183        .unindent(),
17184    );
17185
17186    cx.update_editor(|editor, window, cx| {
17187        for _ in 0..2 {
17188            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17189        }
17190    });
17191
17192    cx.assert_editor_state(
17193        &r#"
17194        use some::modified;
17195
17196
17197        fn main() {
17198        ˇ    println!("hello there");
17199
17200            println!("around the");
17201            println!("world");
17202        }
17203        "#
17204        .unindent(),
17205    );
17206
17207    cx.update_editor(|editor, window, cx| {
17208        editor.fold(&Fold, window, cx);
17209    });
17210
17211    cx.update_editor(|editor, window, cx| {
17212        editor.go_to_next_hunk(&GoToHunk, window, cx);
17213    });
17214
17215    cx.assert_editor_state(
17216        &r#"
17217        ˇuse some::modified;
17218
17219
17220        fn main() {
17221            println!("hello there");
17222
17223            println!("around the");
17224            println!("world");
17225        }
17226        "#
17227        .unindent(),
17228    );
17229}
17230
17231#[test]
17232fn test_split_words() {
17233    fn split(text: &str) -> Vec<&str> {
17234        split_words(text).collect()
17235    }
17236
17237    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17238    assert_eq!(split("hello_world"), &["hello_", "world"]);
17239    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17240    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17241    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17242    assert_eq!(split("helloworld"), &["helloworld"]);
17243
17244    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17245}
17246
17247#[gpui::test]
17248async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17249    init_test(cx, |_| {});
17250
17251    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17252    let mut assert = |before, after| {
17253        let _state_context = cx.set_state(before);
17254        cx.run_until_parked();
17255        cx.update_editor(|editor, window, cx| {
17256            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17257        });
17258        cx.run_until_parked();
17259        cx.assert_editor_state(after);
17260    };
17261
17262    // Outside bracket jumps to outside of matching bracket
17263    assert("console.logˇ(var);", "console.log(var)ˇ;");
17264    assert("console.log(var)ˇ;", "console.logˇ(var);");
17265
17266    // Inside bracket jumps to inside of matching bracket
17267    assert("console.log(ˇvar);", "console.log(varˇ);");
17268    assert("console.log(varˇ);", "console.log(ˇvar);");
17269
17270    // When outside a bracket and inside, favor jumping to the inside bracket
17271    assert(
17272        "console.log('foo', [1, 2, 3]ˇ);",
17273        "console.log(ˇ'foo', [1, 2, 3]);",
17274    );
17275    assert(
17276        "console.log(ˇ'foo', [1, 2, 3]);",
17277        "console.log('foo', [1, 2, 3]ˇ);",
17278    );
17279
17280    // Bias forward if two options are equally likely
17281    assert(
17282        "let result = curried_fun()ˇ();",
17283        "let result = curried_fun()()ˇ;",
17284    );
17285
17286    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17287    assert(
17288        indoc! {"
17289            function test() {
17290                console.log('test')ˇ
17291            }"},
17292        indoc! {"
17293            function test() {
17294                console.logˇ('test')
17295            }"},
17296    );
17297}
17298
17299#[gpui::test]
17300async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17301    init_test(cx, |_| {});
17302
17303    let fs = FakeFs::new(cx.executor());
17304    fs.insert_tree(
17305        path!("/a"),
17306        json!({
17307            "main.rs": "fn main() { let a = 5; }",
17308            "other.rs": "// Test file",
17309        }),
17310    )
17311    .await;
17312    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17313
17314    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17315    language_registry.add(Arc::new(Language::new(
17316        LanguageConfig {
17317            name: "Rust".into(),
17318            matcher: LanguageMatcher {
17319                path_suffixes: vec!["rs".to_string()],
17320                ..Default::default()
17321            },
17322            brackets: BracketPairConfig {
17323                pairs: vec![BracketPair {
17324                    start: "{".to_string(),
17325                    end: "}".to_string(),
17326                    close: true,
17327                    surround: true,
17328                    newline: true,
17329                }],
17330                disabled_scopes_by_bracket_ix: Vec::new(),
17331            },
17332            ..Default::default()
17333        },
17334        Some(tree_sitter_rust::LANGUAGE.into()),
17335    )));
17336    let mut fake_servers = language_registry.register_fake_lsp(
17337        "Rust",
17338        FakeLspAdapter {
17339            capabilities: lsp::ServerCapabilities {
17340                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17341                    first_trigger_character: "{".to_string(),
17342                    more_trigger_character: None,
17343                }),
17344                ..Default::default()
17345            },
17346            ..Default::default()
17347        },
17348    );
17349
17350    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17351
17352    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17353
17354    let worktree_id = workspace
17355        .update(cx, |workspace, _, cx| {
17356            workspace.project().update(cx, |project, cx| {
17357                project.worktrees(cx).next().unwrap().read(cx).id()
17358            })
17359        })
17360        .unwrap();
17361
17362    let buffer = project
17363        .update(cx, |project, cx| {
17364            project.open_local_buffer(path!("/a/main.rs"), cx)
17365        })
17366        .await
17367        .unwrap();
17368    let editor_handle = workspace
17369        .update(cx, |workspace, window, cx| {
17370            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17371        })
17372        .unwrap()
17373        .await
17374        .unwrap()
17375        .downcast::<Editor>()
17376        .unwrap();
17377
17378    cx.executor().start_waiting();
17379    let fake_server = fake_servers.next().await.unwrap();
17380
17381    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17382        |params, _| async move {
17383            assert_eq!(
17384                params.text_document_position.text_document.uri,
17385                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17386            );
17387            assert_eq!(
17388                params.text_document_position.position,
17389                lsp::Position::new(0, 21),
17390            );
17391
17392            Ok(Some(vec![lsp::TextEdit {
17393                new_text: "]".to_string(),
17394                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17395            }]))
17396        },
17397    );
17398
17399    editor_handle.update_in(cx, |editor, window, cx| {
17400        window.focus(&editor.focus_handle(cx));
17401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17402            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17403        });
17404        editor.handle_input("{", window, cx);
17405    });
17406
17407    cx.executor().run_until_parked();
17408
17409    buffer.update(cx, |buffer, _| {
17410        assert_eq!(
17411            buffer.text(),
17412            "fn main() { let a = {5}; }",
17413            "No extra braces from on type formatting should appear in the buffer"
17414        )
17415    });
17416}
17417
17418#[gpui::test(iterations = 20, seeds(31))]
17419async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17420    init_test(cx, |_| {});
17421
17422    let mut cx = EditorLspTestContext::new_rust(
17423        lsp::ServerCapabilities {
17424            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17425                first_trigger_character: ".".to_string(),
17426                more_trigger_character: None,
17427            }),
17428            ..Default::default()
17429        },
17430        cx,
17431    )
17432    .await;
17433
17434    cx.update_buffer(|buffer, _| {
17435        // This causes autoindent to be async.
17436        buffer.set_sync_parse_timeout(Duration::ZERO)
17437    });
17438
17439    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17440    cx.simulate_keystroke("\n");
17441    cx.run_until_parked();
17442
17443    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17444    let mut request =
17445        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17446            let buffer_cloned = buffer_cloned.clone();
17447            async move {
17448                buffer_cloned.update(&mut cx, |buffer, _| {
17449                    assert_eq!(
17450                        buffer.text(),
17451                        "fn c() {\n    d()\n        .\n}\n",
17452                        "OnTypeFormatting should triggered after autoindent applied"
17453                    )
17454                })?;
17455
17456                Ok(Some(vec![]))
17457            }
17458        });
17459
17460    cx.simulate_keystroke(".");
17461    cx.run_until_parked();
17462
17463    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17464    assert!(request.next().await.is_some());
17465    request.close();
17466    assert!(request.next().await.is_none());
17467}
17468
17469#[gpui::test]
17470async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17471    init_test(cx, |_| {});
17472
17473    let fs = FakeFs::new(cx.executor());
17474    fs.insert_tree(
17475        path!("/a"),
17476        json!({
17477            "main.rs": "fn main() { let a = 5; }",
17478            "other.rs": "// Test file",
17479        }),
17480    )
17481    .await;
17482
17483    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17484
17485    let server_restarts = Arc::new(AtomicUsize::new(0));
17486    let closure_restarts = Arc::clone(&server_restarts);
17487    let language_server_name = "test language server";
17488    let language_name: LanguageName = "Rust".into();
17489
17490    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17491    language_registry.add(Arc::new(Language::new(
17492        LanguageConfig {
17493            name: language_name.clone(),
17494            matcher: LanguageMatcher {
17495                path_suffixes: vec!["rs".to_string()],
17496                ..Default::default()
17497            },
17498            ..Default::default()
17499        },
17500        Some(tree_sitter_rust::LANGUAGE.into()),
17501    )));
17502    let mut fake_servers = language_registry.register_fake_lsp(
17503        "Rust",
17504        FakeLspAdapter {
17505            name: language_server_name,
17506            initialization_options: Some(json!({
17507                "testOptionValue": true
17508            })),
17509            initializer: Some(Box::new(move |fake_server| {
17510                let task_restarts = Arc::clone(&closure_restarts);
17511                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17512                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17513                    futures::future::ready(Ok(()))
17514                });
17515            })),
17516            ..Default::default()
17517        },
17518    );
17519
17520    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17521    let _buffer = project
17522        .update(cx, |project, cx| {
17523            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17524        })
17525        .await
17526        .unwrap();
17527    let _fake_server = fake_servers.next().await.unwrap();
17528    update_test_language_settings(cx, |language_settings| {
17529        language_settings.languages.0.insert(
17530            language_name.clone().0,
17531            LanguageSettingsContent {
17532                tab_size: NonZeroU32::new(8),
17533                ..Default::default()
17534            },
17535        );
17536    });
17537    cx.executor().run_until_parked();
17538    assert_eq!(
17539        server_restarts.load(atomic::Ordering::Acquire),
17540        0,
17541        "Should not restart LSP server on an unrelated change"
17542    );
17543
17544    update_test_project_settings(cx, |project_settings| {
17545        project_settings.lsp.insert(
17546            "Some other server name".into(),
17547            LspSettings {
17548                binary: None,
17549                settings: None,
17550                initialization_options: Some(json!({
17551                    "some other init value": false
17552                })),
17553                enable_lsp_tasks: false,
17554                fetch: None,
17555            },
17556        );
17557    });
17558    cx.executor().run_until_parked();
17559    assert_eq!(
17560        server_restarts.load(atomic::Ordering::Acquire),
17561        0,
17562        "Should not restart LSP server on an unrelated LSP settings change"
17563    );
17564
17565    update_test_project_settings(cx, |project_settings| {
17566        project_settings.lsp.insert(
17567            language_server_name.into(),
17568            LspSettings {
17569                binary: None,
17570                settings: None,
17571                initialization_options: Some(json!({
17572                    "anotherInitValue": false
17573                })),
17574                enable_lsp_tasks: false,
17575                fetch: None,
17576            },
17577        );
17578    });
17579    cx.executor().run_until_parked();
17580    assert_eq!(
17581        server_restarts.load(atomic::Ordering::Acquire),
17582        1,
17583        "Should restart LSP server on a related LSP settings change"
17584    );
17585
17586    update_test_project_settings(cx, |project_settings| {
17587        project_settings.lsp.insert(
17588            language_server_name.into(),
17589            LspSettings {
17590                binary: None,
17591                settings: None,
17592                initialization_options: Some(json!({
17593                    "anotherInitValue": false
17594                })),
17595                enable_lsp_tasks: false,
17596                fetch: None,
17597            },
17598        );
17599    });
17600    cx.executor().run_until_parked();
17601    assert_eq!(
17602        server_restarts.load(atomic::Ordering::Acquire),
17603        1,
17604        "Should not restart LSP server on a related LSP settings change that is the same"
17605    );
17606
17607    update_test_project_settings(cx, |project_settings| {
17608        project_settings.lsp.insert(
17609            language_server_name.into(),
17610            LspSettings {
17611                binary: None,
17612                settings: None,
17613                initialization_options: None,
17614                enable_lsp_tasks: false,
17615                fetch: None,
17616            },
17617        );
17618    });
17619    cx.executor().run_until_parked();
17620    assert_eq!(
17621        server_restarts.load(atomic::Ordering::Acquire),
17622        2,
17623        "Should restart LSP server on another related LSP settings change"
17624    );
17625}
17626
17627#[gpui::test]
17628async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17629    init_test(cx, |_| {});
17630
17631    let mut cx = EditorLspTestContext::new_rust(
17632        lsp::ServerCapabilities {
17633            completion_provider: Some(lsp::CompletionOptions {
17634                trigger_characters: Some(vec![".".to_string()]),
17635                resolve_provider: Some(true),
17636                ..Default::default()
17637            }),
17638            ..Default::default()
17639        },
17640        cx,
17641    )
17642    .await;
17643
17644    cx.set_state("fn main() { let a = 2ˇ; }");
17645    cx.simulate_keystroke(".");
17646    let completion_item = lsp::CompletionItem {
17647        label: "some".into(),
17648        kind: Some(lsp::CompletionItemKind::SNIPPET),
17649        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17650        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17651            kind: lsp::MarkupKind::Markdown,
17652            value: "```rust\nSome(2)\n```".to_string(),
17653        })),
17654        deprecated: Some(false),
17655        sort_text: Some("fffffff2".to_string()),
17656        filter_text: Some("some".to_string()),
17657        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17658        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17659            range: lsp::Range {
17660                start: lsp::Position {
17661                    line: 0,
17662                    character: 22,
17663                },
17664                end: lsp::Position {
17665                    line: 0,
17666                    character: 22,
17667                },
17668            },
17669            new_text: "Some(2)".to_string(),
17670        })),
17671        additional_text_edits: Some(vec![lsp::TextEdit {
17672            range: lsp::Range {
17673                start: lsp::Position {
17674                    line: 0,
17675                    character: 20,
17676                },
17677                end: lsp::Position {
17678                    line: 0,
17679                    character: 22,
17680                },
17681            },
17682            new_text: "".to_string(),
17683        }]),
17684        ..Default::default()
17685    };
17686
17687    let closure_completion_item = completion_item.clone();
17688    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17689        let task_completion_item = closure_completion_item.clone();
17690        async move {
17691            Ok(Some(lsp::CompletionResponse::Array(vec![
17692                task_completion_item,
17693            ])))
17694        }
17695    });
17696
17697    request.next().await;
17698
17699    cx.condition(|editor, _| editor.context_menu_visible())
17700        .await;
17701    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17702        editor
17703            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17704            .unwrap()
17705    });
17706    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17707
17708    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17709        let task_completion_item = completion_item.clone();
17710        async move { Ok(task_completion_item) }
17711    })
17712    .next()
17713    .await
17714    .unwrap();
17715    apply_additional_edits.await.unwrap();
17716    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17717}
17718
17719#[gpui::test]
17720async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17721    init_test(cx, |_| {});
17722
17723    let mut cx = EditorLspTestContext::new_rust(
17724        lsp::ServerCapabilities {
17725            completion_provider: Some(lsp::CompletionOptions {
17726                trigger_characters: Some(vec![".".to_string()]),
17727                resolve_provider: Some(true),
17728                ..Default::default()
17729            }),
17730            ..Default::default()
17731        },
17732        cx,
17733    )
17734    .await;
17735
17736    cx.set_state("fn main() { let a = 2ˇ; }");
17737    cx.simulate_keystroke(".");
17738
17739    let item1 = lsp::CompletionItem {
17740        label: "method id()".to_string(),
17741        filter_text: Some("id".to_string()),
17742        detail: None,
17743        documentation: None,
17744        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17745            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17746            new_text: ".id".to_string(),
17747        })),
17748        ..lsp::CompletionItem::default()
17749    };
17750
17751    let item2 = lsp::CompletionItem {
17752        label: "other".to_string(),
17753        filter_text: Some("other".to_string()),
17754        detail: None,
17755        documentation: None,
17756        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17757            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17758            new_text: ".other".to_string(),
17759        })),
17760        ..lsp::CompletionItem::default()
17761    };
17762
17763    let item1 = item1.clone();
17764    cx.set_request_handler::<lsp::request::Completion, _, _>({
17765        let item1 = item1.clone();
17766        move |_, _, _| {
17767            let item1 = item1.clone();
17768            let item2 = item2.clone();
17769            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17770        }
17771    })
17772    .next()
17773    .await;
17774
17775    cx.condition(|editor, _| editor.context_menu_visible())
17776        .await;
17777    cx.update_editor(|editor, _, _| {
17778        let context_menu = editor.context_menu.borrow_mut();
17779        let context_menu = context_menu
17780            .as_ref()
17781            .expect("Should have the context menu deployed");
17782        match context_menu {
17783            CodeContextMenu::Completions(completions_menu) => {
17784                let completions = completions_menu.completions.borrow_mut();
17785                assert_eq!(
17786                    completions
17787                        .iter()
17788                        .map(|completion| &completion.label.text)
17789                        .collect::<Vec<_>>(),
17790                    vec!["method id()", "other"]
17791                )
17792            }
17793            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17794        }
17795    });
17796
17797    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17798        let item1 = item1.clone();
17799        move |_, item_to_resolve, _| {
17800            let item1 = item1.clone();
17801            async move {
17802                if item1 == item_to_resolve {
17803                    Ok(lsp::CompletionItem {
17804                        label: "method id()".to_string(),
17805                        filter_text: Some("id".to_string()),
17806                        detail: Some("Now resolved!".to_string()),
17807                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17808                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17809                            range: lsp::Range::new(
17810                                lsp::Position::new(0, 22),
17811                                lsp::Position::new(0, 22),
17812                            ),
17813                            new_text: ".id".to_string(),
17814                        })),
17815                        ..lsp::CompletionItem::default()
17816                    })
17817                } else {
17818                    Ok(item_to_resolve)
17819                }
17820            }
17821        }
17822    })
17823    .next()
17824    .await
17825    .unwrap();
17826    cx.run_until_parked();
17827
17828    cx.update_editor(|editor, window, cx| {
17829        editor.context_menu_next(&Default::default(), window, cx);
17830    });
17831
17832    cx.update_editor(|editor, _, _| {
17833        let context_menu = editor.context_menu.borrow_mut();
17834        let context_menu = context_menu
17835            .as_ref()
17836            .expect("Should have the context menu deployed");
17837        match context_menu {
17838            CodeContextMenu::Completions(completions_menu) => {
17839                let completions = completions_menu.completions.borrow_mut();
17840                assert_eq!(
17841                    completions
17842                        .iter()
17843                        .map(|completion| &completion.label.text)
17844                        .collect::<Vec<_>>(),
17845                    vec!["method id() Now resolved!", "other"],
17846                    "Should update first completion label, but not second as the filter text did not match."
17847                );
17848            }
17849            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17850        }
17851    });
17852}
17853
17854#[gpui::test]
17855async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17856    init_test(cx, |_| {});
17857    let mut cx = EditorLspTestContext::new_rust(
17858        lsp::ServerCapabilities {
17859            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17860            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17861            completion_provider: Some(lsp::CompletionOptions {
17862                resolve_provider: Some(true),
17863                ..Default::default()
17864            }),
17865            ..Default::default()
17866        },
17867        cx,
17868    )
17869    .await;
17870    cx.set_state(indoc! {"
17871        struct TestStruct {
17872            field: i32
17873        }
17874
17875        fn mainˇ() {
17876            let unused_var = 42;
17877            let test_struct = TestStruct { field: 42 };
17878        }
17879    "});
17880    let symbol_range = cx.lsp_range(indoc! {"
17881        struct TestStruct {
17882            field: i32
17883        }
17884
17885        «fn main»() {
17886            let unused_var = 42;
17887            let test_struct = TestStruct { field: 42 };
17888        }
17889    "});
17890    let mut hover_requests =
17891        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17892            Ok(Some(lsp::Hover {
17893                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17894                    kind: lsp::MarkupKind::Markdown,
17895                    value: "Function documentation".to_string(),
17896                }),
17897                range: Some(symbol_range),
17898            }))
17899        });
17900
17901    // Case 1: Test that code action menu hide hover popover
17902    cx.dispatch_action(Hover);
17903    hover_requests.next().await;
17904    cx.condition(|editor, _| editor.hover_state.visible()).await;
17905    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17906        move |_, _, _| async move {
17907            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17908                lsp::CodeAction {
17909                    title: "Remove unused variable".to_string(),
17910                    kind: Some(CodeActionKind::QUICKFIX),
17911                    edit: Some(lsp::WorkspaceEdit {
17912                        changes: Some(
17913                            [(
17914                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17915                                vec![lsp::TextEdit {
17916                                    range: lsp::Range::new(
17917                                        lsp::Position::new(5, 4),
17918                                        lsp::Position::new(5, 27),
17919                                    ),
17920                                    new_text: "".to_string(),
17921                                }],
17922                            )]
17923                            .into_iter()
17924                            .collect(),
17925                        ),
17926                        ..Default::default()
17927                    }),
17928                    ..Default::default()
17929                },
17930            )]))
17931        },
17932    );
17933    cx.update_editor(|editor, window, cx| {
17934        editor.toggle_code_actions(
17935            &ToggleCodeActions {
17936                deployed_from: None,
17937                quick_launch: false,
17938            },
17939            window,
17940            cx,
17941        );
17942    });
17943    code_action_requests.next().await;
17944    cx.run_until_parked();
17945    cx.condition(|editor, _| editor.context_menu_visible())
17946        .await;
17947    cx.update_editor(|editor, _, _| {
17948        assert!(
17949            !editor.hover_state.visible(),
17950            "Hover popover should be hidden when code action menu is shown"
17951        );
17952        // Hide code actions
17953        editor.context_menu.take();
17954    });
17955
17956    // Case 2: Test that code completions hide hover popover
17957    cx.dispatch_action(Hover);
17958    hover_requests.next().await;
17959    cx.condition(|editor, _| editor.hover_state.visible()).await;
17960    let counter = Arc::new(AtomicUsize::new(0));
17961    let mut completion_requests =
17962        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17963            let counter = counter.clone();
17964            async move {
17965                counter.fetch_add(1, atomic::Ordering::Release);
17966                Ok(Some(lsp::CompletionResponse::Array(vec![
17967                    lsp::CompletionItem {
17968                        label: "main".into(),
17969                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17970                        detail: Some("() -> ()".to_string()),
17971                        ..Default::default()
17972                    },
17973                    lsp::CompletionItem {
17974                        label: "TestStruct".into(),
17975                        kind: Some(lsp::CompletionItemKind::STRUCT),
17976                        detail: Some("struct TestStruct".to_string()),
17977                        ..Default::default()
17978                    },
17979                ])))
17980            }
17981        });
17982    cx.update_editor(|editor, window, cx| {
17983        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17984    });
17985    completion_requests.next().await;
17986    cx.condition(|editor, _| editor.context_menu_visible())
17987        .await;
17988    cx.update_editor(|editor, _, _| {
17989        assert!(
17990            !editor.hover_state.visible(),
17991            "Hover popover should be hidden when completion menu is shown"
17992        );
17993    });
17994}
17995
17996#[gpui::test]
17997async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17998    init_test(cx, |_| {});
17999
18000    let mut cx = EditorLspTestContext::new_rust(
18001        lsp::ServerCapabilities {
18002            completion_provider: Some(lsp::CompletionOptions {
18003                trigger_characters: Some(vec![".".to_string()]),
18004                resolve_provider: Some(true),
18005                ..Default::default()
18006            }),
18007            ..Default::default()
18008        },
18009        cx,
18010    )
18011    .await;
18012
18013    cx.set_state("fn main() { let a = 2ˇ; }");
18014    cx.simulate_keystroke(".");
18015
18016    let unresolved_item_1 = lsp::CompletionItem {
18017        label: "id".to_string(),
18018        filter_text: Some("id".to_string()),
18019        detail: None,
18020        documentation: None,
18021        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18022            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18023            new_text: ".id".to_string(),
18024        })),
18025        ..lsp::CompletionItem::default()
18026    };
18027    let resolved_item_1 = lsp::CompletionItem {
18028        additional_text_edits: Some(vec![lsp::TextEdit {
18029            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18030            new_text: "!!".to_string(),
18031        }]),
18032        ..unresolved_item_1.clone()
18033    };
18034    let unresolved_item_2 = lsp::CompletionItem {
18035        label: "other".to_string(),
18036        filter_text: Some("other".to_string()),
18037        detail: None,
18038        documentation: None,
18039        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18040            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18041            new_text: ".other".to_string(),
18042        })),
18043        ..lsp::CompletionItem::default()
18044    };
18045    let resolved_item_2 = lsp::CompletionItem {
18046        additional_text_edits: Some(vec![lsp::TextEdit {
18047            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18048            new_text: "??".to_string(),
18049        }]),
18050        ..unresolved_item_2.clone()
18051    };
18052
18053    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18054    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18055    cx.lsp
18056        .server
18057        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18058            let unresolved_item_1 = unresolved_item_1.clone();
18059            let resolved_item_1 = resolved_item_1.clone();
18060            let unresolved_item_2 = unresolved_item_2.clone();
18061            let resolved_item_2 = resolved_item_2.clone();
18062            let resolve_requests_1 = resolve_requests_1.clone();
18063            let resolve_requests_2 = resolve_requests_2.clone();
18064            move |unresolved_request, _| {
18065                let unresolved_item_1 = unresolved_item_1.clone();
18066                let resolved_item_1 = resolved_item_1.clone();
18067                let unresolved_item_2 = unresolved_item_2.clone();
18068                let resolved_item_2 = resolved_item_2.clone();
18069                let resolve_requests_1 = resolve_requests_1.clone();
18070                let resolve_requests_2 = resolve_requests_2.clone();
18071                async move {
18072                    if unresolved_request == unresolved_item_1 {
18073                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18074                        Ok(resolved_item_1.clone())
18075                    } else if unresolved_request == unresolved_item_2 {
18076                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18077                        Ok(resolved_item_2.clone())
18078                    } else {
18079                        panic!("Unexpected completion item {unresolved_request:?}")
18080                    }
18081                }
18082            }
18083        })
18084        .detach();
18085
18086    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18087        let unresolved_item_1 = unresolved_item_1.clone();
18088        let unresolved_item_2 = unresolved_item_2.clone();
18089        async move {
18090            Ok(Some(lsp::CompletionResponse::Array(vec![
18091                unresolved_item_1,
18092                unresolved_item_2,
18093            ])))
18094        }
18095    })
18096    .next()
18097    .await;
18098
18099    cx.condition(|editor, _| editor.context_menu_visible())
18100        .await;
18101    cx.update_editor(|editor, _, _| {
18102        let context_menu = editor.context_menu.borrow_mut();
18103        let context_menu = context_menu
18104            .as_ref()
18105            .expect("Should have the context menu deployed");
18106        match context_menu {
18107            CodeContextMenu::Completions(completions_menu) => {
18108                let completions = completions_menu.completions.borrow_mut();
18109                assert_eq!(
18110                    completions
18111                        .iter()
18112                        .map(|completion| &completion.label.text)
18113                        .collect::<Vec<_>>(),
18114                    vec!["id", "other"]
18115                )
18116            }
18117            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18118        }
18119    });
18120    cx.run_until_parked();
18121
18122    cx.update_editor(|editor, window, cx| {
18123        editor.context_menu_next(&ContextMenuNext, window, cx);
18124    });
18125    cx.run_until_parked();
18126    cx.update_editor(|editor, window, cx| {
18127        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18128    });
18129    cx.run_until_parked();
18130    cx.update_editor(|editor, window, cx| {
18131        editor.context_menu_next(&ContextMenuNext, window, cx);
18132    });
18133    cx.run_until_parked();
18134    cx.update_editor(|editor, window, cx| {
18135        editor
18136            .compose_completion(&ComposeCompletion::default(), window, cx)
18137            .expect("No task returned")
18138    })
18139    .await
18140    .expect("Completion failed");
18141    cx.run_until_parked();
18142
18143    cx.update_editor(|editor, _, cx| {
18144        assert_eq!(
18145            resolve_requests_1.load(atomic::Ordering::Acquire),
18146            1,
18147            "Should always resolve once despite multiple selections"
18148        );
18149        assert_eq!(
18150            resolve_requests_2.load(atomic::Ordering::Acquire),
18151            1,
18152            "Should always resolve once after multiple selections and applying the completion"
18153        );
18154        assert_eq!(
18155            editor.text(cx),
18156            "fn main() { let a = ??.other; }",
18157            "Should use resolved data when applying the completion"
18158        );
18159    });
18160}
18161
18162#[gpui::test]
18163async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18164    init_test(cx, |_| {});
18165
18166    let item_0 = lsp::CompletionItem {
18167        label: "abs".into(),
18168        insert_text: Some("abs".into()),
18169        data: Some(json!({ "very": "special"})),
18170        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18171        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18172            lsp::InsertReplaceEdit {
18173                new_text: "abs".to_string(),
18174                insert: lsp::Range::default(),
18175                replace: lsp::Range::default(),
18176            },
18177        )),
18178        ..lsp::CompletionItem::default()
18179    };
18180    let items = iter::once(item_0.clone())
18181        .chain((11..51).map(|i| lsp::CompletionItem {
18182            label: format!("item_{}", i),
18183            insert_text: Some(format!("item_{}", i)),
18184            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18185            ..lsp::CompletionItem::default()
18186        }))
18187        .collect::<Vec<_>>();
18188
18189    let default_commit_characters = vec!["?".to_string()];
18190    let default_data = json!({ "default": "data"});
18191    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18192    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18193    let default_edit_range = lsp::Range {
18194        start: lsp::Position {
18195            line: 0,
18196            character: 5,
18197        },
18198        end: lsp::Position {
18199            line: 0,
18200            character: 5,
18201        },
18202    };
18203
18204    let mut cx = EditorLspTestContext::new_rust(
18205        lsp::ServerCapabilities {
18206            completion_provider: Some(lsp::CompletionOptions {
18207                trigger_characters: Some(vec![".".to_string()]),
18208                resolve_provider: Some(true),
18209                ..Default::default()
18210            }),
18211            ..Default::default()
18212        },
18213        cx,
18214    )
18215    .await;
18216
18217    cx.set_state("fn main() { let a = 2ˇ; }");
18218    cx.simulate_keystroke(".");
18219
18220    let completion_data = default_data.clone();
18221    let completion_characters = default_commit_characters.clone();
18222    let completion_items = items.clone();
18223    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18224        let default_data = completion_data.clone();
18225        let default_commit_characters = completion_characters.clone();
18226        let items = completion_items.clone();
18227        async move {
18228            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18229                items,
18230                item_defaults: Some(lsp::CompletionListItemDefaults {
18231                    data: Some(default_data.clone()),
18232                    commit_characters: Some(default_commit_characters.clone()),
18233                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18234                        default_edit_range,
18235                    )),
18236                    insert_text_format: Some(default_insert_text_format),
18237                    insert_text_mode: Some(default_insert_text_mode),
18238                }),
18239                ..lsp::CompletionList::default()
18240            })))
18241        }
18242    })
18243    .next()
18244    .await;
18245
18246    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18247    cx.lsp
18248        .server
18249        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18250            let closure_resolved_items = resolved_items.clone();
18251            move |item_to_resolve, _| {
18252                let closure_resolved_items = closure_resolved_items.clone();
18253                async move {
18254                    closure_resolved_items.lock().push(item_to_resolve.clone());
18255                    Ok(item_to_resolve)
18256                }
18257            }
18258        })
18259        .detach();
18260
18261    cx.condition(|editor, _| editor.context_menu_visible())
18262        .await;
18263    cx.run_until_parked();
18264    cx.update_editor(|editor, _, _| {
18265        let menu = editor.context_menu.borrow_mut();
18266        match menu.as_ref().expect("should have the completions menu") {
18267            CodeContextMenu::Completions(completions_menu) => {
18268                assert_eq!(
18269                    completions_menu
18270                        .entries
18271                        .borrow()
18272                        .iter()
18273                        .map(|mat| mat.string.clone())
18274                        .collect::<Vec<String>>(),
18275                    items
18276                        .iter()
18277                        .map(|completion| completion.label.clone())
18278                        .collect::<Vec<String>>()
18279                );
18280            }
18281            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18282        }
18283    });
18284    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18285    // with 4 from the end.
18286    assert_eq!(
18287        *resolved_items.lock(),
18288        [&items[0..16], &items[items.len() - 4..items.len()]]
18289            .concat()
18290            .iter()
18291            .cloned()
18292            .map(|mut item| {
18293                if item.data.is_none() {
18294                    item.data = Some(default_data.clone());
18295                }
18296                item
18297            })
18298            .collect::<Vec<lsp::CompletionItem>>(),
18299        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18300    );
18301    resolved_items.lock().clear();
18302
18303    cx.update_editor(|editor, window, cx| {
18304        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18305    });
18306    cx.run_until_parked();
18307    // Completions that have already been resolved are skipped.
18308    assert_eq!(
18309        *resolved_items.lock(),
18310        items[items.len() - 17..items.len() - 4]
18311            .iter()
18312            .cloned()
18313            .map(|mut item| {
18314                if item.data.is_none() {
18315                    item.data = Some(default_data.clone());
18316                }
18317                item
18318            })
18319            .collect::<Vec<lsp::CompletionItem>>()
18320    );
18321    resolved_items.lock().clear();
18322}
18323
18324#[gpui::test]
18325async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18326    init_test(cx, |_| {});
18327
18328    let mut cx = EditorLspTestContext::new(
18329        Language::new(
18330            LanguageConfig {
18331                matcher: LanguageMatcher {
18332                    path_suffixes: vec!["jsx".into()],
18333                    ..Default::default()
18334                },
18335                overrides: [(
18336                    "element".into(),
18337                    LanguageConfigOverride {
18338                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18339                        ..Default::default()
18340                    },
18341                )]
18342                .into_iter()
18343                .collect(),
18344                ..Default::default()
18345            },
18346            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18347        )
18348        .with_override_query("(jsx_self_closing_element) @element")
18349        .unwrap(),
18350        lsp::ServerCapabilities {
18351            completion_provider: Some(lsp::CompletionOptions {
18352                trigger_characters: Some(vec![":".to_string()]),
18353                ..Default::default()
18354            }),
18355            ..Default::default()
18356        },
18357        cx,
18358    )
18359    .await;
18360
18361    cx.lsp
18362        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18363            Ok(Some(lsp::CompletionResponse::Array(vec![
18364                lsp::CompletionItem {
18365                    label: "bg-blue".into(),
18366                    ..Default::default()
18367                },
18368                lsp::CompletionItem {
18369                    label: "bg-red".into(),
18370                    ..Default::default()
18371                },
18372                lsp::CompletionItem {
18373                    label: "bg-yellow".into(),
18374                    ..Default::default()
18375                },
18376            ])))
18377        });
18378
18379    cx.set_state(r#"<p class="bgˇ" />"#);
18380
18381    // Trigger completion when typing a dash, because the dash is an extra
18382    // word character in the 'element' scope, which contains the cursor.
18383    cx.simulate_keystroke("-");
18384    cx.executor().run_until_parked();
18385    cx.update_editor(|editor, _, _| {
18386        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18387        {
18388            assert_eq!(
18389                completion_menu_entries(menu),
18390                &["bg-blue", "bg-red", "bg-yellow"]
18391            );
18392        } else {
18393            panic!("expected completion menu to be open");
18394        }
18395    });
18396
18397    cx.simulate_keystroke("l");
18398    cx.executor().run_until_parked();
18399    cx.update_editor(|editor, _, _| {
18400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18401        {
18402            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18403        } else {
18404            panic!("expected completion menu to be open");
18405        }
18406    });
18407
18408    // When filtering completions, consider the character after the '-' to
18409    // be the start of a subword.
18410    cx.set_state(r#"<p class="yelˇ" />"#);
18411    cx.simulate_keystroke("l");
18412    cx.executor().run_until_parked();
18413    cx.update_editor(|editor, _, _| {
18414        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18415        {
18416            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18417        } else {
18418            panic!("expected completion menu to be open");
18419        }
18420    });
18421}
18422
18423fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18424    let entries = menu.entries.borrow();
18425    entries.iter().map(|mat| mat.string.clone()).collect()
18426}
18427
18428#[gpui::test]
18429async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18430    init_test(cx, |settings| {
18431        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18432    });
18433
18434    let fs = FakeFs::new(cx.executor());
18435    fs.insert_file(path!("/file.ts"), Default::default()).await;
18436
18437    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18438    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18439
18440    language_registry.add(Arc::new(Language::new(
18441        LanguageConfig {
18442            name: "TypeScript".into(),
18443            matcher: LanguageMatcher {
18444                path_suffixes: vec!["ts".to_string()],
18445                ..Default::default()
18446            },
18447            ..Default::default()
18448        },
18449        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18450    )));
18451    update_test_language_settings(cx, |settings| {
18452        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18453    });
18454
18455    let test_plugin = "test_plugin";
18456    let _ = language_registry.register_fake_lsp(
18457        "TypeScript",
18458        FakeLspAdapter {
18459            prettier_plugins: vec![test_plugin],
18460            ..Default::default()
18461        },
18462    );
18463
18464    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18465    let buffer = project
18466        .update(cx, |project, cx| {
18467            project.open_local_buffer(path!("/file.ts"), cx)
18468        })
18469        .await
18470        .unwrap();
18471
18472    let buffer_text = "one\ntwo\nthree\n";
18473    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18474    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18475    editor.update_in(cx, |editor, window, cx| {
18476        editor.set_text(buffer_text, window, cx)
18477    });
18478
18479    editor
18480        .update_in(cx, |editor, window, cx| {
18481            editor.perform_format(
18482                project.clone(),
18483                FormatTrigger::Manual,
18484                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18485                window,
18486                cx,
18487            )
18488        })
18489        .unwrap()
18490        .await;
18491    assert_eq!(
18492        editor.update(cx, |editor, cx| editor.text(cx)),
18493        buffer_text.to_string() + prettier_format_suffix,
18494        "Test prettier formatting was not applied to the original buffer text",
18495    );
18496
18497    update_test_language_settings(cx, |settings| {
18498        settings.defaults.formatter = Some(FormatterList::default())
18499    });
18500    let format = editor.update_in(cx, |editor, window, cx| {
18501        editor.perform_format(
18502            project.clone(),
18503            FormatTrigger::Manual,
18504            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18505            window,
18506            cx,
18507        )
18508    });
18509    format.await.unwrap();
18510    assert_eq!(
18511        editor.update(cx, |editor, cx| editor.text(cx)),
18512        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18513        "Autoformatting (via test prettier) was not applied to the original buffer text",
18514    );
18515}
18516
18517#[gpui::test]
18518async fn test_addition_reverts(cx: &mut TestAppContext) {
18519    init_test(cx, |_| {});
18520    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18521    let base_text = indoc! {r#"
18522        struct Row;
18523        struct Row1;
18524        struct Row2;
18525
18526        struct Row4;
18527        struct Row5;
18528        struct Row6;
18529
18530        struct Row8;
18531        struct Row9;
18532        struct Row10;"#};
18533
18534    // When addition hunks are not adjacent to carets, no hunk revert is performed
18535    assert_hunk_revert(
18536        indoc! {r#"struct Row;
18537                   struct Row1;
18538                   struct Row1.1;
18539                   struct Row1.2;
18540                   struct Row2;ˇ
18541
18542                   struct Row4;
18543                   struct Row5;
18544                   struct Row6;
18545
18546                   struct Row8;
18547                   ˇstruct Row9;
18548                   struct Row9.1;
18549                   struct Row9.2;
18550                   struct Row9.3;
18551                   struct Row10;"#},
18552        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18553        indoc! {r#"struct Row;
18554                   struct Row1;
18555                   struct Row1.1;
18556                   struct Row1.2;
18557                   struct Row2;ˇ
18558
18559                   struct Row4;
18560                   struct Row5;
18561                   struct Row6;
18562
18563                   struct Row8;
18564                   ˇstruct Row9;
18565                   struct Row9.1;
18566                   struct Row9.2;
18567                   struct Row9.3;
18568                   struct Row10;"#},
18569        base_text,
18570        &mut cx,
18571    );
18572    // Same for selections
18573    assert_hunk_revert(
18574        indoc! {r#"struct Row;
18575                   struct Row1;
18576                   struct Row2;
18577                   struct Row2.1;
18578                   struct Row2.2;
18579                   «ˇ
18580                   struct Row4;
18581                   struct» Row5;
18582                   «struct Row6;
18583                   ˇ»
18584                   struct Row9.1;
18585                   struct Row9.2;
18586                   struct Row9.3;
18587                   struct Row8;
18588                   struct Row9;
18589                   struct Row10;"#},
18590        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18591        indoc! {r#"struct Row;
18592                   struct Row1;
18593                   struct Row2;
18594                   struct Row2.1;
18595                   struct Row2.2;
18596                   «ˇ
18597                   struct Row4;
18598                   struct» Row5;
18599                   «struct Row6;
18600                   ˇ»
18601                   struct Row9.1;
18602                   struct Row9.2;
18603                   struct Row9.3;
18604                   struct Row8;
18605                   struct Row9;
18606                   struct Row10;"#},
18607        base_text,
18608        &mut cx,
18609    );
18610
18611    // When carets and selections intersect the addition hunks, those are reverted.
18612    // Adjacent carets got merged.
18613    assert_hunk_revert(
18614        indoc! {r#"struct Row;
18615                   ˇ// something on the top
18616                   struct Row1;
18617                   struct Row2;
18618                   struct Roˇw3.1;
18619                   struct Row2.2;
18620                   struct Row2.3;ˇ
18621
18622                   struct Row4;
18623                   struct ˇRow5.1;
18624                   struct Row5.2;
18625                   struct «Rowˇ»5.3;
18626                   struct Row5;
18627                   struct Row6;
18628                   ˇ
18629                   struct Row9.1;
18630                   struct «Rowˇ»9.2;
18631                   struct «ˇRow»9.3;
18632                   struct Row8;
18633                   struct Row9;
18634                   «ˇ// something on bottom»
18635                   struct Row10;"#},
18636        vec![
18637            DiffHunkStatusKind::Added,
18638            DiffHunkStatusKind::Added,
18639            DiffHunkStatusKind::Added,
18640            DiffHunkStatusKind::Added,
18641            DiffHunkStatusKind::Added,
18642        ],
18643        indoc! {r#"struct Row;
18644                   ˇstruct Row1;
18645                   struct Row2;
18646                   ˇ
18647                   struct Row4;
18648                   ˇstruct Row5;
18649                   struct Row6;
18650                   ˇ
18651                   ˇstruct Row8;
18652                   struct Row9;
18653                   ˇstruct Row10;"#},
18654        base_text,
18655        &mut cx,
18656    );
18657}
18658
18659#[gpui::test]
18660async fn test_modification_reverts(cx: &mut TestAppContext) {
18661    init_test(cx, |_| {});
18662    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18663    let base_text = indoc! {r#"
18664        struct Row;
18665        struct Row1;
18666        struct Row2;
18667
18668        struct Row4;
18669        struct Row5;
18670        struct Row6;
18671
18672        struct Row8;
18673        struct Row9;
18674        struct Row10;"#};
18675
18676    // Modification hunks behave the same as the addition ones.
18677    assert_hunk_revert(
18678        indoc! {r#"struct Row;
18679                   struct Row1;
18680                   struct Row33;
18681                   ˇ
18682                   struct Row4;
18683                   struct Row5;
18684                   struct Row6;
18685                   ˇ
18686                   struct Row99;
18687                   struct Row9;
18688                   struct Row10;"#},
18689        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18690        indoc! {r#"struct Row;
18691                   struct Row1;
18692                   struct Row33;
18693                   ˇ
18694                   struct Row4;
18695                   struct Row5;
18696                   struct Row6;
18697                   ˇ
18698                   struct Row99;
18699                   struct Row9;
18700                   struct Row10;"#},
18701        base_text,
18702        &mut cx,
18703    );
18704    assert_hunk_revert(
18705        indoc! {r#"struct Row;
18706                   struct Row1;
18707                   struct Row33;
18708                   «ˇ
18709                   struct Row4;
18710                   struct» Row5;
18711                   «struct Row6;
18712                   ˇ»
18713                   struct Row99;
18714                   struct Row9;
18715                   struct Row10;"#},
18716        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18717        indoc! {r#"struct Row;
18718                   struct Row1;
18719                   struct Row33;
18720                   «ˇ
18721                   struct Row4;
18722                   struct» Row5;
18723                   «struct Row6;
18724                   ˇ»
18725                   struct Row99;
18726                   struct Row9;
18727                   struct Row10;"#},
18728        base_text,
18729        &mut cx,
18730    );
18731
18732    assert_hunk_revert(
18733        indoc! {r#"ˇstruct Row1.1;
18734                   struct Row1;
18735                   «ˇstr»uct Row22;
18736
18737                   struct ˇRow44;
18738                   struct Row5;
18739                   struct «Rˇ»ow66;ˇ
18740
18741                   «struˇ»ct Row88;
18742                   struct Row9;
18743                   struct Row1011;ˇ"#},
18744        vec![
18745            DiffHunkStatusKind::Modified,
18746            DiffHunkStatusKind::Modified,
18747            DiffHunkStatusKind::Modified,
18748            DiffHunkStatusKind::Modified,
18749            DiffHunkStatusKind::Modified,
18750            DiffHunkStatusKind::Modified,
18751        ],
18752        indoc! {r#"struct Row;
18753                   ˇstruct Row1;
18754                   struct Row2;
18755                   ˇ
18756                   struct Row4;
18757                   ˇstruct Row5;
18758                   struct Row6;
18759                   ˇ
18760                   struct Row8;
18761                   ˇstruct Row9;
18762                   struct Row10;ˇ"#},
18763        base_text,
18764        &mut cx,
18765    );
18766}
18767
18768#[gpui::test]
18769async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18770    init_test(cx, |_| {});
18771    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18772    let base_text = indoc! {r#"
18773        one
18774
18775        two
18776        three
18777        "#};
18778
18779    cx.set_head_text(base_text);
18780    cx.set_state("\nˇ\n");
18781    cx.executor().run_until_parked();
18782    cx.update_editor(|editor, _window, cx| {
18783        editor.expand_selected_diff_hunks(cx);
18784    });
18785    cx.executor().run_until_parked();
18786    cx.update_editor(|editor, window, cx| {
18787        editor.backspace(&Default::default(), window, cx);
18788    });
18789    cx.run_until_parked();
18790    cx.assert_state_with_diff(
18791        indoc! {r#"
18792
18793        - two
18794        - threeˇ
18795        +
18796        "#}
18797        .to_string(),
18798    );
18799}
18800
18801#[gpui::test]
18802async fn test_deletion_reverts(cx: &mut TestAppContext) {
18803    init_test(cx, |_| {});
18804    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18805    let base_text = indoc! {r#"struct Row;
18806struct Row1;
18807struct Row2;
18808
18809struct Row4;
18810struct Row5;
18811struct Row6;
18812
18813struct Row8;
18814struct Row9;
18815struct Row10;"#};
18816
18817    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18818    assert_hunk_revert(
18819        indoc! {r#"struct Row;
18820                   struct Row2;
18821
18822                   ˇstruct Row4;
18823                   struct Row5;
18824                   struct Row6;
18825                   ˇ
18826                   struct Row8;
18827                   struct Row10;"#},
18828        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18829        indoc! {r#"struct Row;
18830                   struct Row2;
18831
18832                   ˇstruct Row4;
18833                   struct Row5;
18834                   struct Row6;
18835                   ˇ
18836                   struct Row8;
18837                   struct Row10;"#},
18838        base_text,
18839        &mut cx,
18840    );
18841    assert_hunk_revert(
18842        indoc! {r#"struct Row;
18843                   struct Row2;
18844
18845                   «ˇstruct Row4;
18846                   struct» Row5;
18847                   «struct Row6;
18848                   ˇ»
18849                   struct Row8;
18850                   struct Row10;"#},
18851        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18852        indoc! {r#"struct Row;
18853                   struct Row2;
18854
18855                   «ˇstruct Row4;
18856                   struct» Row5;
18857                   «struct Row6;
18858                   ˇ»
18859                   struct Row8;
18860                   struct Row10;"#},
18861        base_text,
18862        &mut cx,
18863    );
18864
18865    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18866    assert_hunk_revert(
18867        indoc! {r#"struct Row;
18868                   ˇstruct Row2;
18869
18870                   struct Row4;
18871                   struct Row5;
18872                   struct Row6;
18873
18874                   struct Row8;ˇ
18875                   struct Row10;"#},
18876        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18877        indoc! {r#"struct Row;
18878                   struct Row1;
18879                   ˇstruct Row2;
18880
18881                   struct Row4;
18882                   struct Row5;
18883                   struct Row6;
18884
18885                   struct Row8;ˇ
18886                   struct Row9;
18887                   struct Row10;"#},
18888        base_text,
18889        &mut cx,
18890    );
18891    assert_hunk_revert(
18892        indoc! {r#"struct Row;
18893                   struct Row2«ˇ;
18894                   struct Row4;
18895                   struct» Row5;
18896                   «struct Row6;
18897
18898                   struct Row8;ˇ»
18899                   struct Row10;"#},
18900        vec![
18901            DiffHunkStatusKind::Deleted,
18902            DiffHunkStatusKind::Deleted,
18903            DiffHunkStatusKind::Deleted,
18904        ],
18905        indoc! {r#"struct Row;
18906                   struct Row1;
18907                   struct Row2«ˇ;
18908
18909                   struct Row4;
18910                   struct» Row5;
18911                   «struct Row6;
18912
18913                   struct Row8;ˇ»
18914                   struct Row9;
18915                   struct Row10;"#},
18916        base_text,
18917        &mut cx,
18918    );
18919}
18920
18921#[gpui::test]
18922async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18923    init_test(cx, |_| {});
18924
18925    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18926    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18927    let base_text_3 =
18928        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18929
18930    let text_1 = edit_first_char_of_every_line(base_text_1);
18931    let text_2 = edit_first_char_of_every_line(base_text_2);
18932    let text_3 = edit_first_char_of_every_line(base_text_3);
18933
18934    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18935    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18936    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18937
18938    let multibuffer = cx.new(|cx| {
18939        let mut multibuffer = MultiBuffer::new(ReadWrite);
18940        multibuffer.push_excerpts(
18941            buffer_1.clone(),
18942            [
18943                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18944                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18945                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18946            ],
18947            cx,
18948        );
18949        multibuffer.push_excerpts(
18950            buffer_2.clone(),
18951            [
18952                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18953                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18954                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18955            ],
18956            cx,
18957        );
18958        multibuffer.push_excerpts(
18959            buffer_3.clone(),
18960            [
18961                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18962                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18963                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18964            ],
18965            cx,
18966        );
18967        multibuffer
18968    });
18969
18970    let fs = FakeFs::new(cx.executor());
18971    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18972    let (editor, cx) = cx
18973        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18974    editor.update_in(cx, |editor, _window, cx| {
18975        for (buffer, diff_base) in [
18976            (buffer_1.clone(), base_text_1),
18977            (buffer_2.clone(), base_text_2),
18978            (buffer_3.clone(), base_text_3),
18979        ] {
18980            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18981            editor
18982                .buffer
18983                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18984        }
18985    });
18986    cx.executor().run_until_parked();
18987
18988    editor.update_in(cx, |editor, window, cx| {
18989        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}");
18990        editor.select_all(&SelectAll, window, cx);
18991        editor.git_restore(&Default::default(), window, cx);
18992    });
18993    cx.executor().run_until_parked();
18994
18995    // When all ranges are selected, all buffer hunks are reverted.
18996    editor.update(cx, |editor, cx| {
18997        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");
18998    });
18999    buffer_1.update(cx, |buffer, _| {
19000        assert_eq!(buffer.text(), base_text_1);
19001    });
19002    buffer_2.update(cx, |buffer, _| {
19003        assert_eq!(buffer.text(), base_text_2);
19004    });
19005    buffer_3.update(cx, |buffer, _| {
19006        assert_eq!(buffer.text(), base_text_3);
19007    });
19008
19009    editor.update_in(cx, |editor, window, cx| {
19010        editor.undo(&Default::default(), window, cx);
19011    });
19012
19013    editor.update_in(cx, |editor, window, cx| {
19014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19015            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19016        });
19017        editor.git_restore(&Default::default(), window, cx);
19018    });
19019
19020    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19021    // but not affect buffer_2 and its related excerpts.
19022    editor.update(cx, |editor, cx| {
19023        assert_eq!(
19024            editor.text(cx),
19025            "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}"
19026        );
19027    });
19028    buffer_1.update(cx, |buffer, _| {
19029        assert_eq!(buffer.text(), base_text_1);
19030    });
19031    buffer_2.update(cx, |buffer, _| {
19032        assert_eq!(
19033            buffer.text(),
19034            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19035        );
19036    });
19037    buffer_3.update(cx, |buffer, _| {
19038        assert_eq!(
19039            buffer.text(),
19040            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19041        );
19042    });
19043
19044    fn edit_first_char_of_every_line(text: &str) -> String {
19045        text.split('\n')
19046            .map(|line| format!("X{}", &line[1..]))
19047            .collect::<Vec<_>>()
19048            .join("\n")
19049    }
19050}
19051
19052#[gpui::test]
19053async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19054    init_test(cx, |_| {});
19055
19056    let cols = 4;
19057    let rows = 10;
19058    let sample_text_1 = sample_text(rows, cols, 'a');
19059    assert_eq!(
19060        sample_text_1,
19061        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19062    );
19063    let sample_text_2 = sample_text(rows, cols, 'l');
19064    assert_eq!(
19065        sample_text_2,
19066        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19067    );
19068    let sample_text_3 = sample_text(rows, cols, 'v');
19069    assert_eq!(
19070        sample_text_3,
19071        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19072    );
19073
19074    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19075    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19076    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19077
19078    let multi_buffer = cx.new(|cx| {
19079        let mut multibuffer = MultiBuffer::new(ReadWrite);
19080        multibuffer.push_excerpts(
19081            buffer_1.clone(),
19082            [
19083                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19084                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19085                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19086            ],
19087            cx,
19088        );
19089        multibuffer.push_excerpts(
19090            buffer_2.clone(),
19091            [
19092                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19093                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19094                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19095            ],
19096            cx,
19097        );
19098        multibuffer.push_excerpts(
19099            buffer_3.clone(),
19100            [
19101                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19102                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19103                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19104            ],
19105            cx,
19106        );
19107        multibuffer
19108    });
19109
19110    let fs = FakeFs::new(cx.executor());
19111    fs.insert_tree(
19112        "/a",
19113        json!({
19114            "main.rs": sample_text_1,
19115            "other.rs": sample_text_2,
19116            "lib.rs": sample_text_3,
19117        }),
19118    )
19119    .await;
19120    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19121    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19122    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19123    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19124        Editor::new(
19125            EditorMode::full(),
19126            multi_buffer,
19127            Some(project.clone()),
19128            window,
19129            cx,
19130        )
19131    });
19132    let multibuffer_item_id = workspace
19133        .update(cx, |workspace, window, cx| {
19134            assert!(
19135                workspace.active_item(cx).is_none(),
19136                "active item should be None before the first item is added"
19137            );
19138            workspace.add_item_to_active_pane(
19139                Box::new(multi_buffer_editor.clone()),
19140                None,
19141                true,
19142                window,
19143                cx,
19144            );
19145            let active_item = workspace
19146                .active_item(cx)
19147                .expect("should have an active item after adding the multi buffer");
19148            assert_eq!(
19149                active_item.buffer_kind(cx),
19150                ItemBufferKind::Multibuffer,
19151                "A multi buffer was expected to active after adding"
19152            );
19153            active_item.item_id()
19154        })
19155        .unwrap();
19156    cx.executor().run_until_parked();
19157
19158    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19159        editor.change_selections(
19160            SelectionEffects::scroll(Autoscroll::Next),
19161            window,
19162            cx,
19163            |s| s.select_ranges(Some(1..2)),
19164        );
19165        editor.open_excerpts(&OpenExcerpts, window, cx);
19166    });
19167    cx.executor().run_until_parked();
19168    let first_item_id = workspace
19169        .update(cx, |workspace, window, cx| {
19170            let active_item = workspace
19171                .active_item(cx)
19172                .expect("should have an active item after navigating into the 1st buffer");
19173            let first_item_id = active_item.item_id();
19174            assert_ne!(
19175                first_item_id, multibuffer_item_id,
19176                "Should navigate into the 1st buffer and activate it"
19177            );
19178            assert_eq!(
19179                active_item.buffer_kind(cx),
19180                ItemBufferKind::Singleton,
19181                "New active item should be a singleton buffer"
19182            );
19183            assert_eq!(
19184                active_item
19185                    .act_as::<Editor>(cx)
19186                    .expect("should have navigated into an editor for the 1st buffer")
19187                    .read(cx)
19188                    .text(cx),
19189                sample_text_1
19190            );
19191
19192            workspace
19193                .go_back(workspace.active_pane().downgrade(), window, cx)
19194                .detach_and_log_err(cx);
19195
19196            first_item_id
19197        })
19198        .unwrap();
19199    cx.executor().run_until_parked();
19200    workspace
19201        .update(cx, |workspace, _, cx| {
19202            let active_item = workspace
19203                .active_item(cx)
19204                .expect("should have an active item after navigating back");
19205            assert_eq!(
19206                active_item.item_id(),
19207                multibuffer_item_id,
19208                "Should navigate back to the multi buffer"
19209            );
19210            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19211        })
19212        .unwrap();
19213
19214    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19215        editor.change_selections(
19216            SelectionEffects::scroll(Autoscroll::Next),
19217            window,
19218            cx,
19219            |s| s.select_ranges(Some(39..40)),
19220        );
19221        editor.open_excerpts(&OpenExcerpts, window, cx);
19222    });
19223    cx.executor().run_until_parked();
19224    let second_item_id = workspace
19225        .update(cx, |workspace, window, cx| {
19226            let active_item = workspace
19227                .active_item(cx)
19228                .expect("should have an active item after navigating into the 2nd buffer");
19229            let second_item_id = active_item.item_id();
19230            assert_ne!(
19231                second_item_id, multibuffer_item_id,
19232                "Should navigate away from the multibuffer"
19233            );
19234            assert_ne!(
19235                second_item_id, first_item_id,
19236                "Should navigate into the 2nd buffer and activate it"
19237            );
19238            assert_eq!(
19239                active_item.buffer_kind(cx),
19240                ItemBufferKind::Singleton,
19241                "New active item should be a singleton buffer"
19242            );
19243            assert_eq!(
19244                active_item
19245                    .act_as::<Editor>(cx)
19246                    .expect("should have navigated into an editor")
19247                    .read(cx)
19248                    .text(cx),
19249                sample_text_2
19250            );
19251
19252            workspace
19253                .go_back(workspace.active_pane().downgrade(), window, cx)
19254                .detach_and_log_err(cx);
19255
19256            second_item_id
19257        })
19258        .unwrap();
19259    cx.executor().run_until_parked();
19260    workspace
19261        .update(cx, |workspace, _, cx| {
19262            let active_item = workspace
19263                .active_item(cx)
19264                .expect("should have an active item after navigating back from the 2nd buffer");
19265            assert_eq!(
19266                active_item.item_id(),
19267                multibuffer_item_id,
19268                "Should navigate back from the 2nd buffer to the multi buffer"
19269            );
19270            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19271        })
19272        .unwrap();
19273
19274    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19275        editor.change_selections(
19276            SelectionEffects::scroll(Autoscroll::Next),
19277            window,
19278            cx,
19279            |s| s.select_ranges(Some(70..70)),
19280        );
19281        editor.open_excerpts(&OpenExcerpts, window, cx);
19282    });
19283    cx.executor().run_until_parked();
19284    workspace
19285        .update(cx, |workspace, window, cx| {
19286            let active_item = workspace
19287                .active_item(cx)
19288                .expect("should have an active item after navigating into the 3rd buffer");
19289            let third_item_id = active_item.item_id();
19290            assert_ne!(
19291                third_item_id, multibuffer_item_id,
19292                "Should navigate into the 3rd buffer and activate it"
19293            );
19294            assert_ne!(third_item_id, first_item_id);
19295            assert_ne!(third_item_id, second_item_id);
19296            assert_eq!(
19297                active_item.buffer_kind(cx),
19298                ItemBufferKind::Singleton,
19299                "New active item should be a singleton buffer"
19300            );
19301            assert_eq!(
19302                active_item
19303                    .act_as::<Editor>(cx)
19304                    .expect("should have navigated into an editor")
19305                    .read(cx)
19306                    .text(cx),
19307                sample_text_3
19308            );
19309
19310            workspace
19311                .go_back(workspace.active_pane().downgrade(), window, cx)
19312                .detach_and_log_err(cx);
19313        })
19314        .unwrap();
19315    cx.executor().run_until_parked();
19316    workspace
19317        .update(cx, |workspace, _, cx| {
19318            let active_item = workspace
19319                .active_item(cx)
19320                .expect("should have an active item after navigating back from the 3rd buffer");
19321            assert_eq!(
19322                active_item.item_id(),
19323                multibuffer_item_id,
19324                "Should navigate back from the 3rd buffer to the multi buffer"
19325            );
19326            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19327        })
19328        .unwrap();
19329}
19330
19331#[gpui::test]
19332async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19333    init_test(cx, |_| {});
19334
19335    let mut cx = EditorTestContext::new(cx).await;
19336
19337    let diff_base = r#"
19338        use some::mod;
19339
19340        const A: u32 = 42;
19341
19342        fn main() {
19343            println!("hello");
19344
19345            println!("world");
19346        }
19347        "#
19348    .unindent();
19349
19350    cx.set_state(
19351        &r#"
19352        use some::modified;
19353
19354        ˇ
19355        fn main() {
19356            println!("hello there");
19357
19358            println!("around the");
19359            println!("world");
19360        }
19361        "#
19362        .unindent(),
19363    );
19364
19365    cx.set_head_text(&diff_base);
19366    executor.run_until_parked();
19367
19368    cx.update_editor(|editor, window, cx| {
19369        editor.go_to_next_hunk(&GoToHunk, window, cx);
19370        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19371    });
19372    executor.run_until_parked();
19373    cx.assert_state_with_diff(
19374        r#"
19375          use some::modified;
19376
19377
19378          fn main() {
19379        -     println!("hello");
19380        + ˇ    println!("hello there");
19381
19382              println!("around the");
19383              println!("world");
19384          }
19385        "#
19386        .unindent(),
19387    );
19388
19389    cx.update_editor(|editor, window, cx| {
19390        for _ in 0..2 {
19391            editor.go_to_next_hunk(&GoToHunk, window, cx);
19392            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19393        }
19394    });
19395    executor.run_until_parked();
19396    cx.assert_state_with_diff(
19397        r#"
19398        - use some::mod;
19399        + ˇuse some::modified;
19400
19401
19402          fn main() {
19403        -     println!("hello");
19404        +     println!("hello there");
19405
19406        +     println!("around the");
19407              println!("world");
19408          }
19409        "#
19410        .unindent(),
19411    );
19412
19413    cx.update_editor(|editor, window, cx| {
19414        editor.go_to_next_hunk(&GoToHunk, window, cx);
19415        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19416    });
19417    executor.run_until_parked();
19418    cx.assert_state_with_diff(
19419        r#"
19420        - use some::mod;
19421        + use some::modified;
19422
19423        - const A: u32 = 42;
19424          ˇ
19425          fn main() {
19426        -     println!("hello");
19427        +     println!("hello there");
19428
19429        +     println!("around the");
19430              println!("world");
19431          }
19432        "#
19433        .unindent(),
19434    );
19435
19436    cx.update_editor(|editor, window, cx| {
19437        editor.cancel(&Cancel, window, cx);
19438    });
19439
19440    cx.assert_state_with_diff(
19441        r#"
19442          use some::modified;
19443
19444          ˇ
19445          fn main() {
19446              println!("hello there");
19447
19448              println!("around the");
19449              println!("world");
19450          }
19451        "#
19452        .unindent(),
19453    );
19454}
19455
19456#[gpui::test]
19457async fn test_diff_base_change_with_expanded_diff_hunks(
19458    executor: BackgroundExecutor,
19459    cx: &mut TestAppContext,
19460) {
19461    init_test(cx, |_| {});
19462
19463    let mut cx = EditorTestContext::new(cx).await;
19464
19465    let diff_base = r#"
19466        use some::mod1;
19467        use some::mod2;
19468
19469        const A: u32 = 42;
19470        const B: u32 = 42;
19471        const C: u32 = 42;
19472
19473        fn main() {
19474            println!("hello");
19475
19476            println!("world");
19477        }
19478        "#
19479    .unindent();
19480
19481    cx.set_state(
19482        &r#"
19483        use some::mod2;
19484
19485        const A: u32 = 42;
19486        const C: u32 = 42;
19487
19488        fn main(ˇ) {
19489            //println!("hello");
19490
19491            println!("world");
19492            //
19493            //
19494        }
19495        "#
19496        .unindent(),
19497    );
19498
19499    cx.set_head_text(&diff_base);
19500    executor.run_until_parked();
19501
19502    cx.update_editor(|editor, window, cx| {
19503        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19504    });
19505    executor.run_until_parked();
19506    cx.assert_state_with_diff(
19507        r#"
19508        - use some::mod1;
19509          use some::mod2;
19510
19511          const A: u32 = 42;
19512        - const B: u32 = 42;
19513          const C: u32 = 42;
19514
19515          fn main(ˇ) {
19516        -     println!("hello");
19517        +     //println!("hello");
19518
19519              println!("world");
19520        +     //
19521        +     //
19522          }
19523        "#
19524        .unindent(),
19525    );
19526
19527    cx.set_head_text("new diff base!");
19528    executor.run_until_parked();
19529    cx.assert_state_with_diff(
19530        r#"
19531        - new diff base!
19532        + use some::mod2;
19533        +
19534        + const A: u32 = 42;
19535        + const C: u32 = 42;
19536        +
19537        + fn main(ˇ) {
19538        +     //println!("hello");
19539        +
19540        +     println!("world");
19541        +     //
19542        +     //
19543        + }
19544        "#
19545        .unindent(),
19546    );
19547}
19548
19549#[gpui::test]
19550async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19551    init_test(cx, |_| {});
19552
19553    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19554    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19555    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19556    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19557    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19558    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19559
19560    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19561    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19562    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19563
19564    let multi_buffer = cx.new(|cx| {
19565        let mut multibuffer = MultiBuffer::new(ReadWrite);
19566        multibuffer.push_excerpts(
19567            buffer_1.clone(),
19568            [
19569                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19570                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19571                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19572            ],
19573            cx,
19574        );
19575        multibuffer.push_excerpts(
19576            buffer_2.clone(),
19577            [
19578                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19579                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19580                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19581            ],
19582            cx,
19583        );
19584        multibuffer.push_excerpts(
19585            buffer_3.clone(),
19586            [
19587                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19588                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19589                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19590            ],
19591            cx,
19592        );
19593        multibuffer
19594    });
19595
19596    let editor =
19597        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19598    editor
19599        .update(cx, |editor, _window, cx| {
19600            for (buffer, diff_base) in [
19601                (buffer_1.clone(), file_1_old),
19602                (buffer_2.clone(), file_2_old),
19603                (buffer_3.clone(), file_3_old),
19604            ] {
19605                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19606                editor
19607                    .buffer
19608                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19609            }
19610        })
19611        .unwrap();
19612
19613    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19614    cx.run_until_parked();
19615
19616    cx.assert_editor_state(
19617        &"
19618            ˇaaa
19619            ccc
19620            ddd
19621
19622            ggg
19623            hhh
19624
19625
19626            lll
19627            mmm
19628            NNN
19629
19630            qqq
19631            rrr
19632
19633            uuu
19634            111
19635            222
19636            333
19637
19638            666
19639            777
19640
19641            000
19642            !!!"
19643        .unindent(),
19644    );
19645
19646    cx.update_editor(|editor, window, cx| {
19647        editor.select_all(&SelectAll, window, cx);
19648        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19649    });
19650    cx.executor().run_until_parked();
19651
19652    cx.assert_state_with_diff(
19653        "
19654            «aaa
19655          - bbb
19656            ccc
19657            ddd
19658
19659            ggg
19660            hhh
19661
19662
19663            lll
19664            mmm
19665          - nnn
19666          + NNN
19667
19668            qqq
19669            rrr
19670
19671            uuu
19672            111
19673            222
19674            333
19675
19676          + 666
19677            777
19678
19679            000
19680            !!!ˇ»"
19681            .unindent(),
19682    );
19683}
19684
19685#[gpui::test]
19686async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19687    init_test(cx, |_| {});
19688
19689    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19690    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19691
19692    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19693    let multi_buffer = cx.new(|cx| {
19694        let mut multibuffer = MultiBuffer::new(ReadWrite);
19695        multibuffer.push_excerpts(
19696            buffer.clone(),
19697            [
19698                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19699                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19700                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19701            ],
19702            cx,
19703        );
19704        multibuffer
19705    });
19706
19707    let editor =
19708        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19709    editor
19710        .update(cx, |editor, _window, cx| {
19711            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19712            editor
19713                .buffer
19714                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19715        })
19716        .unwrap();
19717
19718    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19719    cx.run_until_parked();
19720
19721    cx.update_editor(|editor, window, cx| {
19722        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19723    });
19724    cx.executor().run_until_parked();
19725
19726    // When the start of a hunk coincides with the start of its excerpt,
19727    // the hunk is expanded. When the start of a hunk is earlier than
19728    // the start of its excerpt, the hunk is not expanded.
19729    cx.assert_state_with_diff(
19730        "
19731            ˇaaa
19732          - bbb
19733          + BBB
19734
19735          - ddd
19736          - eee
19737          + DDD
19738          + EEE
19739            fff
19740
19741            iii
19742        "
19743        .unindent(),
19744    );
19745}
19746
19747#[gpui::test]
19748async fn test_edits_around_expanded_insertion_hunks(
19749    executor: BackgroundExecutor,
19750    cx: &mut TestAppContext,
19751) {
19752    init_test(cx, |_| {});
19753
19754    let mut cx = EditorTestContext::new(cx).await;
19755
19756    let diff_base = r#"
19757        use some::mod1;
19758        use some::mod2;
19759
19760        const A: u32 = 42;
19761
19762        fn main() {
19763            println!("hello");
19764
19765            println!("world");
19766        }
19767        "#
19768    .unindent();
19769    executor.run_until_parked();
19770    cx.set_state(
19771        &r#"
19772        use some::mod1;
19773        use some::mod2;
19774
19775        const A: u32 = 42;
19776        const B: u32 = 42;
19777        const C: u32 = 42;
19778        ˇ
19779
19780        fn main() {
19781            println!("hello");
19782
19783            println!("world");
19784        }
19785        "#
19786        .unindent(),
19787    );
19788
19789    cx.set_head_text(&diff_base);
19790    executor.run_until_parked();
19791
19792    cx.update_editor(|editor, window, cx| {
19793        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19794    });
19795    executor.run_until_parked();
19796
19797    cx.assert_state_with_diff(
19798        r#"
19799        use some::mod1;
19800        use some::mod2;
19801
19802        const A: u32 = 42;
19803      + const B: u32 = 42;
19804      + const C: u32 = 42;
19805      + ˇ
19806
19807        fn main() {
19808            println!("hello");
19809
19810            println!("world");
19811        }
19812      "#
19813        .unindent(),
19814    );
19815
19816    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19817    executor.run_until_parked();
19818
19819    cx.assert_state_with_diff(
19820        r#"
19821        use some::mod1;
19822        use some::mod2;
19823
19824        const A: u32 = 42;
19825      + const B: u32 = 42;
19826      + const C: u32 = 42;
19827      + const D: u32 = 42;
19828      + ˇ
19829
19830        fn main() {
19831            println!("hello");
19832
19833            println!("world");
19834        }
19835      "#
19836        .unindent(),
19837    );
19838
19839    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19840    executor.run_until_parked();
19841
19842    cx.assert_state_with_diff(
19843        r#"
19844        use some::mod1;
19845        use some::mod2;
19846
19847        const A: u32 = 42;
19848      + const B: u32 = 42;
19849      + const C: u32 = 42;
19850      + const D: u32 = 42;
19851      + const E: u32 = 42;
19852      + ˇ
19853
19854        fn main() {
19855            println!("hello");
19856
19857            println!("world");
19858        }
19859      "#
19860        .unindent(),
19861    );
19862
19863    cx.update_editor(|editor, window, cx| {
19864        editor.delete_line(&DeleteLine, window, cx);
19865    });
19866    executor.run_until_parked();
19867
19868    cx.assert_state_with_diff(
19869        r#"
19870        use some::mod1;
19871        use some::mod2;
19872
19873        const A: u32 = 42;
19874      + const B: u32 = 42;
19875      + const C: u32 = 42;
19876      + const D: u32 = 42;
19877      + const E: u32 = 42;
19878        ˇ
19879        fn main() {
19880            println!("hello");
19881
19882            println!("world");
19883        }
19884      "#
19885        .unindent(),
19886    );
19887
19888    cx.update_editor(|editor, window, cx| {
19889        editor.move_up(&MoveUp, window, cx);
19890        editor.delete_line(&DeleteLine, window, cx);
19891        editor.move_up(&MoveUp, window, cx);
19892        editor.delete_line(&DeleteLine, window, cx);
19893        editor.move_up(&MoveUp, window, cx);
19894        editor.delete_line(&DeleteLine, window, cx);
19895    });
19896    executor.run_until_parked();
19897    cx.assert_state_with_diff(
19898        r#"
19899        use some::mod1;
19900        use some::mod2;
19901
19902        const A: u32 = 42;
19903      + const B: u32 = 42;
19904        ˇ
19905        fn main() {
19906            println!("hello");
19907
19908            println!("world");
19909        }
19910      "#
19911        .unindent(),
19912    );
19913
19914    cx.update_editor(|editor, window, cx| {
19915        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19916        editor.delete_line(&DeleteLine, window, cx);
19917    });
19918    executor.run_until_parked();
19919    cx.assert_state_with_diff(
19920        r#"
19921        ˇ
19922        fn main() {
19923            println!("hello");
19924
19925            println!("world");
19926        }
19927      "#
19928        .unindent(),
19929    );
19930}
19931
19932#[gpui::test]
19933async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19934    init_test(cx, |_| {});
19935
19936    let mut cx = EditorTestContext::new(cx).await;
19937    cx.set_head_text(indoc! { "
19938        one
19939        two
19940        three
19941        four
19942        five
19943        "
19944    });
19945    cx.set_state(indoc! { "
19946        one
19947        ˇthree
19948        five
19949    "});
19950    cx.run_until_parked();
19951    cx.update_editor(|editor, window, cx| {
19952        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19953    });
19954    cx.assert_state_with_diff(
19955        indoc! { "
19956        one
19957      - two
19958        ˇthree
19959      - four
19960        five
19961    "}
19962        .to_string(),
19963    );
19964    cx.update_editor(|editor, window, cx| {
19965        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19966    });
19967
19968    cx.assert_state_with_diff(
19969        indoc! { "
19970        one
19971        ˇthree
19972        five
19973    "}
19974        .to_string(),
19975    );
19976
19977    cx.set_state(indoc! { "
19978        one
19979        ˇTWO
19980        three
19981        four
19982        five
19983    "});
19984    cx.run_until_parked();
19985    cx.update_editor(|editor, window, cx| {
19986        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19987    });
19988
19989    cx.assert_state_with_diff(
19990        indoc! { "
19991            one
19992          - two
19993          + ˇTWO
19994            three
19995            four
19996            five
19997        "}
19998        .to_string(),
19999    );
20000    cx.update_editor(|editor, window, cx| {
20001        editor.move_up(&Default::default(), window, cx);
20002        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20003    });
20004    cx.assert_state_with_diff(
20005        indoc! { "
20006            one
20007            ˇTWO
20008            three
20009            four
20010            five
20011        "}
20012        .to_string(),
20013    );
20014}
20015
20016#[gpui::test]
20017async fn test_edits_around_expanded_deletion_hunks(
20018    executor: BackgroundExecutor,
20019    cx: &mut TestAppContext,
20020) {
20021    init_test(cx, |_| {});
20022
20023    let mut cx = EditorTestContext::new(cx).await;
20024
20025    let diff_base = r#"
20026        use some::mod1;
20027        use some::mod2;
20028
20029        const A: u32 = 42;
20030        const B: u32 = 42;
20031        const C: u32 = 42;
20032
20033
20034        fn main() {
20035            println!("hello");
20036
20037            println!("world");
20038        }
20039    "#
20040    .unindent();
20041    executor.run_until_parked();
20042    cx.set_state(
20043        &r#"
20044        use some::mod1;
20045        use some::mod2;
20046
20047        ˇconst B: u32 = 42;
20048        const C: u32 = 42;
20049
20050
20051        fn main() {
20052            println!("hello");
20053
20054            println!("world");
20055        }
20056        "#
20057        .unindent(),
20058    );
20059
20060    cx.set_head_text(&diff_base);
20061    executor.run_until_parked();
20062
20063    cx.update_editor(|editor, window, cx| {
20064        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20065    });
20066    executor.run_until_parked();
20067
20068    cx.assert_state_with_diff(
20069        r#"
20070        use some::mod1;
20071        use some::mod2;
20072
20073      - const A: u32 = 42;
20074        ˇconst B: u32 = 42;
20075        const C: u32 = 42;
20076
20077
20078        fn main() {
20079            println!("hello");
20080
20081            println!("world");
20082        }
20083      "#
20084        .unindent(),
20085    );
20086
20087    cx.update_editor(|editor, window, cx| {
20088        editor.delete_line(&DeleteLine, window, cx);
20089    });
20090    executor.run_until_parked();
20091    cx.assert_state_with_diff(
20092        r#"
20093        use some::mod1;
20094        use some::mod2;
20095
20096      - const A: u32 = 42;
20097      - const B: u32 = 42;
20098        ˇconst C: u32 = 42;
20099
20100
20101        fn main() {
20102            println!("hello");
20103
20104            println!("world");
20105        }
20106      "#
20107        .unindent(),
20108    );
20109
20110    cx.update_editor(|editor, window, cx| {
20111        editor.delete_line(&DeleteLine, window, cx);
20112    });
20113    executor.run_until_parked();
20114    cx.assert_state_with_diff(
20115        r#"
20116        use some::mod1;
20117        use some::mod2;
20118
20119      - const A: u32 = 42;
20120      - const B: u32 = 42;
20121      - const C: u32 = 42;
20122        ˇ
20123
20124        fn main() {
20125            println!("hello");
20126
20127            println!("world");
20128        }
20129      "#
20130        .unindent(),
20131    );
20132
20133    cx.update_editor(|editor, window, cx| {
20134        editor.handle_input("replacement", window, cx);
20135    });
20136    executor.run_until_parked();
20137    cx.assert_state_with_diff(
20138        r#"
20139        use some::mod1;
20140        use some::mod2;
20141
20142      - const A: u32 = 42;
20143      - const B: u32 = 42;
20144      - const C: u32 = 42;
20145      -
20146      + replacementˇ
20147
20148        fn main() {
20149            println!("hello");
20150
20151            println!("world");
20152        }
20153      "#
20154        .unindent(),
20155    );
20156}
20157
20158#[gpui::test]
20159async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20160    init_test(cx, |_| {});
20161
20162    let mut cx = EditorTestContext::new(cx).await;
20163
20164    let base_text = r#"
20165        one
20166        two
20167        three
20168        four
20169        five
20170    "#
20171    .unindent();
20172    executor.run_until_parked();
20173    cx.set_state(
20174        &r#"
20175        one
20176        two
20177        fˇour
20178        five
20179        "#
20180        .unindent(),
20181    );
20182
20183    cx.set_head_text(&base_text);
20184    executor.run_until_parked();
20185
20186    cx.update_editor(|editor, window, cx| {
20187        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20188    });
20189    executor.run_until_parked();
20190
20191    cx.assert_state_with_diff(
20192        r#"
20193          one
20194          two
20195        - three
20196          fˇour
20197          five
20198        "#
20199        .unindent(),
20200    );
20201
20202    cx.update_editor(|editor, window, cx| {
20203        editor.backspace(&Backspace, window, cx);
20204        editor.backspace(&Backspace, window, cx);
20205    });
20206    executor.run_until_parked();
20207    cx.assert_state_with_diff(
20208        r#"
20209          one
20210          two
20211        - threeˇ
20212        - four
20213        + our
20214          five
20215        "#
20216        .unindent(),
20217    );
20218}
20219
20220#[gpui::test]
20221async fn test_edit_after_expanded_modification_hunk(
20222    executor: BackgroundExecutor,
20223    cx: &mut TestAppContext,
20224) {
20225    init_test(cx, |_| {});
20226
20227    let mut cx = EditorTestContext::new(cx).await;
20228
20229    let diff_base = r#"
20230        use some::mod1;
20231        use some::mod2;
20232
20233        const A: u32 = 42;
20234        const B: u32 = 42;
20235        const C: u32 = 42;
20236        const D: u32 = 42;
20237
20238
20239        fn main() {
20240            println!("hello");
20241
20242            println!("world");
20243        }"#
20244    .unindent();
20245
20246    cx.set_state(
20247        &r#"
20248        use some::mod1;
20249        use some::mod2;
20250
20251        const A: u32 = 42;
20252        const B: u32 = 42;
20253        const C: u32 = 43ˇ
20254        const D: u32 = 42;
20255
20256
20257        fn main() {
20258            println!("hello");
20259
20260            println!("world");
20261        }"#
20262        .unindent(),
20263    );
20264
20265    cx.set_head_text(&diff_base);
20266    executor.run_until_parked();
20267    cx.update_editor(|editor, window, cx| {
20268        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20269    });
20270    executor.run_until_parked();
20271
20272    cx.assert_state_with_diff(
20273        r#"
20274        use some::mod1;
20275        use some::mod2;
20276
20277        const A: u32 = 42;
20278        const B: u32 = 42;
20279      - const C: u32 = 42;
20280      + const C: u32 = 43ˇ
20281        const D: u32 = 42;
20282
20283
20284        fn main() {
20285            println!("hello");
20286
20287            println!("world");
20288        }"#
20289        .unindent(),
20290    );
20291
20292    cx.update_editor(|editor, window, cx| {
20293        editor.handle_input("\nnew_line\n", window, cx);
20294    });
20295    executor.run_until_parked();
20296
20297    cx.assert_state_with_diff(
20298        r#"
20299        use some::mod1;
20300        use some::mod2;
20301
20302        const A: u32 = 42;
20303        const B: u32 = 42;
20304      - const C: u32 = 42;
20305      + const C: u32 = 43
20306      + new_line
20307      + ˇ
20308        const D: u32 = 42;
20309
20310
20311        fn main() {
20312            println!("hello");
20313
20314            println!("world");
20315        }"#
20316        .unindent(),
20317    );
20318}
20319
20320#[gpui::test]
20321async fn test_stage_and_unstage_added_file_hunk(
20322    executor: BackgroundExecutor,
20323    cx: &mut TestAppContext,
20324) {
20325    init_test(cx, |_| {});
20326
20327    let mut cx = EditorTestContext::new(cx).await;
20328    cx.update_editor(|editor, _, cx| {
20329        editor.set_expand_all_diff_hunks(cx);
20330    });
20331
20332    let working_copy = r#"
20333            ˇfn main() {
20334                println!("hello, world!");
20335            }
20336        "#
20337    .unindent();
20338
20339    cx.set_state(&working_copy);
20340    executor.run_until_parked();
20341
20342    cx.assert_state_with_diff(
20343        r#"
20344            + ˇfn main() {
20345            +     println!("hello, world!");
20346            + }
20347        "#
20348        .unindent(),
20349    );
20350    cx.assert_index_text(None);
20351
20352    cx.update_editor(|editor, window, cx| {
20353        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20354    });
20355    executor.run_until_parked();
20356    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20357    cx.assert_state_with_diff(
20358        r#"
20359            + ˇfn main() {
20360            +     println!("hello, world!");
20361            + }
20362        "#
20363        .unindent(),
20364    );
20365
20366    cx.update_editor(|editor, window, cx| {
20367        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20368    });
20369    executor.run_until_parked();
20370    cx.assert_index_text(None);
20371}
20372
20373async fn setup_indent_guides_editor(
20374    text: &str,
20375    cx: &mut TestAppContext,
20376) -> (BufferId, EditorTestContext) {
20377    init_test(cx, |_| {});
20378
20379    let mut cx = EditorTestContext::new(cx).await;
20380
20381    let buffer_id = cx.update_editor(|editor, window, cx| {
20382        editor.set_text(text, window, cx);
20383        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20384
20385        buffer_ids[0]
20386    });
20387
20388    (buffer_id, cx)
20389}
20390
20391fn assert_indent_guides(
20392    range: Range<u32>,
20393    expected: Vec<IndentGuide>,
20394    active_indices: Option<Vec<usize>>,
20395    cx: &mut EditorTestContext,
20396) {
20397    let indent_guides = cx.update_editor(|editor, window, cx| {
20398        let snapshot = editor.snapshot(window, cx).display_snapshot;
20399        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20400            editor,
20401            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20402            true,
20403            &snapshot,
20404            cx,
20405        );
20406
20407        indent_guides.sort_by(|a, b| {
20408            a.depth.cmp(&b.depth).then(
20409                a.start_row
20410                    .cmp(&b.start_row)
20411                    .then(a.end_row.cmp(&b.end_row)),
20412            )
20413        });
20414        indent_guides
20415    });
20416
20417    if let Some(expected) = active_indices {
20418        let active_indices = cx.update_editor(|editor, window, cx| {
20419            let snapshot = editor.snapshot(window, cx).display_snapshot;
20420            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20421        });
20422
20423        assert_eq!(
20424            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20425            expected,
20426            "Active indent guide indices do not match"
20427        );
20428    }
20429
20430    assert_eq!(indent_guides, expected, "Indent guides do not match");
20431}
20432
20433fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20434    IndentGuide {
20435        buffer_id,
20436        start_row: MultiBufferRow(start_row),
20437        end_row: MultiBufferRow(end_row),
20438        depth,
20439        tab_size: 4,
20440        settings: IndentGuideSettings {
20441            enabled: true,
20442            line_width: 1,
20443            active_line_width: 1,
20444            coloring: IndentGuideColoring::default(),
20445            background_coloring: IndentGuideBackgroundColoring::default(),
20446        },
20447    }
20448}
20449
20450#[gpui::test]
20451async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20452    let (buffer_id, mut cx) = setup_indent_guides_editor(
20453        &"
20454        fn main() {
20455            let a = 1;
20456        }"
20457        .unindent(),
20458        cx,
20459    )
20460    .await;
20461
20462    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20463}
20464
20465#[gpui::test]
20466async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20467    let (buffer_id, mut cx) = setup_indent_guides_editor(
20468        &"
20469        fn main() {
20470            let a = 1;
20471            let b = 2;
20472        }"
20473        .unindent(),
20474        cx,
20475    )
20476    .await;
20477
20478    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20479}
20480
20481#[gpui::test]
20482async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20483    let (buffer_id, mut cx) = setup_indent_guides_editor(
20484        &"
20485        fn main() {
20486            let a = 1;
20487            if a == 3 {
20488                let b = 2;
20489            } else {
20490                let c = 3;
20491            }
20492        }"
20493        .unindent(),
20494        cx,
20495    )
20496    .await;
20497
20498    assert_indent_guides(
20499        0..8,
20500        vec![
20501            indent_guide(buffer_id, 1, 6, 0),
20502            indent_guide(buffer_id, 3, 3, 1),
20503            indent_guide(buffer_id, 5, 5, 1),
20504        ],
20505        None,
20506        &mut cx,
20507    );
20508}
20509
20510#[gpui::test]
20511async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20512    let (buffer_id, mut cx) = setup_indent_guides_editor(
20513        &"
20514        fn main() {
20515            let a = 1;
20516                let b = 2;
20517            let c = 3;
20518        }"
20519        .unindent(),
20520        cx,
20521    )
20522    .await;
20523
20524    assert_indent_guides(
20525        0..5,
20526        vec![
20527            indent_guide(buffer_id, 1, 3, 0),
20528            indent_guide(buffer_id, 2, 2, 1),
20529        ],
20530        None,
20531        &mut cx,
20532    );
20533}
20534
20535#[gpui::test]
20536async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20537    let (buffer_id, mut cx) = setup_indent_guides_editor(
20538        &"
20539        fn main() {
20540            let a = 1;
20541
20542            let c = 3;
20543        }"
20544        .unindent(),
20545        cx,
20546    )
20547    .await;
20548
20549    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20550}
20551
20552#[gpui::test]
20553async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20554    let (buffer_id, mut cx) = setup_indent_guides_editor(
20555        &"
20556        fn main() {
20557            let a = 1;
20558
20559            let c = 3;
20560
20561            if a == 3 {
20562                let b = 2;
20563            } else {
20564                let c = 3;
20565            }
20566        }"
20567        .unindent(),
20568        cx,
20569    )
20570    .await;
20571
20572    assert_indent_guides(
20573        0..11,
20574        vec![
20575            indent_guide(buffer_id, 1, 9, 0),
20576            indent_guide(buffer_id, 6, 6, 1),
20577            indent_guide(buffer_id, 8, 8, 1),
20578        ],
20579        None,
20580        &mut cx,
20581    );
20582}
20583
20584#[gpui::test]
20585async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20586    let (buffer_id, mut cx) = setup_indent_guides_editor(
20587        &"
20588        fn main() {
20589            let a = 1;
20590
20591            let c = 3;
20592
20593            if a == 3 {
20594                let b = 2;
20595            } else {
20596                let c = 3;
20597            }
20598        }"
20599        .unindent(),
20600        cx,
20601    )
20602    .await;
20603
20604    assert_indent_guides(
20605        1..11,
20606        vec![
20607            indent_guide(buffer_id, 1, 9, 0),
20608            indent_guide(buffer_id, 6, 6, 1),
20609            indent_guide(buffer_id, 8, 8, 1),
20610        ],
20611        None,
20612        &mut cx,
20613    );
20614}
20615
20616#[gpui::test]
20617async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20618    let (buffer_id, mut cx) = setup_indent_guides_editor(
20619        &"
20620        fn main() {
20621            let a = 1;
20622
20623            let c = 3;
20624
20625            if a == 3 {
20626                let b = 2;
20627            } else {
20628                let c = 3;
20629            }
20630        }"
20631        .unindent(),
20632        cx,
20633    )
20634    .await;
20635
20636    assert_indent_guides(
20637        1..10,
20638        vec![
20639            indent_guide(buffer_id, 1, 9, 0),
20640            indent_guide(buffer_id, 6, 6, 1),
20641            indent_guide(buffer_id, 8, 8, 1),
20642        ],
20643        None,
20644        &mut cx,
20645    );
20646}
20647
20648#[gpui::test]
20649async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20650    let (buffer_id, mut cx) = setup_indent_guides_editor(
20651        &"
20652        fn main() {
20653            if a {
20654                b(
20655                    c,
20656                    d,
20657                )
20658            } else {
20659                e(
20660                    f
20661                )
20662            }
20663        }"
20664        .unindent(),
20665        cx,
20666    )
20667    .await;
20668
20669    assert_indent_guides(
20670        0..11,
20671        vec![
20672            indent_guide(buffer_id, 1, 10, 0),
20673            indent_guide(buffer_id, 2, 5, 1),
20674            indent_guide(buffer_id, 7, 9, 1),
20675            indent_guide(buffer_id, 3, 4, 2),
20676            indent_guide(buffer_id, 8, 8, 2),
20677        ],
20678        None,
20679        &mut cx,
20680    );
20681
20682    cx.update_editor(|editor, window, cx| {
20683        editor.fold_at(MultiBufferRow(2), window, cx);
20684        assert_eq!(
20685            editor.display_text(cx),
20686            "
20687            fn main() {
20688                if a {
20689                    b(⋯
20690                    )
20691                } else {
20692                    e(
20693                        f
20694                    )
20695                }
20696            }"
20697            .unindent()
20698        );
20699    });
20700
20701    assert_indent_guides(
20702        0..11,
20703        vec![
20704            indent_guide(buffer_id, 1, 10, 0),
20705            indent_guide(buffer_id, 2, 5, 1),
20706            indent_guide(buffer_id, 7, 9, 1),
20707            indent_guide(buffer_id, 8, 8, 2),
20708        ],
20709        None,
20710        &mut cx,
20711    );
20712}
20713
20714#[gpui::test]
20715async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20716    let (buffer_id, mut cx) = setup_indent_guides_editor(
20717        &"
20718        block1
20719            block2
20720                block3
20721                    block4
20722            block2
20723        block1
20724        block1"
20725            .unindent(),
20726        cx,
20727    )
20728    .await;
20729
20730    assert_indent_guides(
20731        1..10,
20732        vec![
20733            indent_guide(buffer_id, 1, 4, 0),
20734            indent_guide(buffer_id, 2, 3, 1),
20735            indent_guide(buffer_id, 3, 3, 2),
20736        ],
20737        None,
20738        &mut cx,
20739    );
20740}
20741
20742#[gpui::test]
20743async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20744    let (buffer_id, mut cx) = setup_indent_guides_editor(
20745        &"
20746        block1
20747            block2
20748                block3
20749
20750        block1
20751        block1"
20752            .unindent(),
20753        cx,
20754    )
20755    .await;
20756
20757    assert_indent_guides(
20758        0..6,
20759        vec![
20760            indent_guide(buffer_id, 1, 2, 0),
20761            indent_guide(buffer_id, 2, 2, 1),
20762        ],
20763        None,
20764        &mut cx,
20765    );
20766}
20767
20768#[gpui::test]
20769async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20770    let (buffer_id, mut cx) = setup_indent_guides_editor(
20771        &"
20772        function component() {
20773        \treturn (
20774        \t\t\t
20775        \t\t<div>
20776        \t\t\t<abc></abc>
20777        \t\t</div>
20778        \t)
20779        }"
20780        .unindent(),
20781        cx,
20782    )
20783    .await;
20784
20785    assert_indent_guides(
20786        0..8,
20787        vec![
20788            indent_guide(buffer_id, 1, 6, 0),
20789            indent_guide(buffer_id, 2, 5, 1),
20790            indent_guide(buffer_id, 4, 4, 2),
20791        ],
20792        None,
20793        &mut cx,
20794    );
20795}
20796
20797#[gpui::test]
20798async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20799    let (buffer_id, mut cx) = setup_indent_guides_editor(
20800        &"
20801        function component() {
20802        \treturn (
20803        \t
20804        \t\t<div>
20805        \t\t\t<abc></abc>
20806        \t\t</div>
20807        \t)
20808        }"
20809        .unindent(),
20810        cx,
20811    )
20812    .await;
20813
20814    assert_indent_guides(
20815        0..8,
20816        vec![
20817            indent_guide(buffer_id, 1, 6, 0),
20818            indent_guide(buffer_id, 2, 5, 1),
20819            indent_guide(buffer_id, 4, 4, 2),
20820        ],
20821        None,
20822        &mut cx,
20823    );
20824}
20825
20826#[gpui::test]
20827async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20828    let (buffer_id, mut cx) = setup_indent_guides_editor(
20829        &"
20830        block1
20831
20832
20833
20834            block2
20835        "
20836        .unindent(),
20837        cx,
20838    )
20839    .await;
20840
20841    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20842}
20843
20844#[gpui::test]
20845async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20846    let (buffer_id, mut cx) = setup_indent_guides_editor(
20847        &"
20848        def a:
20849        \tb = 3
20850        \tif True:
20851        \t\tc = 4
20852        \t\td = 5
20853        \tprint(b)
20854        "
20855        .unindent(),
20856        cx,
20857    )
20858    .await;
20859
20860    assert_indent_guides(
20861        0..6,
20862        vec![
20863            indent_guide(buffer_id, 1, 5, 0),
20864            indent_guide(buffer_id, 3, 4, 1),
20865        ],
20866        None,
20867        &mut cx,
20868    );
20869}
20870
20871#[gpui::test]
20872async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20873    let (buffer_id, mut cx) = setup_indent_guides_editor(
20874        &"
20875    fn main() {
20876        let a = 1;
20877    }"
20878        .unindent(),
20879        cx,
20880    )
20881    .await;
20882
20883    cx.update_editor(|editor, window, cx| {
20884        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20885            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20886        });
20887    });
20888
20889    assert_indent_guides(
20890        0..3,
20891        vec![indent_guide(buffer_id, 1, 1, 0)],
20892        Some(vec![0]),
20893        &mut cx,
20894    );
20895}
20896
20897#[gpui::test]
20898async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20899    let (buffer_id, mut cx) = setup_indent_guides_editor(
20900        &"
20901    fn main() {
20902        if 1 == 2 {
20903            let a = 1;
20904        }
20905    }"
20906        .unindent(),
20907        cx,
20908    )
20909    .await;
20910
20911    cx.update_editor(|editor, window, cx| {
20912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20913            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20914        });
20915    });
20916
20917    assert_indent_guides(
20918        0..4,
20919        vec![
20920            indent_guide(buffer_id, 1, 3, 0),
20921            indent_guide(buffer_id, 2, 2, 1),
20922        ],
20923        Some(vec![1]),
20924        &mut cx,
20925    );
20926
20927    cx.update_editor(|editor, window, cx| {
20928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20929            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20930        });
20931    });
20932
20933    assert_indent_guides(
20934        0..4,
20935        vec![
20936            indent_guide(buffer_id, 1, 3, 0),
20937            indent_guide(buffer_id, 2, 2, 1),
20938        ],
20939        Some(vec![1]),
20940        &mut cx,
20941    );
20942
20943    cx.update_editor(|editor, window, cx| {
20944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20945            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20946        });
20947    });
20948
20949    assert_indent_guides(
20950        0..4,
20951        vec![
20952            indent_guide(buffer_id, 1, 3, 0),
20953            indent_guide(buffer_id, 2, 2, 1),
20954        ],
20955        Some(vec![0]),
20956        &mut cx,
20957    );
20958}
20959
20960#[gpui::test]
20961async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20962    let (buffer_id, mut cx) = setup_indent_guides_editor(
20963        &"
20964    fn main() {
20965        let a = 1;
20966
20967        let b = 2;
20968    }"
20969        .unindent(),
20970        cx,
20971    )
20972    .await;
20973
20974    cx.update_editor(|editor, window, cx| {
20975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20976            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20977        });
20978    });
20979
20980    assert_indent_guides(
20981        0..5,
20982        vec![indent_guide(buffer_id, 1, 3, 0)],
20983        Some(vec![0]),
20984        &mut cx,
20985    );
20986}
20987
20988#[gpui::test]
20989async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20990    let (buffer_id, mut cx) = setup_indent_guides_editor(
20991        &"
20992    def m:
20993        a = 1
20994        pass"
20995            .unindent(),
20996        cx,
20997    )
20998    .await;
20999
21000    cx.update_editor(|editor, window, cx| {
21001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21002            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21003        });
21004    });
21005
21006    assert_indent_guides(
21007        0..3,
21008        vec![indent_guide(buffer_id, 1, 2, 0)],
21009        Some(vec![0]),
21010        &mut cx,
21011    );
21012}
21013
21014#[gpui::test]
21015async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21016    init_test(cx, |_| {});
21017    let mut cx = EditorTestContext::new(cx).await;
21018    let text = indoc! {
21019        "
21020        impl A {
21021            fn b() {
21022                0;
21023                3;
21024                5;
21025                6;
21026                7;
21027            }
21028        }
21029        "
21030    };
21031    let base_text = indoc! {
21032        "
21033        impl A {
21034            fn b() {
21035                0;
21036                1;
21037                2;
21038                3;
21039                4;
21040            }
21041            fn c() {
21042                5;
21043                6;
21044                7;
21045            }
21046        }
21047        "
21048    };
21049
21050    cx.update_editor(|editor, window, cx| {
21051        editor.set_text(text, window, cx);
21052
21053        editor.buffer().update(cx, |multibuffer, cx| {
21054            let buffer = multibuffer.as_singleton().unwrap();
21055            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21056
21057            multibuffer.set_all_diff_hunks_expanded(cx);
21058            multibuffer.add_diff(diff, cx);
21059
21060            buffer.read(cx).remote_id()
21061        })
21062    });
21063    cx.run_until_parked();
21064
21065    cx.assert_state_with_diff(
21066        indoc! { "
21067          impl A {
21068              fn b() {
21069                  0;
21070        -         1;
21071        -         2;
21072                  3;
21073        -         4;
21074        -     }
21075        -     fn c() {
21076                  5;
21077                  6;
21078                  7;
21079              }
21080          }
21081          ˇ"
21082        }
21083        .to_string(),
21084    );
21085
21086    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21087        editor
21088            .snapshot(window, cx)
21089            .buffer_snapshot()
21090            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21091            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21092            .collect::<Vec<_>>()
21093    });
21094    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21095    assert_eq!(
21096        actual_guides,
21097        vec![
21098            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21099            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21100            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21101        ]
21102    );
21103}
21104
21105#[gpui::test]
21106async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21107    init_test(cx, |_| {});
21108    let mut cx = EditorTestContext::new(cx).await;
21109
21110    let diff_base = r#"
21111        a
21112        b
21113        c
21114        "#
21115    .unindent();
21116
21117    cx.set_state(
21118        &r#"
21119        ˇA
21120        b
21121        C
21122        "#
21123        .unindent(),
21124    );
21125    cx.set_head_text(&diff_base);
21126    cx.update_editor(|editor, window, cx| {
21127        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21128    });
21129    executor.run_until_parked();
21130
21131    let both_hunks_expanded = r#"
21132        - a
21133        + ˇA
21134          b
21135        - c
21136        + C
21137        "#
21138    .unindent();
21139
21140    cx.assert_state_with_diff(both_hunks_expanded.clone());
21141
21142    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21143        let snapshot = editor.snapshot(window, cx);
21144        let hunks = editor
21145            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21146            .collect::<Vec<_>>();
21147        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21148        let buffer_id = hunks[0].buffer_id;
21149        hunks
21150            .into_iter()
21151            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21152            .collect::<Vec<_>>()
21153    });
21154    assert_eq!(hunk_ranges.len(), 2);
21155
21156    cx.update_editor(|editor, _, cx| {
21157        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21158    });
21159    executor.run_until_parked();
21160
21161    let second_hunk_expanded = r#"
21162          ˇA
21163          b
21164        - c
21165        + C
21166        "#
21167    .unindent();
21168
21169    cx.assert_state_with_diff(second_hunk_expanded);
21170
21171    cx.update_editor(|editor, _, cx| {
21172        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21173    });
21174    executor.run_until_parked();
21175
21176    cx.assert_state_with_diff(both_hunks_expanded.clone());
21177
21178    cx.update_editor(|editor, _, cx| {
21179        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21180    });
21181    executor.run_until_parked();
21182
21183    let first_hunk_expanded = r#"
21184        - a
21185        + ˇA
21186          b
21187          C
21188        "#
21189    .unindent();
21190
21191    cx.assert_state_with_diff(first_hunk_expanded);
21192
21193    cx.update_editor(|editor, _, cx| {
21194        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21195    });
21196    executor.run_until_parked();
21197
21198    cx.assert_state_with_diff(both_hunks_expanded);
21199
21200    cx.set_state(
21201        &r#"
21202        ˇA
21203        b
21204        "#
21205        .unindent(),
21206    );
21207    cx.run_until_parked();
21208
21209    // TODO this cursor position seems bad
21210    cx.assert_state_with_diff(
21211        r#"
21212        - ˇa
21213        + A
21214          b
21215        "#
21216        .unindent(),
21217    );
21218
21219    cx.update_editor(|editor, window, cx| {
21220        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21221    });
21222
21223    cx.assert_state_with_diff(
21224        r#"
21225            - ˇa
21226            + A
21227              b
21228            - c
21229            "#
21230        .unindent(),
21231    );
21232
21233    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21234        let snapshot = editor.snapshot(window, cx);
21235        let hunks = editor
21236            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21237            .collect::<Vec<_>>();
21238        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21239        let buffer_id = hunks[0].buffer_id;
21240        hunks
21241            .into_iter()
21242            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21243            .collect::<Vec<_>>()
21244    });
21245    assert_eq!(hunk_ranges.len(), 2);
21246
21247    cx.update_editor(|editor, _, cx| {
21248        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21249    });
21250    executor.run_until_parked();
21251
21252    cx.assert_state_with_diff(
21253        r#"
21254        - ˇa
21255        + A
21256          b
21257        "#
21258        .unindent(),
21259    );
21260}
21261
21262#[gpui::test]
21263async fn test_toggle_deletion_hunk_at_start_of_file(
21264    executor: BackgroundExecutor,
21265    cx: &mut TestAppContext,
21266) {
21267    init_test(cx, |_| {});
21268    let mut cx = EditorTestContext::new(cx).await;
21269
21270    let diff_base = r#"
21271        a
21272        b
21273        c
21274        "#
21275    .unindent();
21276
21277    cx.set_state(
21278        &r#"
21279        ˇb
21280        c
21281        "#
21282        .unindent(),
21283    );
21284    cx.set_head_text(&diff_base);
21285    cx.update_editor(|editor, window, cx| {
21286        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21287    });
21288    executor.run_until_parked();
21289
21290    let hunk_expanded = r#"
21291        - a
21292          ˇb
21293          c
21294        "#
21295    .unindent();
21296
21297    cx.assert_state_with_diff(hunk_expanded.clone());
21298
21299    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21300        let snapshot = editor.snapshot(window, cx);
21301        let hunks = editor
21302            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21303            .collect::<Vec<_>>();
21304        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21305        let buffer_id = hunks[0].buffer_id;
21306        hunks
21307            .into_iter()
21308            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21309            .collect::<Vec<_>>()
21310    });
21311    assert_eq!(hunk_ranges.len(), 1);
21312
21313    cx.update_editor(|editor, _, cx| {
21314        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21315    });
21316    executor.run_until_parked();
21317
21318    let hunk_collapsed = r#"
21319          ˇb
21320          c
21321        "#
21322    .unindent();
21323
21324    cx.assert_state_with_diff(hunk_collapsed);
21325
21326    cx.update_editor(|editor, _, cx| {
21327        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21328    });
21329    executor.run_until_parked();
21330
21331    cx.assert_state_with_diff(hunk_expanded);
21332}
21333
21334#[gpui::test]
21335async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21336    init_test(cx, |_| {});
21337
21338    let fs = FakeFs::new(cx.executor());
21339    fs.insert_tree(
21340        path!("/test"),
21341        json!({
21342            ".git": {},
21343            "file-1": "ONE\n",
21344            "file-2": "TWO\n",
21345            "file-3": "THREE\n",
21346        }),
21347    )
21348    .await;
21349
21350    fs.set_head_for_repo(
21351        path!("/test/.git").as_ref(),
21352        &[
21353            ("file-1", "one\n".into()),
21354            ("file-2", "two\n".into()),
21355            ("file-3", "three\n".into()),
21356        ],
21357        "deadbeef",
21358    );
21359
21360    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21361    let mut buffers = vec![];
21362    for i in 1..=3 {
21363        let buffer = project
21364            .update(cx, |project, cx| {
21365                let path = format!(path!("/test/file-{}"), i);
21366                project.open_local_buffer(path, cx)
21367            })
21368            .await
21369            .unwrap();
21370        buffers.push(buffer);
21371    }
21372
21373    let multibuffer = cx.new(|cx| {
21374        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21375        multibuffer.set_all_diff_hunks_expanded(cx);
21376        for buffer in &buffers {
21377            let snapshot = buffer.read(cx).snapshot();
21378            multibuffer.set_excerpts_for_path(
21379                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21380                buffer.clone(),
21381                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21382                2,
21383                cx,
21384            );
21385        }
21386        multibuffer
21387    });
21388
21389    let editor = cx.add_window(|window, cx| {
21390        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21391    });
21392    cx.run_until_parked();
21393
21394    let snapshot = editor
21395        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21396        .unwrap();
21397    let hunks = snapshot
21398        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21399        .map(|hunk| match hunk {
21400            DisplayDiffHunk::Unfolded {
21401                display_row_range, ..
21402            } => display_row_range,
21403            DisplayDiffHunk::Folded { .. } => unreachable!(),
21404        })
21405        .collect::<Vec<_>>();
21406    assert_eq!(
21407        hunks,
21408        [
21409            DisplayRow(2)..DisplayRow(4),
21410            DisplayRow(7)..DisplayRow(9),
21411            DisplayRow(12)..DisplayRow(14),
21412        ]
21413    );
21414}
21415
21416#[gpui::test]
21417async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21418    init_test(cx, |_| {});
21419
21420    let mut cx = EditorTestContext::new(cx).await;
21421    cx.set_head_text(indoc! { "
21422        one
21423        two
21424        three
21425        four
21426        five
21427        "
21428    });
21429    cx.set_index_text(indoc! { "
21430        one
21431        two
21432        three
21433        four
21434        five
21435        "
21436    });
21437    cx.set_state(indoc! {"
21438        one
21439        TWO
21440        ˇTHREE
21441        FOUR
21442        five
21443    "});
21444    cx.run_until_parked();
21445    cx.update_editor(|editor, window, cx| {
21446        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21447    });
21448    cx.run_until_parked();
21449    cx.assert_index_text(Some(indoc! {"
21450        one
21451        TWO
21452        THREE
21453        FOUR
21454        five
21455    "}));
21456    cx.set_state(indoc! { "
21457        one
21458        TWO
21459        ˇTHREE-HUNDRED
21460        FOUR
21461        five
21462    "});
21463    cx.run_until_parked();
21464    cx.update_editor(|editor, window, cx| {
21465        let snapshot = editor.snapshot(window, cx);
21466        let hunks = editor
21467            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21468            .collect::<Vec<_>>();
21469        assert_eq!(hunks.len(), 1);
21470        assert_eq!(
21471            hunks[0].status(),
21472            DiffHunkStatus {
21473                kind: DiffHunkStatusKind::Modified,
21474                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21475            }
21476        );
21477
21478        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21479    });
21480    cx.run_until_parked();
21481    cx.assert_index_text(Some(indoc! {"
21482        one
21483        TWO
21484        THREE-HUNDRED
21485        FOUR
21486        five
21487    "}));
21488}
21489
21490#[gpui::test]
21491fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21492    init_test(cx, |_| {});
21493
21494    let editor = cx.add_window(|window, cx| {
21495        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21496        build_editor(buffer, window, cx)
21497    });
21498
21499    let render_args = Arc::new(Mutex::new(None));
21500    let snapshot = editor
21501        .update(cx, |editor, window, cx| {
21502            let snapshot = editor.buffer().read(cx).snapshot(cx);
21503            let range =
21504                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21505
21506            struct RenderArgs {
21507                row: MultiBufferRow,
21508                folded: bool,
21509                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21510            }
21511
21512            let crease = Crease::inline(
21513                range,
21514                FoldPlaceholder::test(),
21515                {
21516                    let toggle_callback = render_args.clone();
21517                    move |row, folded, callback, _window, _cx| {
21518                        *toggle_callback.lock() = Some(RenderArgs {
21519                            row,
21520                            folded,
21521                            callback,
21522                        });
21523                        div()
21524                    }
21525                },
21526                |_row, _folded, _window, _cx| div(),
21527            );
21528
21529            editor.insert_creases(Some(crease), cx);
21530            let snapshot = editor.snapshot(window, cx);
21531            let _div =
21532                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21533            snapshot
21534        })
21535        .unwrap();
21536
21537    let render_args = render_args.lock().take().unwrap();
21538    assert_eq!(render_args.row, MultiBufferRow(1));
21539    assert!(!render_args.folded);
21540    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21541
21542    cx.update_window(*editor, |_, window, cx| {
21543        (render_args.callback)(true, window, cx)
21544    })
21545    .unwrap();
21546    let snapshot = editor
21547        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21548        .unwrap();
21549    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21550
21551    cx.update_window(*editor, |_, window, cx| {
21552        (render_args.callback)(false, window, cx)
21553    })
21554    .unwrap();
21555    let snapshot = editor
21556        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21557        .unwrap();
21558    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21559}
21560
21561#[gpui::test]
21562async fn test_input_text(cx: &mut TestAppContext) {
21563    init_test(cx, |_| {});
21564    let mut cx = EditorTestContext::new(cx).await;
21565
21566    cx.set_state(
21567        &r#"ˇone
21568        two
21569
21570        three
21571        fourˇ
21572        five
21573
21574        siˇx"#
21575            .unindent(),
21576    );
21577
21578    cx.dispatch_action(HandleInput(String::new()));
21579    cx.assert_editor_state(
21580        &r#"ˇone
21581        two
21582
21583        three
21584        fourˇ
21585        five
21586
21587        siˇx"#
21588            .unindent(),
21589    );
21590
21591    cx.dispatch_action(HandleInput("AAAA".to_string()));
21592    cx.assert_editor_state(
21593        &r#"AAAAˇone
21594        two
21595
21596        three
21597        fourAAAAˇ
21598        five
21599
21600        siAAAAˇx"#
21601            .unindent(),
21602    );
21603}
21604
21605#[gpui::test]
21606async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21607    init_test(cx, |_| {});
21608
21609    let mut cx = EditorTestContext::new(cx).await;
21610    cx.set_state(
21611        r#"let foo = 1;
21612let foo = 2;
21613let foo = 3;
21614let fooˇ = 4;
21615let foo = 5;
21616let foo = 6;
21617let foo = 7;
21618let foo = 8;
21619let foo = 9;
21620let foo = 10;
21621let foo = 11;
21622let foo = 12;
21623let foo = 13;
21624let foo = 14;
21625let foo = 15;"#,
21626    );
21627
21628    cx.update_editor(|e, window, cx| {
21629        assert_eq!(
21630            e.next_scroll_position,
21631            NextScrollCursorCenterTopBottom::Center,
21632            "Default next scroll direction is center",
21633        );
21634
21635        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21636        assert_eq!(
21637            e.next_scroll_position,
21638            NextScrollCursorCenterTopBottom::Top,
21639            "After center, next scroll direction should be top",
21640        );
21641
21642        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21643        assert_eq!(
21644            e.next_scroll_position,
21645            NextScrollCursorCenterTopBottom::Bottom,
21646            "After top, next scroll direction should be bottom",
21647        );
21648
21649        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21650        assert_eq!(
21651            e.next_scroll_position,
21652            NextScrollCursorCenterTopBottom::Center,
21653            "After bottom, scrolling should start over",
21654        );
21655
21656        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21657        assert_eq!(
21658            e.next_scroll_position,
21659            NextScrollCursorCenterTopBottom::Top,
21660            "Scrolling continues if retriggered fast enough"
21661        );
21662    });
21663
21664    cx.executor()
21665        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21666    cx.executor().run_until_parked();
21667    cx.update_editor(|e, _, _| {
21668        assert_eq!(
21669            e.next_scroll_position,
21670            NextScrollCursorCenterTopBottom::Center,
21671            "If scrolling is not triggered fast enough, it should reset"
21672        );
21673    });
21674}
21675
21676#[gpui::test]
21677async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21678    init_test(cx, |_| {});
21679    let mut cx = EditorLspTestContext::new_rust(
21680        lsp::ServerCapabilities {
21681            definition_provider: Some(lsp::OneOf::Left(true)),
21682            references_provider: Some(lsp::OneOf::Left(true)),
21683            ..lsp::ServerCapabilities::default()
21684        },
21685        cx,
21686    )
21687    .await;
21688
21689    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21690        let go_to_definition = cx
21691            .lsp
21692            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21693                move |params, _| async move {
21694                    if empty_go_to_definition {
21695                        Ok(None)
21696                    } else {
21697                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21698                            uri: params.text_document_position_params.text_document.uri,
21699                            range: lsp::Range::new(
21700                                lsp::Position::new(4, 3),
21701                                lsp::Position::new(4, 6),
21702                            ),
21703                        })))
21704                    }
21705                },
21706            );
21707        let references = cx
21708            .lsp
21709            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21710                Ok(Some(vec![lsp::Location {
21711                    uri: params.text_document_position.text_document.uri,
21712                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21713                }]))
21714            });
21715        (go_to_definition, references)
21716    };
21717
21718    cx.set_state(
21719        &r#"fn one() {
21720            let mut a = ˇtwo();
21721        }
21722
21723        fn two() {}"#
21724            .unindent(),
21725    );
21726    set_up_lsp_handlers(false, &mut cx);
21727    let navigated = cx
21728        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21729        .await
21730        .expect("Failed to navigate to definition");
21731    assert_eq!(
21732        navigated,
21733        Navigated::Yes,
21734        "Should have navigated to definition from the GetDefinition response"
21735    );
21736    cx.assert_editor_state(
21737        &r#"fn one() {
21738            let mut a = two();
21739        }
21740
21741        fn «twoˇ»() {}"#
21742            .unindent(),
21743    );
21744
21745    let editors = cx.update_workspace(|workspace, _, cx| {
21746        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21747    });
21748    cx.update_editor(|_, _, test_editor_cx| {
21749        assert_eq!(
21750            editors.len(),
21751            1,
21752            "Initially, only one, test, editor should be open in the workspace"
21753        );
21754        assert_eq!(
21755            test_editor_cx.entity(),
21756            editors.last().expect("Asserted len is 1").clone()
21757        );
21758    });
21759
21760    set_up_lsp_handlers(true, &mut cx);
21761    let navigated = cx
21762        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21763        .await
21764        .expect("Failed to navigate to lookup references");
21765    assert_eq!(
21766        navigated,
21767        Navigated::Yes,
21768        "Should have navigated to references as a fallback after empty GoToDefinition response"
21769    );
21770    // We should not change the selections in the existing file,
21771    // if opening another milti buffer with the references
21772    cx.assert_editor_state(
21773        &r#"fn one() {
21774            let mut a = two();
21775        }
21776
21777        fn «twoˇ»() {}"#
21778            .unindent(),
21779    );
21780    let editors = cx.update_workspace(|workspace, _, cx| {
21781        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21782    });
21783    cx.update_editor(|_, _, test_editor_cx| {
21784        assert_eq!(
21785            editors.len(),
21786            2,
21787            "After falling back to references search, we open a new editor with the results"
21788        );
21789        let references_fallback_text = editors
21790            .into_iter()
21791            .find(|new_editor| *new_editor != test_editor_cx.entity())
21792            .expect("Should have one non-test editor now")
21793            .read(test_editor_cx)
21794            .text(test_editor_cx);
21795        assert_eq!(
21796            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21797            "Should use the range from the references response and not the GoToDefinition one"
21798        );
21799    });
21800}
21801
21802#[gpui::test]
21803async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21804    init_test(cx, |_| {});
21805    cx.update(|cx| {
21806        let mut editor_settings = EditorSettings::get_global(cx).clone();
21807        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21808        EditorSettings::override_global(editor_settings, cx);
21809    });
21810    let mut cx = EditorLspTestContext::new_rust(
21811        lsp::ServerCapabilities {
21812            definition_provider: Some(lsp::OneOf::Left(true)),
21813            references_provider: Some(lsp::OneOf::Left(true)),
21814            ..lsp::ServerCapabilities::default()
21815        },
21816        cx,
21817    )
21818    .await;
21819    let original_state = r#"fn one() {
21820        let mut a = ˇtwo();
21821    }
21822
21823    fn two() {}"#
21824        .unindent();
21825    cx.set_state(&original_state);
21826
21827    let mut go_to_definition = cx
21828        .lsp
21829        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21830            move |_, _| async move { Ok(None) },
21831        );
21832    let _references = cx
21833        .lsp
21834        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21835            panic!("Should not call for references with no go to definition fallback")
21836        });
21837
21838    let navigated = cx
21839        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21840        .await
21841        .expect("Failed to navigate to lookup references");
21842    go_to_definition
21843        .next()
21844        .await
21845        .expect("Should have called the go_to_definition handler");
21846
21847    assert_eq!(
21848        navigated,
21849        Navigated::No,
21850        "Should have navigated to references as a fallback after empty GoToDefinition response"
21851    );
21852    cx.assert_editor_state(&original_state);
21853    let editors = cx.update_workspace(|workspace, _, cx| {
21854        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21855    });
21856    cx.update_editor(|_, _, _| {
21857        assert_eq!(
21858            editors.len(),
21859            1,
21860            "After unsuccessful fallback, no other editor should have been opened"
21861        );
21862    });
21863}
21864
21865#[gpui::test]
21866async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21867    init_test(cx, |_| {});
21868    let mut cx = EditorLspTestContext::new_rust(
21869        lsp::ServerCapabilities {
21870            references_provider: Some(lsp::OneOf::Left(true)),
21871            ..lsp::ServerCapabilities::default()
21872        },
21873        cx,
21874    )
21875    .await;
21876
21877    cx.set_state(
21878        &r#"
21879        fn one() {
21880            let mut a = two();
21881        }
21882
21883        fn ˇtwo() {}"#
21884            .unindent(),
21885    );
21886    cx.lsp
21887        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21888            Ok(Some(vec![
21889                lsp::Location {
21890                    uri: params.text_document_position.text_document.uri.clone(),
21891                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21892                },
21893                lsp::Location {
21894                    uri: params.text_document_position.text_document.uri,
21895                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21896                },
21897            ]))
21898        });
21899    let navigated = cx
21900        .update_editor(|editor, window, cx| {
21901            editor.find_all_references(&FindAllReferences, window, cx)
21902        })
21903        .unwrap()
21904        .await
21905        .expect("Failed to navigate to references");
21906    assert_eq!(
21907        navigated,
21908        Navigated::Yes,
21909        "Should have navigated to references from the FindAllReferences response"
21910    );
21911    cx.assert_editor_state(
21912        &r#"fn one() {
21913            let mut a = two();
21914        }
21915
21916        fn ˇtwo() {}"#
21917            .unindent(),
21918    );
21919
21920    let editors = cx.update_workspace(|workspace, _, cx| {
21921        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21922    });
21923    cx.update_editor(|_, _, _| {
21924        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21925    });
21926
21927    cx.set_state(
21928        &r#"fn one() {
21929            let mut a = ˇtwo();
21930        }
21931
21932        fn two() {}"#
21933            .unindent(),
21934    );
21935    let navigated = cx
21936        .update_editor(|editor, window, cx| {
21937            editor.find_all_references(&FindAllReferences, window, cx)
21938        })
21939        .unwrap()
21940        .await
21941        .expect("Failed to navigate to references");
21942    assert_eq!(
21943        navigated,
21944        Navigated::Yes,
21945        "Should have navigated to references from the FindAllReferences response"
21946    );
21947    cx.assert_editor_state(
21948        &r#"fn one() {
21949            let mut a = ˇtwo();
21950        }
21951
21952        fn two() {}"#
21953            .unindent(),
21954    );
21955    let editors = cx.update_workspace(|workspace, _, cx| {
21956        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21957    });
21958    cx.update_editor(|_, _, _| {
21959        assert_eq!(
21960            editors.len(),
21961            2,
21962            "should have re-used the previous multibuffer"
21963        );
21964    });
21965
21966    cx.set_state(
21967        &r#"fn one() {
21968            let mut a = ˇtwo();
21969        }
21970        fn three() {}
21971        fn two() {}"#
21972            .unindent(),
21973    );
21974    cx.lsp
21975        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21976            Ok(Some(vec![
21977                lsp::Location {
21978                    uri: params.text_document_position.text_document.uri.clone(),
21979                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21980                },
21981                lsp::Location {
21982                    uri: params.text_document_position.text_document.uri,
21983                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21984                },
21985            ]))
21986        });
21987    let navigated = cx
21988        .update_editor(|editor, window, cx| {
21989            editor.find_all_references(&FindAllReferences, window, cx)
21990        })
21991        .unwrap()
21992        .await
21993        .expect("Failed to navigate to references");
21994    assert_eq!(
21995        navigated,
21996        Navigated::Yes,
21997        "Should have navigated to references from the FindAllReferences response"
21998    );
21999    cx.assert_editor_state(
22000        &r#"fn one() {
22001                let mut a = ˇtwo();
22002            }
22003            fn three() {}
22004            fn two() {}"#
22005            .unindent(),
22006    );
22007    let editors = cx.update_workspace(|workspace, _, cx| {
22008        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22009    });
22010    cx.update_editor(|_, _, _| {
22011        assert_eq!(
22012            editors.len(),
22013            3,
22014            "should have used a new multibuffer as offsets changed"
22015        );
22016    });
22017}
22018#[gpui::test]
22019async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22020    init_test(cx, |_| {});
22021
22022    let language = Arc::new(Language::new(
22023        LanguageConfig::default(),
22024        Some(tree_sitter_rust::LANGUAGE.into()),
22025    ));
22026
22027    let text = r#"
22028        #[cfg(test)]
22029        mod tests() {
22030            #[test]
22031            fn runnable_1() {
22032                let a = 1;
22033            }
22034
22035            #[test]
22036            fn runnable_2() {
22037                let a = 1;
22038                let b = 2;
22039            }
22040        }
22041    "#
22042    .unindent();
22043
22044    let fs = FakeFs::new(cx.executor());
22045    fs.insert_file("/file.rs", Default::default()).await;
22046
22047    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22048    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22049    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22050    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22051    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22052
22053    let editor = cx.new_window_entity(|window, cx| {
22054        Editor::new(
22055            EditorMode::full(),
22056            multi_buffer,
22057            Some(project.clone()),
22058            window,
22059            cx,
22060        )
22061    });
22062
22063    editor.update_in(cx, |editor, window, cx| {
22064        let snapshot = editor.buffer().read(cx).snapshot(cx);
22065        editor.tasks.insert(
22066            (buffer.read(cx).remote_id(), 3),
22067            RunnableTasks {
22068                templates: vec![],
22069                offset: snapshot.anchor_before(43),
22070                column: 0,
22071                extra_variables: HashMap::default(),
22072                context_range: BufferOffset(43)..BufferOffset(85),
22073            },
22074        );
22075        editor.tasks.insert(
22076            (buffer.read(cx).remote_id(), 8),
22077            RunnableTasks {
22078                templates: vec![],
22079                offset: snapshot.anchor_before(86),
22080                column: 0,
22081                extra_variables: HashMap::default(),
22082                context_range: BufferOffset(86)..BufferOffset(191),
22083            },
22084        );
22085
22086        // Test finding task when cursor is inside function body
22087        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22088            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22089        });
22090        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22091        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22092
22093        // Test finding task when cursor is on function name
22094        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22095            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22096        });
22097        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22098        assert_eq!(row, 8, "Should find task when cursor is on function name");
22099    });
22100}
22101
22102#[gpui::test]
22103async fn test_folding_buffers(cx: &mut TestAppContext) {
22104    init_test(cx, |_| {});
22105
22106    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22107    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22108    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22109
22110    let fs = FakeFs::new(cx.executor());
22111    fs.insert_tree(
22112        path!("/a"),
22113        json!({
22114            "first.rs": sample_text_1,
22115            "second.rs": sample_text_2,
22116            "third.rs": sample_text_3,
22117        }),
22118    )
22119    .await;
22120    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22121    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22122    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22123    let worktree = project.update(cx, |project, cx| {
22124        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22125        assert_eq!(worktrees.len(), 1);
22126        worktrees.pop().unwrap()
22127    });
22128    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22129
22130    let buffer_1 = project
22131        .update(cx, |project, cx| {
22132            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22133        })
22134        .await
22135        .unwrap();
22136    let buffer_2 = project
22137        .update(cx, |project, cx| {
22138            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22139        })
22140        .await
22141        .unwrap();
22142    let buffer_3 = project
22143        .update(cx, |project, cx| {
22144            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22145        })
22146        .await
22147        .unwrap();
22148
22149    let multi_buffer = cx.new(|cx| {
22150        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22151        multi_buffer.push_excerpts(
22152            buffer_1.clone(),
22153            [
22154                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22155                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22156                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22157            ],
22158            cx,
22159        );
22160        multi_buffer.push_excerpts(
22161            buffer_2.clone(),
22162            [
22163                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22164                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22165                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22166            ],
22167            cx,
22168        );
22169        multi_buffer.push_excerpts(
22170            buffer_3.clone(),
22171            [
22172                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22173                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22174                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22175            ],
22176            cx,
22177        );
22178        multi_buffer
22179    });
22180    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22181        Editor::new(
22182            EditorMode::full(),
22183            multi_buffer.clone(),
22184            Some(project.clone()),
22185            window,
22186            cx,
22187        )
22188    });
22189
22190    assert_eq!(
22191        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22192        "\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",
22193    );
22194
22195    multi_buffer_editor.update(cx, |editor, cx| {
22196        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22197    });
22198    assert_eq!(
22199        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22200        "\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",
22201        "After folding the first buffer, its text should not be displayed"
22202    );
22203
22204    multi_buffer_editor.update(cx, |editor, cx| {
22205        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22206    });
22207    assert_eq!(
22208        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22209        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22210        "After folding the second buffer, its text should not be displayed"
22211    );
22212
22213    multi_buffer_editor.update(cx, |editor, cx| {
22214        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22215    });
22216    assert_eq!(
22217        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22218        "\n\n\n\n\n",
22219        "After folding the third buffer, its text should not be displayed"
22220    );
22221
22222    // Emulate selection inside the fold logic, that should work
22223    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22224        editor
22225            .snapshot(window, cx)
22226            .next_line_boundary(Point::new(0, 4));
22227    });
22228
22229    multi_buffer_editor.update(cx, |editor, cx| {
22230        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22231    });
22232    assert_eq!(
22233        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22234        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22235        "After unfolding the second buffer, its text should be displayed"
22236    );
22237
22238    // Typing inside of buffer 1 causes that buffer to be unfolded.
22239    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22240        assert_eq!(
22241            multi_buffer
22242                .read(cx)
22243                .snapshot(cx)
22244                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22245                .collect::<String>(),
22246            "bbbb"
22247        );
22248        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22249            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22250        });
22251        editor.handle_input("B", window, cx);
22252    });
22253
22254    assert_eq!(
22255        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22256        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22257        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22258    );
22259
22260    multi_buffer_editor.update(cx, |editor, cx| {
22261        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22262    });
22263    assert_eq!(
22264        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22265        "\n\nB\n\n\n\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",
22266        "After unfolding the all buffers, all original text should be displayed"
22267    );
22268}
22269
22270#[gpui::test]
22271async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22272    init_test(cx, |_| {});
22273
22274    let sample_text_1 = "1111\n2222\n3333".to_string();
22275    let sample_text_2 = "4444\n5555\n6666".to_string();
22276    let sample_text_3 = "7777\n8888\n9999".to_string();
22277
22278    let fs = FakeFs::new(cx.executor());
22279    fs.insert_tree(
22280        path!("/a"),
22281        json!({
22282            "first.rs": sample_text_1,
22283            "second.rs": sample_text_2,
22284            "third.rs": sample_text_3,
22285        }),
22286    )
22287    .await;
22288    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22289    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22290    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22291    let worktree = project.update(cx, |project, cx| {
22292        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22293        assert_eq!(worktrees.len(), 1);
22294        worktrees.pop().unwrap()
22295    });
22296    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22297
22298    let buffer_1 = project
22299        .update(cx, |project, cx| {
22300            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22301        })
22302        .await
22303        .unwrap();
22304    let buffer_2 = project
22305        .update(cx, |project, cx| {
22306            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22307        })
22308        .await
22309        .unwrap();
22310    let buffer_3 = project
22311        .update(cx, |project, cx| {
22312            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22313        })
22314        .await
22315        .unwrap();
22316
22317    let multi_buffer = cx.new(|cx| {
22318        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22319        multi_buffer.push_excerpts(
22320            buffer_1.clone(),
22321            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22322            cx,
22323        );
22324        multi_buffer.push_excerpts(
22325            buffer_2.clone(),
22326            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22327            cx,
22328        );
22329        multi_buffer.push_excerpts(
22330            buffer_3.clone(),
22331            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22332            cx,
22333        );
22334        multi_buffer
22335    });
22336
22337    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22338        Editor::new(
22339            EditorMode::full(),
22340            multi_buffer,
22341            Some(project.clone()),
22342            window,
22343            cx,
22344        )
22345    });
22346
22347    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22348    assert_eq!(
22349        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22350        full_text,
22351    );
22352
22353    multi_buffer_editor.update(cx, |editor, cx| {
22354        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22355    });
22356    assert_eq!(
22357        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22358        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22359        "After folding the first buffer, its text should not be displayed"
22360    );
22361
22362    multi_buffer_editor.update(cx, |editor, cx| {
22363        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22364    });
22365
22366    assert_eq!(
22367        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22368        "\n\n\n\n\n\n7777\n8888\n9999",
22369        "After folding the second buffer, its text should not be displayed"
22370    );
22371
22372    multi_buffer_editor.update(cx, |editor, cx| {
22373        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22374    });
22375    assert_eq!(
22376        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22377        "\n\n\n\n\n",
22378        "After folding the third buffer, its text should not be displayed"
22379    );
22380
22381    multi_buffer_editor.update(cx, |editor, cx| {
22382        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22383    });
22384    assert_eq!(
22385        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22386        "\n\n\n\n4444\n5555\n6666\n\n",
22387        "After unfolding the second buffer, its text should be displayed"
22388    );
22389
22390    multi_buffer_editor.update(cx, |editor, cx| {
22391        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22392    });
22393    assert_eq!(
22394        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22395        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22396        "After unfolding the first buffer, its text should be displayed"
22397    );
22398
22399    multi_buffer_editor.update(cx, |editor, cx| {
22400        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22401    });
22402    assert_eq!(
22403        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22404        full_text,
22405        "After unfolding all buffers, all original text should be displayed"
22406    );
22407}
22408
22409#[gpui::test]
22410async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22411    init_test(cx, |_| {});
22412
22413    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22414
22415    let fs = FakeFs::new(cx.executor());
22416    fs.insert_tree(
22417        path!("/a"),
22418        json!({
22419            "main.rs": sample_text,
22420        }),
22421    )
22422    .await;
22423    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22424    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22425    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22426    let worktree = project.update(cx, |project, cx| {
22427        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22428        assert_eq!(worktrees.len(), 1);
22429        worktrees.pop().unwrap()
22430    });
22431    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22432
22433    let buffer_1 = project
22434        .update(cx, |project, cx| {
22435            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22436        })
22437        .await
22438        .unwrap();
22439
22440    let multi_buffer = cx.new(|cx| {
22441        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22442        multi_buffer.push_excerpts(
22443            buffer_1.clone(),
22444            [ExcerptRange::new(
22445                Point::new(0, 0)
22446                    ..Point::new(
22447                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22448                        0,
22449                    ),
22450            )],
22451            cx,
22452        );
22453        multi_buffer
22454    });
22455    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22456        Editor::new(
22457            EditorMode::full(),
22458            multi_buffer,
22459            Some(project.clone()),
22460            window,
22461            cx,
22462        )
22463    });
22464
22465    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22466    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22467        enum TestHighlight {}
22468        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22469        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22470        editor.highlight_text::<TestHighlight>(
22471            vec![highlight_range.clone()],
22472            HighlightStyle::color(Hsla::green()),
22473            cx,
22474        );
22475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22476            s.select_ranges(Some(highlight_range))
22477        });
22478    });
22479
22480    let full_text = format!("\n\n{sample_text}");
22481    assert_eq!(
22482        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22483        full_text,
22484    );
22485}
22486
22487#[gpui::test]
22488async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22489    init_test(cx, |_| {});
22490    cx.update(|cx| {
22491        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22492            "keymaps/default-linux.json",
22493            cx,
22494        )
22495        .unwrap();
22496        cx.bind_keys(default_key_bindings);
22497    });
22498
22499    let (editor, cx) = cx.add_window_view(|window, cx| {
22500        let multi_buffer = MultiBuffer::build_multi(
22501            [
22502                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22503                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22504                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22505                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22506            ],
22507            cx,
22508        );
22509        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22510
22511        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22512        // fold all but the second buffer, so that we test navigating between two
22513        // adjacent folded buffers, as well as folded buffers at the start and
22514        // end the multibuffer
22515        editor.fold_buffer(buffer_ids[0], cx);
22516        editor.fold_buffer(buffer_ids[2], cx);
22517        editor.fold_buffer(buffer_ids[3], cx);
22518
22519        editor
22520    });
22521    cx.simulate_resize(size(px(1000.), px(1000.)));
22522
22523    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22524    cx.assert_excerpts_with_selections(indoc! {"
22525        [EXCERPT]
22526        ˇ[FOLDED]
22527        [EXCERPT]
22528        a1
22529        b1
22530        [EXCERPT]
22531        [FOLDED]
22532        [EXCERPT]
22533        [FOLDED]
22534        "
22535    });
22536    cx.simulate_keystroke("down");
22537    cx.assert_excerpts_with_selections(indoc! {"
22538        [EXCERPT]
22539        [FOLDED]
22540        [EXCERPT]
22541        ˇa1
22542        b1
22543        [EXCERPT]
22544        [FOLDED]
22545        [EXCERPT]
22546        [FOLDED]
22547        "
22548    });
22549    cx.simulate_keystroke("down");
22550    cx.assert_excerpts_with_selections(indoc! {"
22551        [EXCERPT]
22552        [FOLDED]
22553        [EXCERPT]
22554        a1
22555        ˇb1
22556        [EXCERPT]
22557        [FOLDED]
22558        [EXCERPT]
22559        [FOLDED]
22560        "
22561    });
22562    cx.simulate_keystroke("down");
22563    cx.assert_excerpts_with_selections(indoc! {"
22564        [EXCERPT]
22565        [FOLDED]
22566        [EXCERPT]
22567        a1
22568        b1
22569        ˇ[EXCERPT]
22570        [FOLDED]
22571        [EXCERPT]
22572        [FOLDED]
22573        "
22574    });
22575    cx.simulate_keystroke("down");
22576    cx.assert_excerpts_with_selections(indoc! {"
22577        [EXCERPT]
22578        [FOLDED]
22579        [EXCERPT]
22580        a1
22581        b1
22582        [EXCERPT]
22583        ˇ[FOLDED]
22584        [EXCERPT]
22585        [FOLDED]
22586        "
22587    });
22588    for _ in 0..5 {
22589        cx.simulate_keystroke("down");
22590        cx.assert_excerpts_with_selections(indoc! {"
22591            [EXCERPT]
22592            [FOLDED]
22593            [EXCERPT]
22594            a1
22595            b1
22596            [EXCERPT]
22597            [FOLDED]
22598            [EXCERPT]
22599            ˇ[FOLDED]
22600            "
22601        });
22602    }
22603
22604    cx.simulate_keystroke("up");
22605    cx.assert_excerpts_with_selections(indoc! {"
22606        [EXCERPT]
22607        [FOLDED]
22608        [EXCERPT]
22609        a1
22610        b1
22611        [EXCERPT]
22612        ˇ[FOLDED]
22613        [EXCERPT]
22614        [FOLDED]
22615        "
22616    });
22617    cx.simulate_keystroke("up");
22618    cx.assert_excerpts_with_selections(indoc! {"
22619        [EXCERPT]
22620        [FOLDED]
22621        [EXCERPT]
22622        a1
22623        b1
22624        ˇ[EXCERPT]
22625        [FOLDED]
22626        [EXCERPT]
22627        [FOLDED]
22628        "
22629    });
22630    cx.simulate_keystroke("up");
22631    cx.assert_excerpts_with_selections(indoc! {"
22632        [EXCERPT]
22633        [FOLDED]
22634        [EXCERPT]
22635        a1
22636        ˇb1
22637        [EXCERPT]
22638        [FOLDED]
22639        [EXCERPT]
22640        [FOLDED]
22641        "
22642    });
22643    cx.simulate_keystroke("up");
22644    cx.assert_excerpts_with_selections(indoc! {"
22645        [EXCERPT]
22646        [FOLDED]
22647        [EXCERPT]
22648        ˇa1
22649        b1
22650        [EXCERPT]
22651        [FOLDED]
22652        [EXCERPT]
22653        [FOLDED]
22654        "
22655    });
22656    for _ in 0..5 {
22657        cx.simulate_keystroke("up");
22658        cx.assert_excerpts_with_selections(indoc! {"
22659            [EXCERPT]
22660            ˇ[FOLDED]
22661            [EXCERPT]
22662            a1
22663            b1
22664            [EXCERPT]
22665            [FOLDED]
22666            [EXCERPT]
22667            [FOLDED]
22668            "
22669        });
22670    }
22671}
22672
22673#[gpui::test]
22674async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22675    init_test(cx, |_| {});
22676
22677    // Simple insertion
22678    assert_highlighted_edits(
22679        "Hello, world!",
22680        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22681        true,
22682        cx,
22683        |highlighted_edits, cx| {
22684            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22685            assert_eq!(highlighted_edits.highlights.len(), 1);
22686            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22687            assert_eq!(
22688                highlighted_edits.highlights[0].1.background_color,
22689                Some(cx.theme().status().created_background)
22690            );
22691        },
22692    )
22693    .await;
22694
22695    // Replacement
22696    assert_highlighted_edits(
22697        "This is a test.",
22698        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22699        false,
22700        cx,
22701        |highlighted_edits, cx| {
22702            assert_eq!(highlighted_edits.text, "That is a test.");
22703            assert_eq!(highlighted_edits.highlights.len(), 1);
22704            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22705            assert_eq!(
22706                highlighted_edits.highlights[0].1.background_color,
22707                Some(cx.theme().status().created_background)
22708            );
22709        },
22710    )
22711    .await;
22712
22713    // Multiple edits
22714    assert_highlighted_edits(
22715        "Hello, world!",
22716        vec![
22717            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22718            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22719        ],
22720        false,
22721        cx,
22722        |highlighted_edits, cx| {
22723            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22724            assert_eq!(highlighted_edits.highlights.len(), 2);
22725            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22726            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22727            assert_eq!(
22728                highlighted_edits.highlights[0].1.background_color,
22729                Some(cx.theme().status().created_background)
22730            );
22731            assert_eq!(
22732                highlighted_edits.highlights[1].1.background_color,
22733                Some(cx.theme().status().created_background)
22734            );
22735        },
22736    )
22737    .await;
22738
22739    // Multiple lines with edits
22740    assert_highlighted_edits(
22741        "First line\nSecond line\nThird line\nFourth line",
22742        vec![
22743            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22744            (
22745                Point::new(2, 0)..Point::new(2, 10),
22746                "New third line".to_string(),
22747            ),
22748            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22749        ],
22750        false,
22751        cx,
22752        |highlighted_edits, cx| {
22753            assert_eq!(
22754                highlighted_edits.text,
22755                "Second modified\nNew third line\nFourth updated line"
22756            );
22757            assert_eq!(highlighted_edits.highlights.len(), 3);
22758            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22759            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22760            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22761            for highlight in &highlighted_edits.highlights {
22762                assert_eq!(
22763                    highlight.1.background_color,
22764                    Some(cx.theme().status().created_background)
22765                );
22766            }
22767        },
22768    )
22769    .await;
22770}
22771
22772#[gpui::test]
22773async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22774    init_test(cx, |_| {});
22775
22776    // Deletion
22777    assert_highlighted_edits(
22778        "Hello, world!",
22779        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22780        true,
22781        cx,
22782        |highlighted_edits, cx| {
22783            assert_eq!(highlighted_edits.text, "Hello, world!");
22784            assert_eq!(highlighted_edits.highlights.len(), 1);
22785            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22786            assert_eq!(
22787                highlighted_edits.highlights[0].1.background_color,
22788                Some(cx.theme().status().deleted_background)
22789            );
22790        },
22791    )
22792    .await;
22793
22794    // Insertion
22795    assert_highlighted_edits(
22796        "Hello, world!",
22797        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22798        true,
22799        cx,
22800        |highlighted_edits, cx| {
22801            assert_eq!(highlighted_edits.highlights.len(), 1);
22802            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22803            assert_eq!(
22804                highlighted_edits.highlights[0].1.background_color,
22805                Some(cx.theme().status().created_background)
22806            );
22807        },
22808    )
22809    .await;
22810}
22811
22812async fn assert_highlighted_edits(
22813    text: &str,
22814    edits: Vec<(Range<Point>, String)>,
22815    include_deletions: bool,
22816    cx: &mut TestAppContext,
22817    assertion_fn: impl Fn(HighlightedText, &App),
22818) {
22819    let window = cx.add_window(|window, cx| {
22820        let buffer = MultiBuffer::build_simple(text, cx);
22821        Editor::new(EditorMode::full(), buffer, None, window, cx)
22822    });
22823    let cx = &mut VisualTestContext::from_window(*window, cx);
22824
22825    let (buffer, snapshot) = window
22826        .update(cx, |editor, _window, cx| {
22827            (
22828                editor.buffer().clone(),
22829                editor.buffer().read(cx).snapshot(cx),
22830            )
22831        })
22832        .unwrap();
22833
22834    let edits = edits
22835        .into_iter()
22836        .map(|(range, edit)| {
22837            (
22838                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22839                edit,
22840            )
22841        })
22842        .collect::<Vec<_>>();
22843
22844    let text_anchor_edits = edits
22845        .clone()
22846        .into_iter()
22847        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22848        .collect::<Vec<_>>();
22849
22850    let edit_preview = window
22851        .update(cx, |_, _window, cx| {
22852            buffer
22853                .read(cx)
22854                .as_singleton()
22855                .unwrap()
22856                .read(cx)
22857                .preview_edits(text_anchor_edits.into(), cx)
22858        })
22859        .unwrap()
22860        .await;
22861
22862    cx.update(|_window, cx| {
22863        let highlighted_edits = edit_prediction_edit_text(
22864            snapshot.as_singleton().unwrap().2,
22865            &edits,
22866            &edit_preview,
22867            include_deletions,
22868            cx,
22869        );
22870        assertion_fn(highlighted_edits, cx)
22871    });
22872}
22873
22874#[track_caller]
22875fn assert_breakpoint(
22876    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22877    path: &Arc<Path>,
22878    expected: Vec<(u32, Breakpoint)>,
22879) {
22880    if expected.is_empty() {
22881        assert!(!breakpoints.contains_key(path), "{}", path.display());
22882    } else {
22883        let mut breakpoint = breakpoints
22884            .get(path)
22885            .unwrap()
22886            .iter()
22887            .map(|breakpoint| {
22888                (
22889                    breakpoint.row,
22890                    Breakpoint {
22891                        message: breakpoint.message.clone(),
22892                        state: breakpoint.state,
22893                        condition: breakpoint.condition.clone(),
22894                        hit_condition: breakpoint.hit_condition.clone(),
22895                    },
22896                )
22897            })
22898            .collect::<Vec<_>>();
22899
22900        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22901
22902        assert_eq!(expected, breakpoint);
22903    }
22904}
22905
22906fn add_log_breakpoint_at_cursor(
22907    editor: &mut Editor,
22908    log_message: &str,
22909    window: &mut Window,
22910    cx: &mut Context<Editor>,
22911) {
22912    let (anchor, bp) = editor
22913        .breakpoints_at_cursors(window, cx)
22914        .first()
22915        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22916        .unwrap_or_else(|| {
22917            let snapshot = editor.snapshot(window, cx);
22918            let cursor_position: Point =
22919                editor.selections.newest(&snapshot.display_snapshot).head();
22920
22921            let breakpoint_position = snapshot
22922                .buffer_snapshot()
22923                .anchor_before(Point::new(cursor_position.row, 0));
22924
22925            (breakpoint_position, Breakpoint::new_log(log_message))
22926        });
22927
22928    editor.edit_breakpoint_at_anchor(
22929        anchor,
22930        bp,
22931        BreakpointEditAction::EditLogMessage(log_message.into()),
22932        cx,
22933    );
22934}
22935
22936#[gpui::test]
22937async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22938    init_test(cx, |_| {});
22939
22940    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22941    let fs = FakeFs::new(cx.executor());
22942    fs.insert_tree(
22943        path!("/a"),
22944        json!({
22945            "main.rs": sample_text,
22946        }),
22947    )
22948    .await;
22949    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22951    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22952
22953    let fs = FakeFs::new(cx.executor());
22954    fs.insert_tree(
22955        path!("/a"),
22956        json!({
22957            "main.rs": sample_text,
22958        }),
22959    )
22960    .await;
22961    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22962    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22963    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22964    let worktree_id = workspace
22965        .update(cx, |workspace, _window, cx| {
22966            workspace.project().update(cx, |project, cx| {
22967                project.worktrees(cx).next().unwrap().read(cx).id()
22968            })
22969        })
22970        .unwrap();
22971
22972    let buffer = project
22973        .update(cx, |project, cx| {
22974            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22975        })
22976        .await
22977        .unwrap();
22978
22979    let (editor, cx) = cx.add_window_view(|window, cx| {
22980        Editor::new(
22981            EditorMode::full(),
22982            MultiBuffer::build_from_buffer(buffer, cx),
22983            Some(project.clone()),
22984            window,
22985            cx,
22986        )
22987    });
22988
22989    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22990    let abs_path = project.read_with(cx, |project, cx| {
22991        project
22992            .absolute_path(&project_path, cx)
22993            .map(Arc::from)
22994            .unwrap()
22995    });
22996
22997    // assert we can add breakpoint on the first line
22998    editor.update_in(cx, |editor, window, cx| {
22999        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23000        editor.move_to_end(&MoveToEnd, window, cx);
23001        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23002    });
23003
23004    let breakpoints = editor.update(cx, |editor, cx| {
23005        editor
23006            .breakpoint_store()
23007            .as_ref()
23008            .unwrap()
23009            .read(cx)
23010            .all_source_breakpoints(cx)
23011    });
23012
23013    assert_eq!(1, breakpoints.len());
23014    assert_breakpoint(
23015        &breakpoints,
23016        &abs_path,
23017        vec![
23018            (0, Breakpoint::new_standard()),
23019            (3, Breakpoint::new_standard()),
23020        ],
23021    );
23022
23023    editor.update_in(cx, |editor, window, cx| {
23024        editor.move_to_beginning(&MoveToBeginning, window, cx);
23025        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23026    });
23027
23028    let breakpoints = editor.update(cx, |editor, cx| {
23029        editor
23030            .breakpoint_store()
23031            .as_ref()
23032            .unwrap()
23033            .read(cx)
23034            .all_source_breakpoints(cx)
23035    });
23036
23037    assert_eq!(1, breakpoints.len());
23038    assert_breakpoint(
23039        &breakpoints,
23040        &abs_path,
23041        vec![(3, Breakpoint::new_standard())],
23042    );
23043
23044    editor.update_in(cx, |editor, window, cx| {
23045        editor.move_to_end(&MoveToEnd, window, cx);
23046        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23047    });
23048
23049    let breakpoints = editor.update(cx, |editor, cx| {
23050        editor
23051            .breakpoint_store()
23052            .as_ref()
23053            .unwrap()
23054            .read(cx)
23055            .all_source_breakpoints(cx)
23056    });
23057
23058    assert_eq!(0, breakpoints.len());
23059    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23060}
23061
23062#[gpui::test]
23063async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23064    init_test(cx, |_| {});
23065
23066    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23067
23068    let fs = FakeFs::new(cx.executor());
23069    fs.insert_tree(
23070        path!("/a"),
23071        json!({
23072            "main.rs": sample_text,
23073        }),
23074    )
23075    .await;
23076    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23077    let (workspace, cx) =
23078        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23079
23080    let worktree_id = workspace.update(cx, |workspace, cx| {
23081        workspace.project().update(cx, |project, cx| {
23082            project.worktrees(cx).next().unwrap().read(cx).id()
23083        })
23084    });
23085
23086    let buffer = project
23087        .update(cx, |project, cx| {
23088            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23089        })
23090        .await
23091        .unwrap();
23092
23093    let (editor, cx) = cx.add_window_view(|window, cx| {
23094        Editor::new(
23095            EditorMode::full(),
23096            MultiBuffer::build_from_buffer(buffer, cx),
23097            Some(project.clone()),
23098            window,
23099            cx,
23100        )
23101    });
23102
23103    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23104    let abs_path = project.read_with(cx, |project, cx| {
23105        project
23106            .absolute_path(&project_path, cx)
23107            .map(Arc::from)
23108            .unwrap()
23109    });
23110
23111    editor.update_in(cx, |editor, window, cx| {
23112        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23113    });
23114
23115    let breakpoints = editor.update(cx, |editor, cx| {
23116        editor
23117            .breakpoint_store()
23118            .as_ref()
23119            .unwrap()
23120            .read(cx)
23121            .all_source_breakpoints(cx)
23122    });
23123
23124    assert_breakpoint(
23125        &breakpoints,
23126        &abs_path,
23127        vec![(0, Breakpoint::new_log("hello world"))],
23128    );
23129
23130    // Removing a log message from a log breakpoint should remove it
23131    editor.update_in(cx, |editor, window, cx| {
23132        add_log_breakpoint_at_cursor(editor, "", window, cx);
23133    });
23134
23135    let breakpoints = editor.update(cx, |editor, cx| {
23136        editor
23137            .breakpoint_store()
23138            .as_ref()
23139            .unwrap()
23140            .read(cx)
23141            .all_source_breakpoints(cx)
23142    });
23143
23144    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23145
23146    editor.update_in(cx, |editor, window, cx| {
23147        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23148        editor.move_to_end(&MoveToEnd, window, cx);
23149        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23150        // Not adding a log message to a standard breakpoint shouldn't remove it
23151        add_log_breakpoint_at_cursor(editor, "", window, cx);
23152    });
23153
23154    let breakpoints = editor.update(cx, |editor, cx| {
23155        editor
23156            .breakpoint_store()
23157            .as_ref()
23158            .unwrap()
23159            .read(cx)
23160            .all_source_breakpoints(cx)
23161    });
23162
23163    assert_breakpoint(
23164        &breakpoints,
23165        &abs_path,
23166        vec![
23167            (0, Breakpoint::new_standard()),
23168            (3, Breakpoint::new_standard()),
23169        ],
23170    );
23171
23172    editor.update_in(cx, |editor, window, cx| {
23173        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23174    });
23175
23176    let breakpoints = editor.update(cx, |editor, cx| {
23177        editor
23178            .breakpoint_store()
23179            .as_ref()
23180            .unwrap()
23181            .read(cx)
23182            .all_source_breakpoints(cx)
23183    });
23184
23185    assert_breakpoint(
23186        &breakpoints,
23187        &abs_path,
23188        vec![
23189            (0, Breakpoint::new_standard()),
23190            (3, Breakpoint::new_log("hello world")),
23191        ],
23192    );
23193
23194    editor.update_in(cx, |editor, window, cx| {
23195        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23196    });
23197
23198    let breakpoints = editor.update(cx, |editor, cx| {
23199        editor
23200            .breakpoint_store()
23201            .as_ref()
23202            .unwrap()
23203            .read(cx)
23204            .all_source_breakpoints(cx)
23205    });
23206
23207    assert_breakpoint(
23208        &breakpoints,
23209        &abs_path,
23210        vec![
23211            (0, Breakpoint::new_standard()),
23212            (3, Breakpoint::new_log("hello Earth!!")),
23213        ],
23214    );
23215}
23216
23217/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23218/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23219/// or when breakpoints were placed out of order. This tests for a regression too
23220#[gpui::test]
23221async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23222    init_test(cx, |_| {});
23223
23224    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23225    let fs = FakeFs::new(cx.executor());
23226    fs.insert_tree(
23227        path!("/a"),
23228        json!({
23229            "main.rs": sample_text,
23230        }),
23231    )
23232    .await;
23233    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23234    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23235    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23236
23237    let fs = FakeFs::new(cx.executor());
23238    fs.insert_tree(
23239        path!("/a"),
23240        json!({
23241            "main.rs": sample_text,
23242        }),
23243    )
23244    .await;
23245    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23246    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23247    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23248    let worktree_id = workspace
23249        .update(cx, |workspace, _window, cx| {
23250            workspace.project().update(cx, |project, cx| {
23251                project.worktrees(cx).next().unwrap().read(cx).id()
23252            })
23253        })
23254        .unwrap();
23255
23256    let buffer = project
23257        .update(cx, |project, cx| {
23258            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23259        })
23260        .await
23261        .unwrap();
23262
23263    let (editor, cx) = cx.add_window_view(|window, cx| {
23264        Editor::new(
23265            EditorMode::full(),
23266            MultiBuffer::build_from_buffer(buffer, cx),
23267            Some(project.clone()),
23268            window,
23269            cx,
23270        )
23271    });
23272
23273    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23274    let abs_path = project.read_with(cx, |project, cx| {
23275        project
23276            .absolute_path(&project_path, cx)
23277            .map(Arc::from)
23278            .unwrap()
23279    });
23280
23281    // assert we can add breakpoint on the first line
23282    editor.update_in(cx, |editor, window, cx| {
23283        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23284        editor.move_to_end(&MoveToEnd, window, cx);
23285        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23286        editor.move_up(&MoveUp, window, cx);
23287        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23288    });
23289
23290    let breakpoints = editor.update(cx, |editor, cx| {
23291        editor
23292            .breakpoint_store()
23293            .as_ref()
23294            .unwrap()
23295            .read(cx)
23296            .all_source_breakpoints(cx)
23297    });
23298
23299    assert_eq!(1, breakpoints.len());
23300    assert_breakpoint(
23301        &breakpoints,
23302        &abs_path,
23303        vec![
23304            (0, Breakpoint::new_standard()),
23305            (2, Breakpoint::new_standard()),
23306            (3, Breakpoint::new_standard()),
23307        ],
23308    );
23309
23310    editor.update_in(cx, |editor, window, cx| {
23311        editor.move_to_beginning(&MoveToBeginning, window, cx);
23312        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23313        editor.move_to_end(&MoveToEnd, window, cx);
23314        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23315        // Disabling a breakpoint that doesn't exist should do nothing
23316        editor.move_up(&MoveUp, window, cx);
23317        editor.move_up(&MoveUp, window, cx);
23318        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23319    });
23320
23321    let breakpoints = editor.update(cx, |editor, cx| {
23322        editor
23323            .breakpoint_store()
23324            .as_ref()
23325            .unwrap()
23326            .read(cx)
23327            .all_source_breakpoints(cx)
23328    });
23329
23330    let disable_breakpoint = {
23331        let mut bp = Breakpoint::new_standard();
23332        bp.state = BreakpointState::Disabled;
23333        bp
23334    };
23335
23336    assert_eq!(1, breakpoints.len());
23337    assert_breakpoint(
23338        &breakpoints,
23339        &abs_path,
23340        vec![
23341            (0, disable_breakpoint.clone()),
23342            (2, Breakpoint::new_standard()),
23343            (3, disable_breakpoint.clone()),
23344        ],
23345    );
23346
23347    editor.update_in(cx, |editor, window, cx| {
23348        editor.move_to_beginning(&MoveToBeginning, window, cx);
23349        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23350        editor.move_to_end(&MoveToEnd, window, cx);
23351        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23352        editor.move_up(&MoveUp, window, cx);
23353        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23354    });
23355
23356    let breakpoints = editor.update(cx, |editor, cx| {
23357        editor
23358            .breakpoint_store()
23359            .as_ref()
23360            .unwrap()
23361            .read(cx)
23362            .all_source_breakpoints(cx)
23363    });
23364
23365    assert_eq!(1, breakpoints.len());
23366    assert_breakpoint(
23367        &breakpoints,
23368        &abs_path,
23369        vec![
23370            (0, Breakpoint::new_standard()),
23371            (2, disable_breakpoint),
23372            (3, Breakpoint::new_standard()),
23373        ],
23374    );
23375}
23376
23377#[gpui::test]
23378async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23379    init_test(cx, |_| {});
23380    let capabilities = lsp::ServerCapabilities {
23381        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23382            prepare_provider: Some(true),
23383            work_done_progress_options: Default::default(),
23384        })),
23385        ..Default::default()
23386    };
23387    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23388
23389    cx.set_state(indoc! {"
23390        struct Fˇoo {}
23391    "});
23392
23393    cx.update_editor(|editor, _, cx| {
23394        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23395        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23396        editor.highlight_background::<DocumentHighlightRead>(
23397            &[highlight_range],
23398            |theme| theme.colors().editor_document_highlight_read_background,
23399            cx,
23400        );
23401    });
23402
23403    let mut prepare_rename_handler = cx
23404        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23405            move |_, _, _| async move {
23406                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23407                    start: lsp::Position {
23408                        line: 0,
23409                        character: 7,
23410                    },
23411                    end: lsp::Position {
23412                        line: 0,
23413                        character: 10,
23414                    },
23415                })))
23416            },
23417        );
23418    let prepare_rename_task = cx
23419        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23420        .expect("Prepare rename was not started");
23421    prepare_rename_handler.next().await.unwrap();
23422    prepare_rename_task.await.expect("Prepare rename failed");
23423
23424    let mut rename_handler =
23425        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23426            let edit = lsp::TextEdit {
23427                range: lsp::Range {
23428                    start: lsp::Position {
23429                        line: 0,
23430                        character: 7,
23431                    },
23432                    end: lsp::Position {
23433                        line: 0,
23434                        character: 10,
23435                    },
23436                },
23437                new_text: "FooRenamed".to_string(),
23438            };
23439            Ok(Some(lsp::WorkspaceEdit::new(
23440                // Specify the same edit twice
23441                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23442            )))
23443        });
23444    let rename_task = cx
23445        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23446        .expect("Confirm rename was not started");
23447    rename_handler.next().await.unwrap();
23448    rename_task.await.expect("Confirm rename failed");
23449    cx.run_until_parked();
23450
23451    // Despite two edits, only one is actually applied as those are identical
23452    cx.assert_editor_state(indoc! {"
23453        struct FooRenamedˇ {}
23454    "});
23455}
23456
23457#[gpui::test]
23458async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23459    init_test(cx, |_| {});
23460    // These capabilities indicate that the server does not support prepare rename.
23461    let capabilities = lsp::ServerCapabilities {
23462        rename_provider: Some(lsp::OneOf::Left(true)),
23463        ..Default::default()
23464    };
23465    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23466
23467    cx.set_state(indoc! {"
23468        struct Fˇoo {}
23469    "});
23470
23471    cx.update_editor(|editor, _window, cx| {
23472        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23473        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23474        editor.highlight_background::<DocumentHighlightRead>(
23475            &[highlight_range],
23476            |theme| theme.colors().editor_document_highlight_read_background,
23477            cx,
23478        );
23479    });
23480
23481    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23482        .expect("Prepare rename was not started")
23483        .await
23484        .expect("Prepare rename failed");
23485
23486    let mut rename_handler =
23487        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23488            let edit = lsp::TextEdit {
23489                range: lsp::Range {
23490                    start: lsp::Position {
23491                        line: 0,
23492                        character: 7,
23493                    },
23494                    end: lsp::Position {
23495                        line: 0,
23496                        character: 10,
23497                    },
23498                },
23499                new_text: "FooRenamed".to_string(),
23500            };
23501            Ok(Some(lsp::WorkspaceEdit::new(
23502                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23503            )))
23504        });
23505    let rename_task = cx
23506        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23507        .expect("Confirm rename was not started");
23508    rename_handler.next().await.unwrap();
23509    rename_task.await.expect("Confirm rename failed");
23510    cx.run_until_parked();
23511
23512    // Correct range is renamed, as `surrounding_word` is used to find it.
23513    cx.assert_editor_state(indoc! {"
23514        struct FooRenamedˇ {}
23515    "});
23516}
23517
23518#[gpui::test]
23519async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23520    init_test(cx, |_| {});
23521    let mut cx = EditorTestContext::new(cx).await;
23522
23523    let language = Arc::new(
23524        Language::new(
23525            LanguageConfig::default(),
23526            Some(tree_sitter_html::LANGUAGE.into()),
23527        )
23528        .with_brackets_query(
23529            r#"
23530            ("<" @open "/>" @close)
23531            ("</" @open ">" @close)
23532            ("<" @open ">" @close)
23533            ("\"" @open "\"" @close)
23534            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23535        "#,
23536        )
23537        .unwrap(),
23538    );
23539    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23540
23541    cx.set_state(indoc! {"
23542        <span>ˇ</span>
23543    "});
23544    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23545    cx.assert_editor_state(indoc! {"
23546        <span>
23547        ˇ
23548        </span>
23549    "});
23550
23551    cx.set_state(indoc! {"
23552        <span><span></span>ˇ</span>
23553    "});
23554    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23555    cx.assert_editor_state(indoc! {"
23556        <span><span></span>
23557        ˇ</span>
23558    "});
23559
23560    cx.set_state(indoc! {"
23561        <span>ˇ
23562        </span>
23563    "});
23564    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23565    cx.assert_editor_state(indoc! {"
23566        <span>
23567        ˇ
23568        </span>
23569    "});
23570}
23571
23572#[gpui::test(iterations = 10)]
23573async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23574    init_test(cx, |_| {});
23575
23576    let fs = FakeFs::new(cx.executor());
23577    fs.insert_tree(
23578        path!("/dir"),
23579        json!({
23580            "a.ts": "a",
23581        }),
23582    )
23583    .await;
23584
23585    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23586    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23587    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23588
23589    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23590    language_registry.add(Arc::new(Language::new(
23591        LanguageConfig {
23592            name: "TypeScript".into(),
23593            matcher: LanguageMatcher {
23594                path_suffixes: vec!["ts".to_string()],
23595                ..Default::default()
23596            },
23597            ..Default::default()
23598        },
23599        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23600    )));
23601    let mut fake_language_servers = language_registry.register_fake_lsp(
23602        "TypeScript",
23603        FakeLspAdapter {
23604            capabilities: lsp::ServerCapabilities {
23605                code_lens_provider: Some(lsp::CodeLensOptions {
23606                    resolve_provider: Some(true),
23607                }),
23608                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23609                    commands: vec!["_the/command".to_string()],
23610                    ..lsp::ExecuteCommandOptions::default()
23611                }),
23612                ..lsp::ServerCapabilities::default()
23613            },
23614            ..FakeLspAdapter::default()
23615        },
23616    );
23617
23618    let editor = workspace
23619        .update(cx, |workspace, window, cx| {
23620            workspace.open_abs_path(
23621                PathBuf::from(path!("/dir/a.ts")),
23622                OpenOptions::default(),
23623                window,
23624                cx,
23625            )
23626        })
23627        .unwrap()
23628        .await
23629        .unwrap()
23630        .downcast::<Editor>()
23631        .unwrap();
23632    cx.executor().run_until_parked();
23633
23634    let fake_server = fake_language_servers.next().await.unwrap();
23635
23636    let buffer = editor.update(cx, |editor, cx| {
23637        editor
23638            .buffer()
23639            .read(cx)
23640            .as_singleton()
23641            .expect("have opened a single file by path")
23642    });
23643
23644    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23645    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23646    drop(buffer_snapshot);
23647    let actions = cx
23648        .update_window(*workspace, |_, window, cx| {
23649            project.code_actions(&buffer, anchor..anchor, window, cx)
23650        })
23651        .unwrap();
23652
23653    fake_server
23654        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23655            Ok(Some(vec![
23656                lsp::CodeLens {
23657                    range: lsp::Range::default(),
23658                    command: Some(lsp::Command {
23659                        title: "Code lens command".to_owned(),
23660                        command: "_the/command".to_owned(),
23661                        arguments: None,
23662                    }),
23663                    data: None,
23664                },
23665                lsp::CodeLens {
23666                    range: lsp::Range::default(),
23667                    command: Some(lsp::Command {
23668                        title: "Command not in capabilities".to_owned(),
23669                        command: "not in capabilities".to_owned(),
23670                        arguments: None,
23671                    }),
23672                    data: None,
23673                },
23674                lsp::CodeLens {
23675                    range: lsp::Range {
23676                        start: lsp::Position {
23677                            line: 1,
23678                            character: 1,
23679                        },
23680                        end: lsp::Position {
23681                            line: 1,
23682                            character: 1,
23683                        },
23684                    },
23685                    command: Some(lsp::Command {
23686                        title: "Command not in range".to_owned(),
23687                        command: "_the/command".to_owned(),
23688                        arguments: None,
23689                    }),
23690                    data: None,
23691                },
23692            ]))
23693        })
23694        .next()
23695        .await;
23696
23697    let actions = actions.await.unwrap();
23698    assert_eq!(
23699        actions.len(),
23700        1,
23701        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23702    );
23703    let action = actions[0].clone();
23704    let apply = project.update(cx, |project, cx| {
23705        project.apply_code_action(buffer.clone(), action, true, cx)
23706    });
23707
23708    // Resolving the code action does not populate its edits. In absence of
23709    // edits, we must execute the given command.
23710    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23711        |mut lens, _| async move {
23712            let lens_command = lens.command.as_mut().expect("should have a command");
23713            assert_eq!(lens_command.title, "Code lens command");
23714            lens_command.arguments = Some(vec![json!("the-argument")]);
23715            Ok(lens)
23716        },
23717    );
23718
23719    // While executing the command, the language server sends the editor
23720    // a `workspaceEdit` request.
23721    fake_server
23722        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23723            let fake = fake_server.clone();
23724            move |params, _| {
23725                assert_eq!(params.command, "_the/command");
23726                let fake = fake.clone();
23727                async move {
23728                    fake.server
23729                        .request::<lsp::request::ApplyWorkspaceEdit>(
23730                            lsp::ApplyWorkspaceEditParams {
23731                                label: None,
23732                                edit: lsp::WorkspaceEdit {
23733                                    changes: Some(
23734                                        [(
23735                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23736                                            vec![lsp::TextEdit {
23737                                                range: lsp::Range::new(
23738                                                    lsp::Position::new(0, 0),
23739                                                    lsp::Position::new(0, 0),
23740                                                ),
23741                                                new_text: "X".into(),
23742                                            }],
23743                                        )]
23744                                        .into_iter()
23745                                        .collect(),
23746                                    ),
23747                                    ..lsp::WorkspaceEdit::default()
23748                                },
23749                            },
23750                        )
23751                        .await
23752                        .into_response()
23753                        .unwrap();
23754                    Ok(Some(json!(null)))
23755                }
23756            }
23757        })
23758        .next()
23759        .await;
23760
23761    // Applying the code lens command returns a project transaction containing the edits
23762    // sent by the language server in its `workspaceEdit` request.
23763    let transaction = apply.await.unwrap();
23764    assert!(transaction.0.contains_key(&buffer));
23765    buffer.update(cx, |buffer, cx| {
23766        assert_eq!(buffer.text(), "Xa");
23767        buffer.undo(cx);
23768        assert_eq!(buffer.text(), "a");
23769    });
23770
23771    let actions_after_edits = cx
23772        .update_window(*workspace, |_, window, cx| {
23773            project.code_actions(&buffer, anchor..anchor, window, cx)
23774        })
23775        .unwrap()
23776        .await
23777        .unwrap();
23778    assert_eq!(
23779        actions, actions_after_edits,
23780        "For the same selection, same code lens actions should be returned"
23781    );
23782
23783    let _responses =
23784        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23785            panic!("No more code lens requests are expected");
23786        });
23787    editor.update_in(cx, |editor, window, cx| {
23788        editor.select_all(&SelectAll, window, cx);
23789    });
23790    cx.executor().run_until_parked();
23791    let new_actions = cx
23792        .update_window(*workspace, |_, window, cx| {
23793            project.code_actions(&buffer, anchor..anchor, window, cx)
23794        })
23795        .unwrap()
23796        .await
23797        .unwrap();
23798    assert_eq!(
23799        actions, new_actions,
23800        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23801    );
23802}
23803
23804#[gpui::test]
23805async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23806    init_test(cx, |_| {});
23807
23808    let fs = FakeFs::new(cx.executor());
23809    let main_text = r#"fn main() {
23810println!("1");
23811println!("2");
23812println!("3");
23813println!("4");
23814println!("5");
23815}"#;
23816    let lib_text = "mod foo {}";
23817    fs.insert_tree(
23818        path!("/a"),
23819        json!({
23820            "lib.rs": lib_text,
23821            "main.rs": main_text,
23822        }),
23823    )
23824    .await;
23825
23826    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23827    let (workspace, cx) =
23828        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23829    let worktree_id = workspace.update(cx, |workspace, cx| {
23830        workspace.project().update(cx, |project, cx| {
23831            project.worktrees(cx).next().unwrap().read(cx).id()
23832        })
23833    });
23834
23835    let expected_ranges = vec![
23836        Point::new(0, 0)..Point::new(0, 0),
23837        Point::new(1, 0)..Point::new(1, 1),
23838        Point::new(2, 0)..Point::new(2, 2),
23839        Point::new(3, 0)..Point::new(3, 3),
23840    ];
23841
23842    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23843    let editor_1 = workspace
23844        .update_in(cx, |workspace, window, cx| {
23845            workspace.open_path(
23846                (worktree_id, rel_path("main.rs")),
23847                Some(pane_1.downgrade()),
23848                true,
23849                window,
23850                cx,
23851            )
23852        })
23853        .unwrap()
23854        .await
23855        .downcast::<Editor>()
23856        .unwrap();
23857    pane_1.update(cx, |pane, cx| {
23858        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23859        open_editor.update(cx, |editor, cx| {
23860            assert_eq!(
23861                editor.display_text(cx),
23862                main_text,
23863                "Original main.rs text on initial open",
23864            );
23865            assert_eq!(
23866                editor
23867                    .selections
23868                    .all::<Point>(&editor.display_snapshot(cx))
23869                    .into_iter()
23870                    .map(|s| s.range())
23871                    .collect::<Vec<_>>(),
23872                vec![Point::zero()..Point::zero()],
23873                "Default selections on initial open",
23874            );
23875        })
23876    });
23877    editor_1.update_in(cx, |editor, window, cx| {
23878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23879            s.select_ranges(expected_ranges.clone());
23880        });
23881    });
23882
23883    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23884        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23885    });
23886    let editor_2 = workspace
23887        .update_in(cx, |workspace, window, cx| {
23888            workspace.open_path(
23889                (worktree_id, rel_path("main.rs")),
23890                Some(pane_2.downgrade()),
23891                true,
23892                window,
23893                cx,
23894            )
23895        })
23896        .unwrap()
23897        .await
23898        .downcast::<Editor>()
23899        .unwrap();
23900    pane_2.update(cx, |pane, cx| {
23901        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23902        open_editor.update(cx, |editor, cx| {
23903            assert_eq!(
23904                editor.display_text(cx),
23905                main_text,
23906                "Original main.rs text on initial open in another panel",
23907            );
23908            assert_eq!(
23909                editor
23910                    .selections
23911                    .all::<Point>(&editor.display_snapshot(cx))
23912                    .into_iter()
23913                    .map(|s| s.range())
23914                    .collect::<Vec<_>>(),
23915                vec![Point::zero()..Point::zero()],
23916                "Default selections on initial open in another panel",
23917            );
23918        })
23919    });
23920
23921    editor_2.update_in(cx, |editor, window, cx| {
23922        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23923    });
23924
23925    let _other_editor_1 = workspace
23926        .update_in(cx, |workspace, window, cx| {
23927            workspace.open_path(
23928                (worktree_id, rel_path("lib.rs")),
23929                Some(pane_1.downgrade()),
23930                true,
23931                window,
23932                cx,
23933            )
23934        })
23935        .unwrap()
23936        .await
23937        .downcast::<Editor>()
23938        .unwrap();
23939    pane_1
23940        .update_in(cx, |pane, window, cx| {
23941            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23942        })
23943        .await
23944        .unwrap();
23945    drop(editor_1);
23946    pane_1.update(cx, |pane, cx| {
23947        pane.active_item()
23948            .unwrap()
23949            .downcast::<Editor>()
23950            .unwrap()
23951            .update(cx, |editor, cx| {
23952                assert_eq!(
23953                    editor.display_text(cx),
23954                    lib_text,
23955                    "Other file should be open and active",
23956                );
23957            });
23958        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23959    });
23960
23961    let _other_editor_2 = workspace
23962        .update_in(cx, |workspace, window, cx| {
23963            workspace.open_path(
23964                (worktree_id, rel_path("lib.rs")),
23965                Some(pane_2.downgrade()),
23966                true,
23967                window,
23968                cx,
23969            )
23970        })
23971        .unwrap()
23972        .await
23973        .downcast::<Editor>()
23974        .unwrap();
23975    pane_2
23976        .update_in(cx, |pane, window, cx| {
23977            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23978        })
23979        .await
23980        .unwrap();
23981    drop(editor_2);
23982    pane_2.update(cx, |pane, cx| {
23983        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23984        open_editor.update(cx, |editor, cx| {
23985            assert_eq!(
23986                editor.display_text(cx),
23987                lib_text,
23988                "Other file should be open and active in another panel too",
23989            );
23990        });
23991        assert_eq!(
23992            pane.items().count(),
23993            1,
23994            "No other editors should be open in another pane",
23995        );
23996    });
23997
23998    let _editor_1_reopened = workspace
23999        .update_in(cx, |workspace, window, cx| {
24000            workspace.open_path(
24001                (worktree_id, rel_path("main.rs")),
24002                Some(pane_1.downgrade()),
24003                true,
24004                window,
24005                cx,
24006            )
24007        })
24008        .unwrap()
24009        .await
24010        .downcast::<Editor>()
24011        .unwrap();
24012    let _editor_2_reopened = workspace
24013        .update_in(cx, |workspace, window, cx| {
24014            workspace.open_path(
24015                (worktree_id, rel_path("main.rs")),
24016                Some(pane_2.downgrade()),
24017                true,
24018                window,
24019                cx,
24020            )
24021        })
24022        .unwrap()
24023        .await
24024        .downcast::<Editor>()
24025        .unwrap();
24026    pane_1.update(cx, |pane, cx| {
24027        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24028        open_editor.update(cx, |editor, cx| {
24029            assert_eq!(
24030                editor.display_text(cx),
24031                main_text,
24032                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24033            );
24034            assert_eq!(
24035                editor
24036                    .selections
24037                    .all::<Point>(&editor.display_snapshot(cx))
24038                    .into_iter()
24039                    .map(|s| s.range())
24040                    .collect::<Vec<_>>(),
24041                expected_ranges,
24042                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24043            );
24044        })
24045    });
24046    pane_2.update(cx, |pane, cx| {
24047        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24048        open_editor.update(cx, |editor, cx| {
24049            assert_eq!(
24050                editor.display_text(cx),
24051                r#"fn main() {
24052⋯rintln!("1");
24053⋯intln!("2");
24054⋯ntln!("3");
24055println!("4");
24056println!("5");
24057}"#,
24058                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24059            );
24060            assert_eq!(
24061                editor
24062                    .selections
24063                    .all::<Point>(&editor.display_snapshot(cx))
24064                    .into_iter()
24065                    .map(|s| s.range())
24066                    .collect::<Vec<_>>(),
24067                vec![Point::zero()..Point::zero()],
24068                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24069            );
24070        })
24071    });
24072}
24073
24074#[gpui::test]
24075async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24076    init_test(cx, |_| {});
24077
24078    let fs = FakeFs::new(cx.executor());
24079    let main_text = r#"fn main() {
24080println!("1");
24081println!("2");
24082println!("3");
24083println!("4");
24084println!("5");
24085}"#;
24086    let lib_text = "mod foo {}";
24087    fs.insert_tree(
24088        path!("/a"),
24089        json!({
24090            "lib.rs": lib_text,
24091            "main.rs": main_text,
24092        }),
24093    )
24094    .await;
24095
24096    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24097    let (workspace, cx) =
24098        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24099    let worktree_id = workspace.update(cx, |workspace, cx| {
24100        workspace.project().update(cx, |project, cx| {
24101            project.worktrees(cx).next().unwrap().read(cx).id()
24102        })
24103    });
24104
24105    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24106    let editor = workspace
24107        .update_in(cx, |workspace, window, cx| {
24108            workspace.open_path(
24109                (worktree_id, rel_path("main.rs")),
24110                Some(pane.downgrade()),
24111                true,
24112                window,
24113                cx,
24114            )
24115        })
24116        .unwrap()
24117        .await
24118        .downcast::<Editor>()
24119        .unwrap();
24120    pane.update(cx, |pane, cx| {
24121        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24122        open_editor.update(cx, |editor, cx| {
24123            assert_eq!(
24124                editor.display_text(cx),
24125                main_text,
24126                "Original main.rs text on initial open",
24127            );
24128        })
24129    });
24130    editor.update_in(cx, |editor, window, cx| {
24131        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24132    });
24133
24134    cx.update_global(|store: &mut SettingsStore, cx| {
24135        store.update_user_settings(cx, |s| {
24136            s.workspace.restore_on_file_reopen = Some(false);
24137        });
24138    });
24139    editor.update_in(cx, |editor, window, cx| {
24140        editor.fold_ranges(
24141            vec![
24142                Point::new(1, 0)..Point::new(1, 1),
24143                Point::new(2, 0)..Point::new(2, 2),
24144                Point::new(3, 0)..Point::new(3, 3),
24145            ],
24146            false,
24147            window,
24148            cx,
24149        );
24150    });
24151    pane.update_in(cx, |pane, window, cx| {
24152        pane.close_all_items(&CloseAllItems::default(), window, cx)
24153    })
24154    .await
24155    .unwrap();
24156    pane.update(cx, |pane, _| {
24157        assert!(pane.active_item().is_none());
24158    });
24159    cx.update_global(|store: &mut SettingsStore, cx| {
24160        store.update_user_settings(cx, |s| {
24161            s.workspace.restore_on_file_reopen = Some(true);
24162        });
24163    });
24164
24165    let _editor_reopened = workspace
24166        .update_in(cx, |workspace, window, cx| {
24167            workspace.open_path(
24168                (worktree_id, rel_path("main.rs")),
24169                Some(pane.downgrade()),
24170                true,
24171                window,
24172                cx,
24173            )
24174        })
24175        .unwrap()
24176        .await
24177        .downcast::<Editor>()
24178        .unwrap();
24179    pane.update(cx, |pane, cx| {
24180        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24181        open_editor.update(cx, |editor, cx| {
24182            assert_eq!(
24183                editor.display_text(cx),
24184                main_text,
24185                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24186            );
24187        })
24188    });
24189}
24190
24191#[gpui::test]
24192async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24193    struct EmptyModalView {
24194        focus_handle: gpui::FocusHandle,
24195    }
24196    impl EventEmitter<DismissEvent> for EmptyModalView {}
24197    impl Render for EmptyModalView {
24198        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24199            div()
24200        }
24201    }
24202    impl Focusable for EmptyModalView {
24203        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24204            self.focus_handle.clone()
24205        }
24206    }
24207    impl workspace::ModalView for EmptyModalView {}
24208    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24209        EmptyModalView {
24210            focus_handle: cx.focus_handle(),
24211        }
24212    }
24213
24214    init_test(cx, |_| {});
24215
24216    let fs = FakeFs::new(cx.executor());
24217    let project = Project::test(fs, [], cx).await;
24218    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24219    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24220    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24221    let editor = cx.new_window_entity(|window, cx| {
24222        Editor::new(
24223            EditorMode::full(),
24224            buffer,
24225            Some(project.clone()),
24226            window,
24227            cx,
24228        )
24229    });
24230    workspace
24231        .update(cx, |workspace, window, cx| {
24232            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24233        })
24234        .unwrap();
24235    editor.update_in(cx, |editor, window, cx| {
24236        editor.open_context_menu(&OpenContextMenu, window, cx);
24237        assert!(editor.mouse_context_menu.is_some());
24238    });
24239    workspace
24240        .update(cx, |workspace, window, cx| {
24241            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24242        })
24243        .unwrap();
24244    cx.read(|cx| {
24245        assert!(editor.read(cx).mouse_context_menu.is_none());
24246    });
24247}
24248
24249fn set_linked_edit_ranges(
24250    opening: (Point, Point),
24251    closing: (Point, Point),
24252    editor: &mut Editor,
24253    cx: &mut Context<Editor>,
24254) {
24255    let Some((buffer, _)) = editor
24256        .buffer
24257        .read(cx)
24258        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24259    else {
24260        panic!("Failed to get buffer for selection position");
24261    };
24262    let buffer = buffer.read(cx);
24263    let buffer_id = buffer.remote_id();
24264    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24265    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24266    let mut linked_ranges = HashMap::default();
24267    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24268    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24269}
24270
24271#[gpui::test]
24272async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24273    init_test(cx, |_| {});
24274
24275    let fs = FakeFs::new(cx.executor());
24276    fs.insert_file(path!("/file.html"), Default::default())
24277        .await;
24278
24279    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24280
24281    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24282    let html_language = Arc::new(Language::new(
24283        LanguageConfig {
24284            name: "HTML".into(),
24285            matcher: LanguageMatcher {
24286                path_suffixes: vec!["html".to_string()],
24287                ..LanguageMatcher::default()
24288            },
24289            brackets: BracketPairConfig {
24290                pairs: vec![BracketPair {
24291                    start: "<".into(),
24292                    end: ">".into(),
24293                    close: true,
24294                    ..Default::default()
24295                }],
24296                ..Default::default()
24297            },
24298            ..Default::default()
24299        },
24300        Some(tree_sitter_html::LANGUAGE.into()),
24301    ));
24302    language_registry.add(html_language);
24303    let mut fake_servers = language_registry.register_fake_lsp(
24304        "HTML",
24305        FakeLspAdapter {
24306            capabilities: lsp::ServerCapabilities {
24307                completion_provider: Some(lsp::CompletionOptions {
24308                    resolve_provider: Some(true),
24309                    ..Default::default()
24310                }),
24311                ..Default::default()
24312            },
24313            ..Default::default()
24314        },
24315    );
24316
24317    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24318    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24319
24320    let worktree_id = workspace
24321        .update(cx, |workspace, _window, cx| {
24322            workspace.project().update(cx, |project, cx| {
24323                project.worktrees(cx).next().unwrap().read(cx).id()
24324            })
24325        })
24326        .unwrap();
24327    project
24328        .update(cx, |project, cx| {
24329            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24330        })
24331        .await
24332        .unwrap();
24333    let editor = workspace
24334        .update(cx, |workspace, window, cx| {
24335            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24336        })
24337        .unwrap()
24338        .await
24339        .unwrap()
24340        .downcast::<Editor>()
24341        .unwrap();
24342
24343    let fake_server = fake_servers.next().await.unwrap();
24344    editor.update_in(cx, |editor, window, cx| {
24345        editor.set_text("<ad></ad>", window, cx);
24346        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24347            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24348        });
24349        set_linked_edit_ranges(
24350            (Point::new(0, 1), Point::new(0, 3)),
24351            (Point::new(0, 6), Point::new(0, 8)),
24352            editor,
24353            cx,
24354        );
24355    });
24356    let mut completion_handle =
24357        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24358            Ok(Some(lsp::CompletionResponse::Array(vec![
24359                lsp::CompletionItem {
24360                    label: "head".to_string(),
24361                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24362                        lsp::InsertReplaceEdit {
24363                            new_text: "head".to_string(),
24364                            insert: lsp::Range::new(
24365                                lsp::Position::new(0, 1),
24366                                lsp::Position::new(0, 3),
24367                            ),
24368                            replace: lsp::Range::new(
24369                                lsp::Position::new(0, 1),
24370                                lsp::Position::new(0, 3),
24371                            ),
24372                        },
24373                    )),
24374                    ..Default::default()
24375                },
24376            ])))
24377        });
24378    editor.update_in(cx, |editor, window, cx| {
24379        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24380    });
24381    cx.run_until_parked();
24382    completion_handle.next().await.unwrap();
24383    editor.update(cx, |editor, _| {
24384        assert!(
24385            editor.context_menu_visible(),
24386            "Completion menu should be visible"
24387        );
24388    });
24389    editor.update_in(cx, |editor, window, cx| {
24390        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24391    });
24392    cx.executor().run_until_parked();
24393    editor.update(cx, |editor, cx| {
24394        assert_eq!(editor.text(cx), "<head></head>");
24395    });
24396}
24397
24398#[gpui::test]
24399async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24400    init_test(cx, |_| {});
24401
24402    let mut cx = EditorTestContext::new(cx).await;
24403    let language = Arc::new(Language::new(
24404        LanguageConfig {
24405            name: "TSX".into(),
24406            matcher: LanguageMatcher {
24407                path_suffixes: vec!["tsx".to_string()],
24408                ..LanguageMatcher::default()
24409            },
24410            brackets: BracketPairConfig {
24411                pairs: vec![BracketPair {
24412                    start: "<".into(),
24413                    end: ">".into(),
24414                    close: true,
24415                    ..Default::default()
24416                }],
24417                ..Default::default()
24418            },
24419            linked_edit_characters: HashSet::from_iter(['.']),
24420            ..Default::default()
24421        },
24422        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24423    ));
24424    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24425
24426    // Test typing > does not extend linked pair
24427    cx.set_state("<divˇ<div></div>");
24428    cx.update_editor(|editor, _, cx| {
24429        set_linked_edit_ranges(
24430            (Point::new(0, 1), Point::new(0, 4)),
24431            (Point::new(0, 11), Point::new(0, 14)),
24432            editor,
24433            cx,
24434        );
24435    });
24436    cx.update_editor(|editor, window, cx| {
24437        editor.handle_input(">", window, cx);
24438    });
24439    cx.assert_editor_state("<div>ˇ<div></div>");
24440
24441    // Test typing . do extend linked pair
24442    cx.set_state("<Animatedˇ></Animated>");
24443    cx.update_editor(|editor, _, cx| {
24444        set_linked_edit_ranges(
24445            (Point::new(0, 1), Point::new(0, 9)),
24446            (Point::new(0, 12), Point::new(0, 20)),
24447            editor,
24448            cx,
24449        );
24450    });
24451    cx.update_editor(|editor, window, cx| {
24452        editor.handle_input(".", window, cx);
24453    });
24454    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24455    cx.update_editor(|editor, _, cx| {
24456        set_linked_edit_ranges(
24457            (Point::new(0, 1), Point::new(0, 10)),
24458            (Point::new(0, 13), Point::new(0, 21)),
24459            editor,
24460            cx,
24461        );
24462    });
24463    cx.update_editor(|editor, window, cx| {
24464        editor.handle_input("V", window, cx);
24465    });
24466    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24467}
24468
24469#[gpui::test]
24470async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24471    init_test(cx, |_| {});
24472
24473    let fs = FakeFs::new(cx.executor());
24474    fs.insert_tree(
24475        path!("/root"),
24476        json!({
24477            "a": {
24478                "main.rs": "fn main() {}",
24479            },
24480            "foo": {
24481                "bar": {
24482                    "external_file.rs": "pub mod external {}",
24483                }
24484            }
24485        }),
24486    )
24487    .await;
24488
24489    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24490    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24491    language_registry.add(rust_lang());
24492    let _fake_servers = language_registry.register_fake_lsp(
24493        "Rust",
24494        FakeLspAdapter {
24495            ..FakeLspAdapter::default()
24496        },
24497    );
24498    let (workspace, cx) =
24499        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24500    let worktree_id = workspace.update(cx, |workspace, cx| {
24501        workspace.project().update(cx, |project, cx| {
24502            project.worktrees(cx).next().unwrap().read(cx).id()
24503        })
24504    });
24505
24506    let assert_language_servers_count =
24507        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24508            project.update(cx, |project, cx| {
24509                let current = project
24510                    .lsp_store()
24511                    .read(cx)
24512                    .as_local()
24513                    .unwrap()
24514                    .language_servers
24515                    .len();
24516                assert_eq!(expected, current, "{context}");
24517            });
24518        };
24519
24520    assert_language_servers_count(
24521        0,
24522        "No servers should be running before any file is open",
24523        cx,
24524    );
24525    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24526    let main_editor = workspace
24527        .update_in(cx, |workspace, window, cx| {
24528            workspace.open_path(
24529                (worktree_id, rel_path("main.rs")),
24530                Some(pane.downgrade()),
24531                true,
24532                window,
24533                cx,
24534            )
24535        })
24536        .unwrap()
24537        .await
24538        .downcast::<Editor>()
24539        .unwrap();
24540    pane.update(cx, |pane, cx| {
24541        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24542        open_editor.update(cx, |editor, cx| {
24543            assert_eq!(
24544                editor.display_text(cx),
24545                "fn main() {}",
24546                "Original main.rs text on initial open",
24547            );
24548        });
24549        assert_eq!(open_editor, main_editor);
24550    });
24551    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24552
24553    let external_editor = workspace
24554        .update_in(cx, |workspace, window, cx| {
24555            workspace.open_abs_path(
24556                PathBuf::from("/root/foo/bar/external_file.rs"),
24557                OpenOptions::default(),
24558                window,
24559                cx,
24560            )
24561        })
24562        .await
24563        .expect("opening external file")
24564        .downcast::<Editor>()
24565        .expect("downcasted external file's open element to editor");
24566    pane.update(cx, |pane, cx| {
24567        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24568        open_editor.update(cx, |editor, cx| {
24569            assert_eq!(
24570                editor.display_text(cx),
24571                "pub mod external {}",
24572                "External file is open now",
24573            );
24574        });
24575        assert_eq!(open_editor, external_editor);
24576    });
24577    assert_language_servers_count(
24578        1,
24579        "Second, external, *.rs file should join the existing server",
24580        cx,
24581    );
24582
24583    pane.update_in(cx, |pane, window, cx| {
24584        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24585    })
24586    .await
24587    .unwrap();
24588    pane.update_in(cx, |pane, window, cx| {
24589        pane.navigate_backward(&Default::default(), window, cx);
24590    });
24591    cx.run_until_parked();
24592    pane.update(cx, |pane, cx| {
24593        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24594        open_editor.update(cx, |editor, cx| {
24595            assert_eq!(
24596                editor.display_text(cx),
24597                "pub mod external {}",
24598                "External file is open now",
24599            );
24600        });
24601    });
24602    assert_language_servers_count(
24603        1,
24604        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24605        cx,
24606    );
24607
24608    cx.update(|_, cx| {
24609        workspace::reload(cx);
24610    });
24611    assert_language_servers_count(
24612        1,
24613        "After reloading the worktree with local and external files opened, only one project should be started",
24614        cx,
24615    );
24616}
24617
24618#[gpui::test]
24619async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24620    init_test(cx, |_| {});
24621
24622    let mut cx = EditorTestContext::new(cx).await;
24623    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24624    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24625
24626    // test cursor move to start of each line on tab
24627    // for `if`, `elif`, `else`, `while`, `with` and `for`
24628    cx.set_state(indoc! {"
24629        def main():
24630        ˇ    for item in items:
24631        ˇ        while item.active:
24632        ˇ            if item.value > 10:
24633        ˇ                continue
24634        ˇ            elif item.value < 0:
24635        ˇ                break
24636        ˇ            else:
24637        ˇ                with item.context() as ctx:
24638        ˇ                    yield count
24639        ˇ        else:
24640        ˇ            log('while else')
24641        ˇ    else:
24642        ˇ        log('for else')
24643    "});
24644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24645    cx.assert_editor_state(indoc! {"
24646        def main():
24647            ˇfor item in items:
24648                ˇwhile item.active:
24649                    ˇif item.value > 10:
24650                        ˇcontinue
24651                    ˇelif item.value < 0:
24652                        ˇbreak
24653                    ˇelse:
24654                        ˇwith item.context() as ctx:
24655                            ˇyield count
24656                ˇelse:
24657                    ˇlog('while else')
24658            ˇelse:
24659                ˇlog('for else')
24660    "});
24661    // test relative indent is preserved when tab
24662    // for `if`, `elif`, `else`, `while`, `with` and `for`
24663    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24664    cx.assert_editor_state(indoc! {"
24665        def main():
24666                ˇfor item in items:
24667                    ˇwhile item.active:
24668                        ˇif item.value > 10:
24669                            ˇcontinue
24670                        ˇelif item.value < 0:
24671                            ˇbreak
24672                        ˇelse:
24673                            ˇwith item.context() as ctx:
24674                                ˇyield count
24675                    ˇelse:
24676                        ˇlog('while else')
24677                ˇelse:
24678                    ˇlog('for else')
24679    "});
24680
24681    // test cursor move to start of each line on tab
24682    // for `try`, `except`, `else`, `finally`, `match` and `def`
24683    cx.set_state(indoc! {"
24684        def main():
24685        ˇ    try:
24686        ˇ        fetch()
24687        ˇ    except ValueError:
24688        ˇ        handle_error()
24689        ˇ    else:
24690        ˇ        match value:
24691        ˇ            case _:
24692        ˇ    finally:
24693        ˇ        def status():
24694        ˇ            return 0
24695    "});
24696    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24697    cx.assert_editor_state(indoc! {"
24698        def main():
24699            ˇtry:
24700                ˇfetch()
24701            ˇexcept ValueError:
24702                ˇhandle_error()
24703            ˇelse:
24704                ˇmatch value:
24705                    ˇcase _:
24706            ˇfinally:
24707                ˇdef status():
24708                    ˇreturn 0
24709    "});
24710    // test relative indent is preserved when tab
24711    // for `try`, `except`, `else`, `finally`, `match` and `def`
24712    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24713    cx.assert_editor_state(indoc! {"
24714        def main():
24715                ˇtry:
24716                    ˇfetch()
24717                ˇexcept ValueError:
24718                    ˇhandle_error()
24719                ˇelse:
24720                    ˇmatch value:
24721                        ˇcase _:
24722                ˇfinally:
24723                    ˇdef status():
24724                        ˇreturn 0
24725    "});
24726}
24727
24728#[gpui::test]
24729async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24730    init_test(cx, |_| {});
24731
24732    let mut cx = EditorTestContext::new(cx).await;
24733    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24734    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24735
24736    // test `else` auto outdents when typed inside `if` block
24737    cx.set_state(indoc! {"
24738        def main():
24739            if i == 2:
24740                return
24741                ˇ
24742    "});
24743    cx.update_editor(|editor, window, cx| {
24744        editor.handle_input("else:", window, cx);
24745    });
24746    cx.assert_editor_state(indoc! {"
24747        def main():
24748            if i == 2:
24749                return
24750            else:ˇ
24751    "});
24752
24753    // test `except` auto outdents when typed inside `try` block
24754    cx.set_state(indoc! {"
24755        def main():
24756            try:
24757                i = 2
24758                ˇ
24759    "});
24760    cx.update_editor(|editor, window, cx| {
24761        editor.handle_input("except:", window, cx);
24762    });
24763    cx.assert_editor_state(indoc! {"
24764        def main():
24765            try:
24766                i = 2
24767            except:ˇ
24768    "});
24769
24770    // test `else` auto outdents when typed inside `except` block
24771    cx.set_state(indoc! {"
24772        def main():
24773            try:
24774                i = 2
24775            except:
24776                j = 2
24777                ˇ
24778    "});
24779    cx.update_editor(|editor, window, cx| {
24780        editor.handle_input("else:", window, cx);
24781    });
24782    cx.assert_editor_state(indoc! {"
24783        def main():
24784            try:
24785                i = 2
24786            except:
24787                j = 2
24788            else:ˇ
24789    "});
24790
24791    // test `finally` auto outdents when typed inside `else` block
24792    cx.set_state(indoc! {"
24793        def main():
24794            try:
24795                i = 2
24796            except:
24797                j = 2
24798            else:
24799                k = 2
24800                ˇ
24801    "});
24802    cx.update_editor(|editor, window, cx| {
24803        editor.handle_input("finally:", window, cx);
24804    });
24805    cx.assert_editor_state(indoc! {"
24806        def main():
24807            try:
24808                i = 2
24809            except:
24810                j = 2
24811            else:
24812                k = 2
24813            finally:ˇ
24814    "});
24815
24816    // test `else` does not outdents when typed inside `except` block right after for block
24817    cx.set_state(indoc! {"
24818        def main():
24819            try:
24820                i = 2
24821            except:
24822                for i in range(n):
24823                    pass
24824                ˇ
24825    "});
24826    cx.update_editor(|editor, window, cx| {
24827        editor.handle_input("else:", window, cx);
24828    });
24829    cx.assert_editor_state(indoc! {"
24830        def main():
24831            try:
24832                i = 2
24833            except:
24834                for i in range(n):
24835                    pass
24836                else:ˇ
24837    "});
24838
24839    // test `finally` auto outdents when typed inside `else` block right after for block
24840    cx.set_state(indoc! {"
24841        def main():
24842            try:
24843                i = 2
24844            except:
24845                j = 2
24846            else:
24847                for i in range(n):
24848                    pass
24849                ˇ
24850    "});
24851    cx.update_editor(|editor, window, cx| {
24852        editor.handle_input("finally:", window, cx);
24853    });
24854    cx.assert_editor_state(indoc! {"
24855        def main():
24856            try:
24857                i = 2
24858            except:
24859                j = 2
24860            else:
24861                for i in range(n):
24862                    pass
24863            finally:ˇ
24864    "});
24865
24866    // test `except` outdents to inner "try" block
24867    cx.set_state(indoc! {"
24868        def main():
24869            try:
24870                i = 2
24871                if i == 2:
24872                    try:
24873                        i = 3
24874                        ˇ
24875    "});
24876    cx.update_editor(|editor, window, cx| {
24877        editor.handle_input("except:", window, cx);
24878    });
24879    cx.assert_editor_state(indoc! {"
24880        def main():
24881            try:
24882                i = 2
24883                if i == 2:
24884                    try:
24885                        i = 3
24886                    except:ˇ
24887    "});
24888
24889    // test `except` outdents to outer "try" block
24890    cx.set_state(indoc! {"
24891        def main():
24892            try:
24893                i = 2
24894                if i == 2:
24895                    try:
24896                        i = 3
24897                ˇ
24898    "});
24899    cx.update_editor(|editor, window, cx| {
24900        editor.handle_input("except:", window, cx);
24901    });
24902    cx.assert_editor_state(indoc! {"
24903        def main():
24904            try:
24905                i = 2
24906                if i == 2:
24907                    try:
24908                        i = 3
24909            except:ˇ
24910    "});
24911
24912    // test `else` stays at correct indent when typed after `for` block
24913    cx.set_state(indoc! {"
24914        def main():
24915            for i in range(10):
24916                if i == 3:
24917                    break
24918            ˇ
24919    "});
24920    cx.update_editor(|editor, window, cx| {
24921        editor.handle_input("else:", window, cx);
24922    });
24923    cx.assert_editor_state(indoc! {"
24924        def main():
24925            for i in range(10):
24926                if i == 3:
24927                    break
24928            else:ˇ
24929    "});
24930
24931    // test does not outdent on typing after line with square brackets
24932    cx.set_state(indoc! {"
24933        def f() -> list[str]:
24934            ˇ
24935    "});
24936    cx.update_editor(|editor, window, cx| {
24937        editor.handle_input("a", window, cx);
24938    });
24939    cx.assert_editor_state(indoc! {"
24940        def f() -> list[str]:
2494124942    "});
24943
24944    // test does not outdent on typing : after case keyword
24945    cx.set_state(indoc! {"
24946        match 1:
24947            caseˇ
24948    "});
24949    cx.update_editor(|editor, window, cx| {
24950        editor.handle_input(":", window, cx);
24951    });
24952    cx.assert_editor_state(indoc! {"
24953        match 1:
24954            case:ˇ
24955    "});
24956}
24957
24958#[gpui::test]
24959async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24960    init_test(cx, |_| {});
24961    update_test_language_settings(cx, |settings| {
24962        settings.defaults.extend_comment_on_newline = Some(false);
24963    });
24964    let mut cx = EditorTestContext::new(cx).await;
24965    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24966    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24967
24968    // test correct indent after newline on comment
24969    cx.set_state(indoc! {"
24970        # COMMENT:ˇ
24971    "});
24972    cx.update_editor(|editor, window, cx| {
24973        editor.newline(&Newline, window, cx);
24974    });
24975    cx.assert_editor_state(indoc! {"
24976        # COMMENT:
24977        ˇ
24978    "});
24979
24980    // test correct indent after newline in brackets
24981    cx.set_state(indoc! {"
24982        {ˇ}
24983    "});
24984    cx.update_editor(|editor, window, cx| {
24985        editor.newline(&Newline, window, cx);
24986    });
24987    cx.run_until_parked();
24988    cx.assert_editor_state(indoc! {"
24989        {
24990            ˇ
24991        }
24992    "});
24993
24994    cx.set_state(indoc! {"
24995        (ˇ)
24996    "});
24997    cx.update_editor(|editor, window, cx| {
24998        editor.newline(&Newline, window, cx);
24999    });
25000    cx.run_until_parked();
25001    cx.assert_editor_state(indoc! {"
25002        (
25003            ˇ
25004        )
25005    "});
25006
25007    // do not indent after empty lists or dictionaries
25008    cx.set_state(indoc! {"
25009        a = []ˇ
25010    "});
25011    cx.update_editor(|editor, window, cx| {
25012        editor.newline(&Newline, window, cx);
25013    });
25014    cx.run_until_parked();
25015    cx.assert_editor_state(indoc! {"
25016        a = []
25017        ˇ
25018    "});
25019}
25020
25021#[gpui::test]
25022async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25023    init_test(cx, |_| {});
25024
25025    let mut cx = EditorTestContext::new(cx).await;
25026    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25027    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25028
25029    // test cursor move to start of each line on tab
25030    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25031    cx.set_state(indoc! {"
25032        function main() {
25033        ˇ    for item in $items; do
25034        ˇ        while [ -n \"$item\" ]; do
25035        ˇ            if [ \"$value\" -gt 10 ]; then
25036        ˇ                continue
25037        ˇ            elif [ \"$value\" -lt 0 ]; then
25038        ˇ                break
25039        ˇ            else
25040        ˇ                echo \"$item\"
25041        ˇ            fi
25042        ˇ        done
25043        ˇ    done
25044        ˇ}
25045    "});
25046    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25047    cx.assert_editor_state(indoc! {"
25048        function main() {
25049            ˇfor item in $items; do
25050                ˇwhile [ -n \"$item\" ]; do
25051                    ˇif [ \"$value\" -gt 10 ]; then
25052                        ˇcontinue
25053                    ˇelif [ \"$value\" -lt 0 ]; then
25054                        ˇbreak
25055                    ˇelse
25056                        ˇecho \"$item\"
25057                    ˇfi
25058                ˇdone
25059            ˇdone
25060        ˇ}
25061    "});
25062    // test relative indent is preserved when tab
25063    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25064    cx.assert_editor_state(indoc! {"
25065        function main() {
25066                ˇfor item in $items; do
25067                    ˇwhile [ -n \"$item\" ]; do
25068                        ˇif [ \"$value\" -gt 10 ]; then
25069                            ˇcontinue
25070                        ˇelif [ \"$value\" -lt 0 ]; then
25071                            ˇbreak
25072                        ˇelse
25073                            ˇecho \"$item\"
25074                        ˇfi
25075                    ˇdone
25076                ˇdone
25077            ˇ}
25078    "});
25079
25080    // test cursor move to start of each line on tab
25081    // for `case` statement with patterns
25082    cx.set_state(indoc! {"
25083        function handle() {
25084        ˇ    case \"$1\" in
25085        ˇ        start)
25086        ˇ            echo \"a\"
25087        ˇ            ;;
25088        ˇ        stop)
25089        ˇ            echo \"b\"
25090        ˇ            ;;
25091        ˇ        *)
25092        ˇ            echo \"c\"
25093        ˇ            ;;
25094        ˇ    esac
25095        ˇ}
25096    "});
25097    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25098    cx.assert_editor_state(indoc! {"
25099        function handle() {
25100            ˇcase \"$1\" in
25101                ˇstart)
25102                    ˇecho \"a\"
25103                    ˇ;;
25104                ˇstop)
25105                    ˇecho \"b\"
25106                    ˇ;;
25107                ˇ*)
25108                    ˇecho \"c\"
25109                    ˇ;;
25110            ˇesac
25111        ˇ}
25112    "});
25113}
25114
25115#[gpui::test]
25116async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25117    init_test(cx, |_| {});
25118
25119    let mut cx = EditorTestContext::new(cx).await;
25120    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25121    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25122
25123    // test indents on comment insert
25124    cx.set_state(indoc! {"
25125        function main() {
25126        ˇ    for item in $items; do
25127        ˇ        while [ -n \"$item\" ]; do
25128        ˇ            if [ \"$value\" -gt 10 ]; then
25129        ˇ                continue
25130        ˇ            elif [ \"$value\" -lt 0 ]; then
25131        ˇ                break
25132        ˇ            else
25133        ˇ                echo \"$item\"
25134        ˇ            fi
25135        ˇ        done
25136        ˇ    done
25137        ˇ}
25138    "});
25139    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25140    cx.assert_editor_state(indoc! {"
25141        function main() {
25142        #ˇ    for item in $items; do
25143        #ˇ        while [ -n \"$item\" ]; do
25144        #ˇ            if [ \"$value\" -gt 10 ]; then
25145        #ˇ                continue
25146        #ˇ            elif [ \"$value\" -lt 0 ]; then
25147        #ˇ                break
25148        #ˇ            else
25149        #ˇ                echo \"$item\"
25150        #ˇ            fi
25151        #ˇ        done
25152        #ˇ    done
25153        #ˇ}
25154    "});
25155}
25156
25157#[gpui::test]
25158async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25159    init_test(cx, |_| {});
25160
25161    let mut cx = EditorTestContext::new(cx).await;
25162    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25163    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25164
25165    // test `else` auto outdents when typed inside `if` block
25166    cx.set_state(indoc! {"
25167        if [ \"$1\" = \"test\" ]; then
25168            echo \"foo bar\"
25169            ˇ
25170    "});
25171    cx.update_editor(|editor, window, cx| {
25172        editor.handle_input("else", window, cx);
25173    });
25174    cx.assert_editor_state(indoc! {"
25175        if [ \"$1\" = \"test\" ]; then
25176            echo \"foo bar\"
25177        elseˇ
25178    "});
25179
25180    // test `elif` auto outdents when typed inside `if` block
25181    cx.set_state(indoc! {"
25182        if [ \"$1\" = \"test\" ]; then
25183            echo \"foo bar\"
25184            ˇ
25185    "});
25186    cx.update_editor(|editor, window, cx| {
25187        editor.handle_input("elif", window, cx);
25188    });
25189    cx.assert_editor_state(indoc! {"
25190        if [ \"$1\" = \"test\" ]; then
25191            echo \"foo bar\"
25192        elifˇ
25193    "});
25194
25195    // test `fi` auto outdents when typed inside `else` block
25196    cx.set_state(indoc! {"
25197        if [ \"$1\" = \"test\" ]; then
25198            echo \"foo bar\"
25199        else
25200            echo \"bar baz\"
25201            ˇ
25202    "});
25203    cx.update_editor(|editor, window, cx| {
25204        editor.handle_input("fi", window, cx);
25205    });
25206    cx.assert_editor_state(indoc! {"
25207        if [ \"$1\" = \"test\" ]; then
25208            echo \"foo bar\"
25209        else
25210            echo \"bar baz\"
25211        fiˇ
25212    "});
25213
25214    // test `done` auto outdents when typed inside `while` block
25215    cx.set_state(indoc! {"
25216        while read line; do
25217            echo \"$line\"
25218            ˇ
25219    "});
25220    cx.update_editor(|editor, window, cx| {
25221        editor.handle_input("done", window, cx);
25222    });
25223    cx.assert_editor_state(indoc! {"
25224        while read line; do
25225            echo \"$line\"
25226        doneˇ
25227    "});
25228
25229    // test `done` auto outdents when typed inside `for` block
25230    cx.set_state(indoc! {"
25231        for file in *.txt; do
25232            cat \"$file\"
25233            ˇ
25234    "});
25235    cx.update_editor(|editor, window, cx| {
25236        editor.handle_input("done", window, cx);
25237    });
25238    cx.assert_editor_state(indoc! {"
25239        for file in *.txt; do
25240            cat \"$file\"
25241        doneˇ
25242    "});
25243
25244    // test `esac` auto outdents when typed inside `case` block
25245    cx.set_state(indoc! {"
25246        case \"$1\" in
25247            start)
25248                echo \"foo bar\"
25249                ;;
25250            stop)
25251                echo \"bar baz\"
25252                ;;
25253            ˇ
25254    "});
25255    cx.update_editor(|editor, window, cx| {
25256        editor.handle_input("esac", window, cx);
25257    });
25258    cx.assert_editor_state(indoc! {"
25259        case \"$1\" in
25260            start)
25261                echo \"foo bar\"
25262                ;;
25263            stop)
25264                echo \"bar baz\"
25265                ;;
25266        esacˇ
25267    "});
25268
25269    // test `*)` auto outdents when typed inside `case` block
25270    cx.set_state(indoc! {"
25271        case \"$1\" in
25272            start)
25273                echo \"foo bar\"
25274                ;;
25275                ˇ
25276    "});
25277    cx.update_editor(|editor, window, cx| {
25278        editor.handle_input("*)", window, cx);
25279    });
25280    cx.assert_editor_state(indoc! {"
25281        case \"$1\" in
25282            start)
25283                echo \"foo bar\"
25284                ;;
25285            *)ˇ
25286    "});
25287
25288    // test `fi` outdents to correct level with nested if blocks
25289    cx.set_state(indoc! {"
25290        if [ \"$1\" = \"test\" ]; then
25291            echo \"outer if\"
25292            if [ \"$2\" = \"debug\" ]; then
25293                echo \"inner if\"
25294                ˇ
25295    "});
25296    cx.update_editor(|editor, window, cx| {
25297        editor.handle_input("fi", window, cx);
25298    });
25299    cx.assert_editor_state(indoc! {"
25300        if [ \"$1\" = \"test\" ]; then
25301            echo \"outer if\"
25302            if [ \"$2\" = \"debug\" ]; then
25303                echo \"inner if\"
25304            fiˇ
25305    "});
25306}
25307
25308#[gpui::test]
25309async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25310    init_test(cx, |_| {});
25311    update_test_language_settings(cx, |settings| {
25312        settings.defaults.extend_comment_on_newline = Some(false);
25313    });
25314    let mut cx = EditorTestContext::new(cx).await;
25315    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25316    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25317
25318    // test correct indent after newline on comment
25319    cx.set_state(indoc! {"
25320        # COMMENT:ˇ
25321    "});
25322    cx.update_editor(|editor, window, cx| {
25323        editor.newline(&Newline, window, cx);
25324    });
25325    cx.assert_editor_state(indoc! {"
25326        # COMMENT:
25327        ˇ
25328    "});
25329
25330    // test correct indent after newline after `then`
25331    cx.set_state(indoc! {"
25332
25333        if [ \"$1\" = \"test\" ]; thenˇ
25334    "});
25335    cx.update_editor(|editor, window, cx| {
25336        editor.newline(&Newline, window, cx);
25337    });
25338    cx.run_until_parked();
25339    cx.assert_editor_state(indoc! {"
25340
25341        if [ \"$1\" = \"test\" ]; then
25342            ˇ
25343    "});
25344
25345    // test correct indent after newline after `else`
25346    cx.set_state(indoc! {"
25347        if [ \"$1\" = \"test\" ]; then
25348        elseˇ
25349    "});
25350    cx.update_editor(|editor, window, cx| {
25351        editor.newline(&Newline, window, cx);
25352    });
25353    cx.run_until_parked();
25354    cx.assert_editor_state(indoc! {"
25355        if [ \"$1\" = \"test\" ]; then
25356        else
25357            ˇ
25358    "});
25359
25360    // test correct indent after newline after `elif`
25361    cx.set_state(indoc! {"
25362        if [ \"$1\" = \"test\" ]; then
25363        elifˇ
25364    "});
25365    cx.update_editor(|editor, window, cx| {
25366        editor.newline(&Newline, window, cx);
25367    });
25368    cx.run_until_parked();
25369    cx.assert_editor_state(indoc! {"
25370        if [ \"$1\" = \"test\" ]; then
25371        elif
25372            ˇ
25373    "});
25374
25375    // test correct indent after newline after `do`
25376    cx.set_state(indoc! {"
25377        for file in *.txt; doˇ
25378    "});
25379    cx.update_editor(|editor, window, cx| {
25380        editor.newline(&Newline, window, cx);
25381    });
25382    cx.run_until_parked();
25383    cx.assert_editor_state(indoc! {"
25384        for file in *.txt; do
25385            ˇ
25386    "});
25387
25388    // test correct indent after newline after case pattern
25389    cx.set_state(indoc! {"
25390        case \"$1\" in
25391            start)ˇ
25392    "});
25393    cx.update_editor(|editor, window, cx| {
25394        editor.newline(&Newline, window, cx);
25395    });
25396    cx.run_until_parked();
25397    cx.assert_editor_state(indoc! {"
25398        case \"$1\" in
25399            start)
25400                ˇ
25401    "});
25402
25403    // test correct indent after newline after case pattern
25404    cx.set_state(indoc! {"
25405        case \"$1\" in
25406            start)
25407                ;;
25408            *)ˇ
25409    "});
25410    cx.update_editor(|editor, window, cx| {
25411        editor.newline(&Newline, window, cx);
25412    });
25413    cx.run_until_parked();
25414    cx.assert_editor_state(indoc! {"
25415        case \"$1\" in
25416            start)
25417                ;;
25418            *)
25419                ˇ
25420    "});
25421
25422    // test correct indent after newline after function opening brace
25423    cx.set_state(indoc! {"
25424        function test() {ˇ}
25425    "});
25426    cx.update_editor(|editor, window, cx| {
25427        editor.newline(&Newline, window, cx);
25428    });
25429    cx.run_until_parked();
25430    cx.assert_editor_state(indoc! {"
25431        function test() {
25432            ˇ
25433        }
25434    "});
25435
25436    // test no extra indent after semicolon on same line
25437    cx.set_state(indoc! {"
25438        echo \"test\"25439    "});
25440    cx.update_editor(|editor, window, cx| {
25441        editor.newline(&Newline, window, cx);
25442    });
25443    cx.run_until_parked();
25444    cx.assert_editor_state(indoc! {"
25445        echo \"test\";
25446        ˇ
25447    "});
25448}
25449
25450fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25451    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25452    point..point
25453}
25454
25455#[track_caller]
25456fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25457    let (text, ranges) = marked_text_ranges(marked_text, true);
25458    assert_eq!(editor.text(cx), text);
25459    assert_eq!(
25460        editor.selections.ranges(&editor.display_snapshot(cx)),
25461        ranges,
25462        "Assert selections are {}",
25463        marked_text
25464    );
25465}
25466
25467pub fn handle_signature_help_request(
25468    cx: &mut EditorLspTestContext,
25469    mocked_response: lsp::SignatureHelp,
25470) -> impl Future<Output = ()> + use<> {
25471    let mut request =
25472        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25473            let mocked_response = mocked_response.clone();
25474            async move { Ok(Some(mocked_response)) }
25475        });
25476
25477    async move {
25478        request.next().await;
25479    }
25480}
25481
25482#[track_caller]
25483pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25484    cx.update_editor(|editor, _, _| {
25485        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25486            let entries = menu.entries.borrow();
25487            let entries = entries
25488                .iter()
25489                .map(|entry| entry.string.as_str())
25490                .collect::<Vec<_>>();
25491            assert_eq!(entries, expected);
25492        } else {
25493            panic!("Expected completions menu");
25494        }
25495    });
25496}
25497
25498/// Handle completion request passing a marked string specifying where the completion
25499/// should be triggered from using '|' character, what range should be replaced, and what completions
25500/// should be returned using '<' and '>' to delimit the range.
25501///
25502/// Also see `handle_completion_request_with_insert_and_replace`.
25503#[track_caller]
25504pub fn handle_completion_request(
25505    marked_string: &str,
25506    completions: Vec<&'static str>,
25507    is_incomplete: bool,
25508    counter: Arc<AtomicUsize>,
25509    cx: &mut EditorLspTestContext,
25510) -> impl Future<Output = ()> {
25511    let complete_from_marker: TextRangeMarker = '|'.into();
25512    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25513    let (_, mut marked_ranges) = marked_text_ranges_by(
25514        marked_string,
25515        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25516    );
25517
25518    let complete_from_position =
25519        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25520    let replace_range =
25521        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25522
25523    let mut request =
25524        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25525            let completions = completions.clone();
25526            counter.fetch_add(1, atomic::Ordering::Release);
25527            async move {
25528                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25529                assert_eq!(
25530                    params.text_document_position.position,
25531                    complete_from_position
25532                );
25533                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25534                    is_incomplete,
25535                    item_defaults: None,
25536                    items: completions
25537                        .iter()
25538                        .map(|completion_text| lsp::CompletionItem {
25539                            label: completion_text.to_string(),
25540                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25541                                range: replace_range,
25542                                new_text: completion_text.to_string(),
25543                            })),
25544                            ..Default::default()
25545                        })
25546                        .collect(),
25547                })))
25548            }
25549        });
25550
25551    async move {
25552        request.next().await;
25553    }
25554}
25555
25556/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25557/// given instead, which also contains an `insert` range.
25558///
25559/// This function uses markers to define ranges:
25560/// - `|` marks the cursor position
25561/// - `<>` marks the replace range
25562/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25563pub fn handle_completion_request_with_insert_and_replace(
25564    cx: &mut EditorLspTestContext,
25565    marked_string: &str,
25566    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25567    counter: Arc<AtomicUsize>,
25568) -> impl Future<Output = ()> {
25569    let complete_from_marker: TextRangeMarker = '|'.into();
25570    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25571    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25572
25573    let (_, mut marked_ranges) = marked_text_ranges_by(
25574        marked_string,
25575        vec![
25576            complete_from_marker.clone(),
25577            replace_range_marker.clone(),
25578            insert_range_marker.clone(),
25579        ],
25580    );
25581
25582    let complete_from_position =
25583        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25584    let replace_range =
25585        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25586
25587    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25588        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25589        _ => lsp::Range {
25590            start: replace_range.start,
25591            end: complete_from_position,
25592        },
25593    };
25594
25595    let mut request =
25596        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25597            let completions = completions.clone();
25598            counter.fetch_add(1, atomic::Ordering::Release);
25599            async move {
25600                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25601                assert_eq!(
25602                    params.text_document_position.position, complete_from_position,
25603                    "marker `|` position doesn't match",
25604                );
25605                Ok(Some(lsp::CompletionResponse::Array(
25606                    completions
25607                        .iter()
25608                        .map(|(label, new_text)| lsp::CompletionItem {
25609                            label: label.to_string(),
25610                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25611                                lsp::InsertReplaceEdit {
25612                                    insert: insert_range,
25613                                    replace: replace_range,
25614                                    new_text: new_text.to_string(),
25615                                },
25616                            )),
25617                            ..Default::default()
25618                        })
25619                        .collect(),
25620                )))
25621            }
25622        });
25623
25624    async move {
25625        request.next().await;
25626    }
25627}
25628
25629fn handle_resolve_completion_request(
25630    cx: &mut EditorLspTestContext,
25631    edits: Option<Vec<(&'static str, &'static str)>>,
25632) -> impl Future<Output = ()> {
25633    let edits = edits.map(|edits| {
25634        edits
25635            .iter()
25636            .map(|(marked_string, new_text)| {
25637                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25638                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25639                lsp::TextEdit::new(replace_range, new_text.to_string())
25640            })
25641            .collect::<Vec<_>>()
25642    });
25643
25644    let mut request =
25645        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25646            let edits = edits.clone();
25647            async move {
25648                Ok(lsp::CompletionItem {
25649                    additional_text_edits: edits,
25650                    ..Default::default()
25651                })
25652            }
25653        });
25654
25655    async move {
25656        request.next().await;
25657    }
25658}
25659
25660pub(crate) fn update_test_language_settings(
25661    cx: &mut TestAppContext,
25662    f: impl Fn(&mut AllLanguageSettingsContent),
25663) {
25664    cx.update(|cx| {
25665        SettingsStore::update_global(cx, |store, cx| {
25666            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25667        });
25668    });
25669}
25670
25671pub(crate) fn update_test_project_settings(
25672    cx: &mut TestAppContext,
25673    f: impl Fn(&mut ProjectSettingsContent),
25674) {
25675    cx.update(|cx| {
25676        SettingsStore::update_global(cx, |store, cx| {
25677            store.update_user_settings(cx, |settings| f(&mut settings.project));
25678        });
25679    });
25680}
25681
25682pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25683    cx.update(|cx| {
25684        assets::Assets.load_test_fonts(cx);
25685        let store = SettingsStore::test(cx);
25686        cx.set_global(store);
25687        theme::init(theme::LoadThemes::JustBase, cx);
25688        release_channel::init(SemanticVersion::default(), cx);
25689        client::init_settings(cx);
25690        language::init(cx);
25691        Project::init_settings(cx);
25692        workspace::init_settings(cx);
25693        crate::init(cx);
25694    });
25695    zlog::init_test();
25696    update_test_language_settings(cx, f);
25697}
25698
25699#[track_caller]
25700fn assert_hunk_revert(
25701    not_reverted_text_with_selections: &str,
25702    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25703    expected_reverted_text_with_selections: &str,
25704    base_text: &str,
25705    cx: &mut EditorLspTestContext,
25706) {
25707    cx.set_state(not_reverted_text_with_selections);
25708    cx.set_head_text(base_text);
25709    cx.executor().run_until_parked();
25710
25711    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25712        let snapshot = editor.snapshot(window, cx);
25713        let reverted_hunk_statuses = snapshot
25714            .buffer_snapshot()
25715            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25716            .map(|hunk| hunk.status().kind)
25717            .collect::<Vec<_>>();
25718
25719        editor.git_restore(&Default::default(), window, cx);
25720        reverted_hunk_statuses
25721    });
25722    cx.executor().run_until_parked();
25723    cx.assert_editor_state(expected_reverted_text_with_selections);
25724    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25725}
25726
25727#[gpui::test(iterations = 10)]
25728async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25729    init_test(cx, |_| {});
25730
25731    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25732    let counter = diagnostic_requests.clone();
25733
25734    let fs = FakeFs::new(cx.executor());
25735    fs.insert_tree(
25736        path!("/a"),
25737        json!({
25738            "first.rs": "fn main() { let a = 5; }",
25739            "second.rs": "// Test file",
25740        }),
25741    )
25742    .await;
25743
25744    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25745    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25746    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25747
25748    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25749    language_registry.add(rust_lang());
25750    let mut fake_servers = language_registry.register_fake_lsp(
25751        "Rust",
25752        FakeLspAdapter {
25753            capabilities: lsp::ServerCapabilities {
25754                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25755                    lsp::DiagnosticOptions {
25756                        identifier: None,
25757                        inter_file_dependencies: true,
25758                        workspace_diagnostics: true,
25759                        work_done_progress_options: Default::default(),
25760                    },
25761                )),
25762                ..Default::default()
25763            },
25764            ..Default::default()
25765        },
25766    );
25767
25768    let editor = workspace
25769        .update(cx, |workspace, window, cx| {
25770            workspace.open_abs_path(
25771                PathBuf::from(path!("/a/first.rs")),
25772                OpenOptions::default(),
25773                window,
25774                cx,
25775            )
25776        })
25777        .unwrap()
25778        .await
25779        .unwrap()
25780        .downcast::<Editor>()
25781        .unwrap();
25782    let fake_server = fake_servers.next().await.unwrap();
25783    let server_id = fake_server.server.server_id();
25784    let mut first_request = fake_server
25785        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25786            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25787            let result_id = Some(new_result_id.to_string());
25788            assert_eq!(
25789                params.text_document.uri,
25790                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25791            );
25792            async move {
25793                Ok(lsp::DocumentDiagnosticReportResult::Report(
25794                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25795                        related_documents: None,
25796                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25797                            items: Vec::new(),
25798                            result_id,
25799                        },
25800                    }),
25801                ))
25802            }
25803        });
25804
25805    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25806        project.update(cx, |project, cx| {
25807            let buffer_id = editor
25808                .read(cx)
25809                .buffer()
25810                .read(cx)
25811                .as_singleton()
25812                .expect("created a singleton buffer")
25813                .read(cx)
25814                .remote_id();
25815            let buffer_result_id = project
25816                .lsp_store()
25817                .read(cx)
25818                .result_id(server_id, buffer_id, cx);
25819            assert_eq!(expected, buffer_result_id);
25820        });
25821    };
25822
25823    ensure_result_id(None, cx);
25824    cx.executor().advance_clock(Duration::from_millis(60));
25825    cx.executor().run_until_parked();
25826    assert_eq!(
25827        diagnostic_requests.load(atomic::Ordering::Acquire),
25828        1,
25829        "Opening file should trigger diagnostic request"
25830    );
25831    first_request
25832        .next()
25833        .await
25834        .expect("should have sent the first diagnostics pull request");
25835    ensure_result_id(Some("1".to_string()), cx);
25836
25837    // Editing should trigger diagnostics
25838    editor.update_in(cx, |editor, window, cx| {
25839        editor.handle_input("2", window, cx)
25840    });
25841    cx.executor().advance_clock(Duration::from_millis(60));
25842    cx.executor().run_until_parked();
25843    assert_eq!(
25844        diagnostic_requests.load(atomic::Ordering::Acquire),
25845        2,
25846        "Editing should trigger diagnostic request"
25847    );
25848    ensure_result_id(Some("2".to_string()), cx);
25849
25850    // Moving cursor should not trigger diagnostic request
25851    editor.update_in(cx, |editor, window, cx| {
25852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25853            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25854        });
25855    });
25856    cx.executor().advance_clock(Duration::from_millis(60));
25857    cx.executor().run_until_parked();
25858    assert_eq!(
25859        diagnostic_requests.load(atomic::Ordering::Acquire),
25860        2,
25861        "Cursor movement should not trigger diagnostic request"
25862    );
25863    ensure_result_id(Some("2".to_string()), cx);
25864    // Multiple rapid edits should be debounced
25865    for _ in 0..5 {
25866        editor.update_in(cx, |editor, window, cx| {
25867            editor.handle_input("x", window, cx)
25868        });
25869    }
25870    cx.executor().advance_clock(Duration::from_millis(60));
25871    cx.executor().run_until_parked();
25872
25873    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25874    assert!(
25875        final_requests <= 4,
25876        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25877    );
25878    ensure_result_id(Some(final_requests.to_string()), cx);
25879}
25880
25881#[gpui::test]
25882async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25883    // Regression test for issue #11671
25884    // Previously, adding a cursor after moving multiple cursors would reset
25885    // the cursor count instead of adding to the existing cursors.
25886    init_test(cx, |_| {});
25887    let mut cx = EditorTestContext::new(cx).await;
25888
25889    // Create a simple buffer with cursor at start
25890    cx.set_state(indoc! {"
25891        ˇaaaa
25892        bbbb
25893        cccc
25894        dddd
25895        eeee
25896        ffff
25897        gggg
25898        hhhh"});
25899
25900    // Add 2 cursors below (so we have 3 total)
25901    cx.update_editor(|editor, window, cx| {
25902        editor.add_selection_below(&Default::default(), window, cx);
25903        editor.add_selection_below(&Default::default(), window, cx);
25904    });
25905
25906    // Verify we have 3 cursors
25907    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25908    assert_eq!(
25909        initial_count, 3,
25910        "Should have 3 cursors after adding 2 below"
25911    );
25912
25913    // Move down one line
25914    cx.update_editor(|editor, window, cx| {
25915        editor.move_down(&MoveDown, window, cx);
25916    });
25917
25918    // Add another cursor below
25919    cx.update_editor(|editor, window, cx| {
25920        editor.add_selection_below(&Default::default(), window, cx);
25921    });
25922
25923    // Should now have 4 cursors (3 original + 1 new)
25924    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25925    assert_eq!(
25926        final_count, 4,
25927        "Should have 4 cursors after moving and adding another"
25928    );
25929}
25930
25931#[gpui::test]
25932async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25933    init_test(cx, |_| {});
25934
25935    let mut cx = EditorTestContext::new(cx).await;
25936
25937    cx.set_state(indoc!(
25938        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25939           Second line here"#
25940    ));
25941
25942    cx.update_editor(|editor, window, cx| {
25943        // Enable soft wrapping with a narrow width to force soft wrapping and
25944        // confirm that more than 2 rows are being displayed.
25945        editor.set_wrap_width(Some(100.0.into()), cx);
25946        assert!(editor.display_text(cx).lines().count() > 2);
25947
25948        editor.add_selection_below(
25949            &AddSelectionBelow {
25950                skip_soft_wrap: true,
25951            },
25952            window,
25953            cx,
25954        );
25955
25956        assert_eq!(
25957            editor.selections.display_ranges(cx),
25958            &[
25959                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25960                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25961            ]
25962        );
25963
25964        editor.add_selection_above(
25965            &AddSelectionAbove {
25966                skip_soft_wrap: true,
25967            },
25968            window,
25969            cx,
25970        );
25971
25972        assert_eq!(
25973            editor.selections.display_ranges(cx),
25974            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25975        );
25976
25977        editor.add_selection_below(
25978            &AddSelectionBelow {
25979                skip_soft_wrap: false,
25980            },
25981            window,
25982            cx,
25983        );
25984
25985        assert_eq!(
25986            editor.selections.display_ranges(cx),
25987            &[
25988                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25989                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25990            ]
25991        );
25992
25993        editor.add_selection_above(
25994            &AddSelectionAbove {
25995                skip_soft_wrap: false,
25996            },
25997            window,
25998            cx,
25999        );
26000
26001        assert_eq!(
26002            editor.selections.display_ranges(cx),
26003            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26004        );
26005    });
26006}
26007
26008#[gpui::test(iterations = 10)]
26009async fn test_document_colors(cx: &mut TestAppContext) {
26010    let expected_color = Rgba {
26011        r: 0.33,
26012        g: 0.33,
26013        b: 0.33,
26014        a: 0.33,
26015    };
26016
26017    init_test(cx, |_| {});
26018
26019    let fs = FakeFs::new(cx.executor());
26020    fs.insert_tree(
26021        path!("/a"),
26022        json!({
26023            "first.rs": "fn main() { let a = 5; }",
26024        }),
26025    )
26026    .await;
26027
26028    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26029    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26030    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26031
26032    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26033    language_registry.add(rust_lang());
26034    let mut fake_servers = language_registry.register_fake_lsp(
26035        "Rust",
26036        FakeLspAdapter {
26037            capabilities: lsp::ServerCapabilities {
26038                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26039                ..lsp::ServerCapabilities::default()
26040            },
26041            name: "rust-analyzer",
26042            ..FakeLspAdapter::default()
26043        },
26044    );
26045    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26046        "Rust",
26047        FakeLspAdapter {
26048            capabilities: lsp::ServerCapabilities {
26049                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26050                ..lsp::ServerCapabilities::default()
26051            },
26052            name: "not-rust-analyzer",
26053            ..FakeLspAdapter::default()
26054        },
26055    );
26056
26057    let editor = workspace
26058        .update(cx, |workspace, window, cx| {
26059            workspace.open_abs_path(
26060                PathBuf::from(path!("/a/first.rs")),
26061                OpenOptions::default(),
26062                window,
26063                cx,
26064            )
26065        })
26066        .unwrap()
26067        .await
26068        .unwrap()
26069        .downcast::<Editor>()
26070        .unwrap();
26071    let fake_language_server = fake_servers.next().await.unwrap();
26072    let fake_language_server_without_capabilities =
26073        fake_servers_without_capabilities.next().await.unwrap();
26074    let requests_made = Arc::new(AtomicUsize::new(0));
26075    let closure_requests_made = Arc::clone(&requests_made);
26076    let mut color_request_handle = fake_language_server
26077        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26078            let requests_made = Arc::clone(&closure_requests_made);
26079            async move {
26080                assert_eq!(
26081                    params.text_document.uri,
26082                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26083                );
26084                requests_made.fetch_add(1, atomic::Ordering::Release);
26085                Ok(vec![
26086                    lsp::ColorInformation {
26087                        range: lsp::Range {
26088                            start: lsp::Position {
26089                                line: 0,
26090                                character: 0,
26091                            },
26092                            end: lsp::Position {
26093                                line: 0,
26094                                character: 1,
26095                            },
26096                        },
26097                        color: lsp::Color {
26098                            red: 0.33,
26099                            green: 0.33,
26100                            blue: 0.33,
26101                            alpha: 0.33,
26102                        },
26103                    },
26104                    lsp::ColorInformation {
26105                        range: lsp::Range {
26106                            start: lsp::Position {
26107                                line: 0,
26108                                character: 0,
26109                            },
26110                            end: lsp::Position {
26111                                line: 0,
26112                                character: 1,
26113                            },
26114                        },
26115                        color: lsp::Color {
26116                            red: 0.33,
26117                            green: 0.33,
26118                            blue: 0.33,
26119                            alpha: 0.33,
26120                        },
26121                    },
26122                ])
26123            }
26124        });
26125
26126    let _handle = fake_language_server_without_capabilities
26127        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26128            panic!("Should not be called");
26129        });
26130    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26131    color_request_handle.next().await.unwrap();
26132    cx.run_until_parked();
26133    assert_eq!(
26134        1,
26135        requests_made.load(atomic::Ordering::Acquire),
26136        "Should query for colors once per editor open"
26137    );
26138    editor.update_in(cx, |editor, _, cx| {
26139        assert_eq!(
26140            vec![expected_color],
26141            extract_color_inlays(editor, cx),
26142            "Should have an initial inlay"
26143        );
26144    });
26145
26146    // opening another file in a split should not influence the LSP query counter
26147    workspace
26148        .update(cx, |workspace, window, cx| {
26149            assert_eq!(
26150                workspace.panes().len(),
26151                1,
26152                "Should have one pane with one editor"
26153            );
26154            workspace.move_item_to_pane_in_direction(
26155                &MoveItemToPaneInDirection {
26156                    direction: SplitDirection::Right,
26157                    focus: false,
26158                    clone: true,
26159                },
26160                window,
26161                cx,
26162            );
26163        })
26164        .unwrap();
26165    cx.run_until_parked();
26166    workspace
26167        .update(cx, |workspace, _, cx| {
26168            let panes = workspace.panes();
26169            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26170            for pane in panes {
26171                let editor = pane
26172                    .read(cx)
26173                    .active_item()
26174                    .and_then(|item| item.downcast::<Editor>())
26175                    .expect("Should have opened an editor in each split");
26176                let editor_file = editor
26177                    .read(cx)
26178                    .buffer()
26179                    .read(cx)
26180                    .as_singleton()
26181                    .expect("test deals with singleton buffers")
26182                    .read(cx)
26183                    .file()
26184                    .expect("test buffese should have a file")
26185                    .path();
26186                assert_eq!(
26187                    editor_file.as_ref(),
26188                    rel_path("first.rs"),
26189                    "Both editors should be opened for the same file"
26190                )
26191            }
26192        })
26193        .unwrap();
26194
26195    cx.executor().advance_clock(Duration::from_millis(500));
26196    let save = editor.update_in(cx, |editor, window, cx| {
26197        editor.move_to_end(&MoveToEnd, window, cx);
26198        editor.handle_input("dirty", window, cx);
26199        editor.save(
26200            SaveOptions {
26201                format: true,
26202                autosave: true,
26203            },
26204            project.clone(),
26205            window,
26206            cx,
26207        )
26208    });
26209    save.await.unwrap();
26210
26211    color_request_handle.next().await.unwrap();
26212    cx.run_until_parked();
26213    assert_eq!(
26214        2,
26215        requests_made.load(atomic::Ordering::Acquire),
26216        "Should query for colors once per save (deduplicated) and once per formatting after save"
26217    );
26218
26219    drop(editor);
26220    let close = workspace
26221        .update(cx, |workspace, window, cx| {
26222            workspace.active_pane().update(cx, |pane, cx| {
26223                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26224            })
26225        })
26226        .unwrap();
26227    close.await.unwrap();
26228    let close = workspace
26229        .update(cx, |workspace, window, cx| {
26230            workspace.active_pane().update(cx, |pane, cx| {
26231                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26232            })
26233        })
26234        .unwrap();
26235    close.await.unwrap();
26236    assert_eq!(
26237        2,
26238        requests_made.load(atomic::Ordering::Acquire),
26239        "After saving and closing all editors, no extra requests should be made"
26240    );
26241    workspace
26242        .update(cx, |workspace, _, cx| {
26243            assert!(
26244                workspace.active_item(cx).is_none(),
26245                "Should close all editors"
26246            )
26247        })
26248        .unwrap();
26249
26250    workspace
26251        .update(cx, |workspace, window, cx| {
26252            workspace.active_pane().update(cx, |pane, cx| {
26253                pane.navigate_backward(&workspace::GoBack, window, cx);
26254            })
26255        })
26256        .unwrap();
26257    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26258    cx.run_until_parked();
26259    let editor = workspace
26260        .update(cx, |workspace, _, cx| {
26261            workspace
26262                .active_item(cx)
26263                .expect("Should have reopened the editor again after navigating back")
26264                .downcast::<Editor>()
26265                .expect("Should be an editor")
26266        })
26267        .unwrap();
26268
26269    assert_eq!(
26270        2,
26271        requests_made.load(atomic::Ordering::Acquire),
26272        "Cache should be reused on buffer close and reopen"
26273    );
26274    editor.update(cx, |editor, cx| {
26275        assert_eq!(
26276            vec![expected_color],
26277            extract_color_inlays(editor, cx),
26278            "Should have an initial inlay"
26279        );
26280    });
26281
26282    drop(color_request_handle);
26283    let closure_requests_made = Arc::clone(&requests_made);
26284    let mut empty_color_request_handle = fake_language_server
26285        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26286            let requests_made = Arc::clone(&closure_requests_made);
26287            async move {
26288                assert_eq!(
26289                    params.text_document.uri,
26290                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26291                );
26292                requests_made.fetch_add(1, atomic::Ordering::Release);
26293                Ok(Vec::new())
26294            }
26295        });
26296    let save = editor.update_in(cx, |editor, window, cx| {
26297        editor.move_to_end(&MoveToEnd, window, cx);
26298        editor.handle_input("dirty_again", window, cx);
26299        editor.save(
26300            SaveOptions {
26301                format: false,
26302                autosave: true,
26303            },
26304            project.clone(),
26305            window,
26306            cx,
26307        )
26308    });
26309    save.await.unwrap();
26310
26311    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26312    empty_color_request_handle.next().await.unwrap();
26313    cx.run_until_parked();
26314    assert_eq!(
26315        3,
26316        requests_made.load(atomic::Ordering::Acquire),
26317        "Should query for colors once per save only, as formatting was not requested"
26318    );
26319    editor.update(cx, |editor, cx| {
26320        assert_eq!(
26321            Vec::<Rgba>::new(),
26322            extract_color_inlays(editor, cx),
26323            "Should clear all colors when the server returns an empty response"
26324        );
26325    });
26326}
26327
26328#[gpui::test]
26329async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26330    init_test(cx, |_| {});
26331    let (editor, cx) = cx.add_window_view(Editor::single_line);
26332    editor.update_in(cx, |editor, window, cx| {
26333        editor.set_text("oops\n\nwow\n", window, cx)
26334    });
26335    cx.run_until_parked();
26336    editor.update(cx, |editor, cx| {
26337        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26338    });
26339    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26340    cx.run_until_parked();
26341    editor.update(cx, |editor, cx| {
26342        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26343    });
26344}
26345
26346#[gpui::test]
26347async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26348    init_test(cx, |_| {});
26349
26350    cx.update(|cx| {
26351        register_project_item::<Editor>(cx);
26352    });
26353
26354    let fs = FakeFs::new(cx.executor());
26355    fs.insert_tree("/root1", json!({})).await;
26356    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26357        .await;
26358
26359    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26360    let (workspace, cx) =
26361        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26362
26363    let worktree_id = project.update(cx, |project, cx| {
26364        project.worktrees(cx).next().unwrap().read(cx).id()
26365    });
26366
26367    let handle = workspace
26368        .update_in(cx, |workspace, window, cx| {
26369            let project_path = (worktree_id, rel_path("one.pdf"));
26370            workspace.open_path(project_path, None, true, window, cx)
26371        })
26372        .await
26373        .unwrap();
26374
26375    assert_eq!(
26376        handle.to_any().entity_type(),
26377        TypeId::of::<InvalidItemView>()
26378    );
26379}
26380
26381#[gpui::test]
26382async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26383    init_test(cx, |_| {});
26384
26385    let language = Arc::new(Language::new(
26386        LanguageConfig::default(),
26387        Some(tree_sitter_rust::LANGUAGE.into()),
26388    ));
26389
26390    // Test hierarchical sibling navigation
26391    let text = r#"
26392        fn outer() {
26393            if condition {
26394                let a = 1;
26395            }
26396            let b = 2;
26397        }
26398
26399        fn another() {
26400            let c = 3;
26401        }
26402    "#;
26403
26404    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26405    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26406    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26407
26408    // Wait for parsing to complete
26409    editor
26410        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26411        .await;
26412
26413    editor.update_in(cx, |editor, window, cx| {
26414        // Start by selecting "let a = 1;" inside the if block
26415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26416            s.select_display_ranges([
26417                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26418            ]);
26419        });
26420
26421        let initial_selection = editor.selections.display_ranges(cx);
26422        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26423
26424        // Test select next sibling - should move up levels to find the next sibling
26425        // Since "let a = 1;" has no siblings in the if block, it should move up
26426        // to find "let b = 2;" which is a sibling of the if block
26427        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26428        let next_selection = editor.selections.display_ranges(cx);
26429
26430        // Should have a selection and it should be different from the initial
26431        assert_eq!(
26432            next_selection.len(),
26433            1,
26434            "Should have one selection after next"
26435        );
26436        assert_ne!(
26437            next_selection[0], initial_selection[0],
26438            "Next sibling selection should be different"
26439        );
26440
26441        // Test hierarchical navigation by going to the end of the current function
26442        // and trying to navigate to the next function
26443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26444            s.select_display_ranges([
26445                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26446            ]);
26447        });
26448
26449        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26450        let function_next_selection = editor.selections.display_ranges(cx);
26451
26452        // Should move to the next function
26453        assert_eq!(
26454            function_next_selection.len(),
26455            1,
26456            "Should have one selection after function next"
26457        );
26458
26459        // Test select previous sibling navigation
26460        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26461        let prev_selection = editor.selections.display_ranges(cx);
26462
26463        // Should have a selection and it should be different
26464        assert_eq!(
26465            prev_selection.len(),
26466            1,
26467            "Should have one selection after prev"
26468        );
26469        assert_ne!(
26470            prev_selection[0], function_next_selection[0],
26471            "Previous sibling selection should be different from next"
26472        );
26473    });
26474}
26475
26476#[gpui::test]
26477async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26478    init_test(cx, |_| {});
26479
26480    let mut cx = EditorTestContext::new(cx).await;
26481    cx.set_state(
26482        "let ˇvariable = 42;
26483let another = variable + 1;
26484let result = variable * 2;",
26485    );
26486
26487    // Set up document highlights manually (simulating LSP response)
26488    cx.update_editor(|editor, _window, cx| {
26489        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26490
26491        // Create highlights for "variable" occurrences
26492        let highlight_ranges = [
26493            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26494            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26495            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26496        ];
26497
26498        let anchor_ranges: Vec<_> = highlight_ranges
26499            .iter()
26500            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26501            .collect();
26502
26503        editor.highlight_background::<DocumentHighlightRead>(
26504            &anchor_ranges,
26505            |theme| theme.colors().editor_document_highlight_read_background,
26506            cx,
26507        );
26508    });
26509
26510    // Go to next highlight - should move to second "variable"
26511    cx.update_editor(|editor, window, cx| {
26512        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26513    });
26514    cx.assert_editor_state(
26515        "let variable = 42;
26516let another = ˇvariable + 1;
26517let result = variable * 2;",
26518    );
26519
26520    // Go to next highlight - should move to third "variable"
26521    cx.update_editor(|editor, window, cx| {
26522        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26523    });
26524    cx.assert_editor_state(
26525        "let variable = 42;
26526let another = variable + 1;
26527let result = ˇvariable * 2;",
26528    );
26529
26530    // Go to next highlight - should stay at third "variable" (no wrap-around)
26531    cx.update_editor(|editor, window, cx| {
26532        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26533    });
26534    cx.assert_editor_state(
26535        "let variable = 42;
26536let another = variable + 1;
26537let result = ˇvariable * 2;",
26538    );
26539
26540    // Now test going backwards from third position
26541    cx.update_editor(|editor, window, cx| {
26542        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26543    });
26544    cx.assert_editor_state(
26545        "let variable = 42;
26546let another = ˇvariable + 1;
26547let result = variable * 2;",
26548    );
26549
26550    // Go to previous highlight - should move to first "variable"
26551    cx.update_editor(|editor, window, cx| {
26552        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26553    });
26554    cx.assert_editor_state(
26555        "let ˇvariable = 42;
26556let another = variable + 1;
26557let result = variable * 2;",
26558    );
26559
26560    // Go to previous highlight - should stay on first "variable"
26561    cx.update_editor(|editor, window, cx| {
26562        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26563    });
26564    cx.assert_editor_state(
26565        "let ˇvariable = 42;
26566let another = variable + 1;
26567let result = variable * 2;",
26568    );
26569}
26570
26571#[gpui::test]
26572async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26573    cx: &mut gpui::TestAppContext,
26574) {
26575    init_test(cx, |_| {});
26576
26577    let url = "https://zed.dev";
26578
26579    let markdown_language = Arc::new(Language::new(
26580        LanguageConfig {
26581            name: "Markdown".into(),
26582            ..LanguageConfig::default()
26583        },
26584        None,
26585    ));
26586
26587    let mut cx = EditorTestContext::new(cx).await;
26588    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26589    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26590
26591    cx.update_editor(|editor, window, cx| {
26592        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26593        editor.paste(&Paste, window, cx);
26594    });
26595
26596    cx.assert_editor_state(&format!(
26597        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26598    ));
26599}
26600
26601#[gpui::test]
26602async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26603    cx: &mut gpui::TestAppContext,
26604) {
26605    init_test(cx, |_| {});
26606
26607    let url = "https://zed.dev";
26608
26609    let markdown_language = Arc::new(Language::new(
26610        LanguageConfig {
26611            name: "Markdown".into(),
26612            ..LanguageConfig::default()
26613        },
26614        None,
26615    ));
26616
26617    let mut cx = EditorTestContext::new(cx).await;
26618    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26619    cx.set_state(&format!(
26620        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26621    ));
26622
26623    cx.update_editor(|editor, window, cx| {
26624        editor.copy(&Copy, window, cx);
26625    });
26626
26627    cx.set_state(&format!(
26628        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26629    ));
26630
26631    cx.update_editor(|editor, window, cx| {
26632        editor.paste(&Paste, window, cx);
26633    });
26634
26635    cx.assert_editor_state(&format!(
26636        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26637    ));
26638}
26639
26640#[gpui::test]
26641async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26642    cx: &mut gpui::TestAppContext,
26643) {
26644    init_test(cx, |_| {});
26645
26646    let url = "https://zed.dev";
26647
26648    let markdown_language = Arc::new(Language::new(
26649        LanguageConfig {
26650            name: "Markdown".into(),
26651            ..LanguageConfig::default()
26652        },
26653        None,
26654    ));
26655
26656    let mut cx = EditorTestContext::new(cx).await;
26657    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26658    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26659
26660    cx.update_editor(|editor, window, cx| {
26661        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26662        editor.paste(&Paste, window, cx);
26663    });
26664
26665    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26666}
26667
26668#[gpui::test]
26669async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26670    cx: &mut gpui::TestAppContext,
26671) {
26672    init_test(cx, |_| {});
26673
26674    let text = "Awesome";
26675
26676    let markdown_language = Arc::new(Language::new(
26677        LanguageConfig {
26678            name: "Markdown".into(),
26679            ..LanguageConfig::default()
26680        },
26681        None,
26682    ));
26683
26684    let mut cx = EditorTestContext::new(cx).await;
26685    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26686    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26687
26688    cx.update_editor(|editor, window, cx| {
26689        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26690        editor.paste(&Paste, window, cx);
26691    });
26692
26693    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26694}
26695
26696#[gpui::test]
26697async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26698    cx: &mut gpui::TestAppContext,
26699) {
26700    init_test(cx, |_| {});
26701
26702    let url = "https://zed.dev";
26703
26704    let markdown_language = Arc::new(Language::new(
26705        LanguageConfig {
26706            name: "Rust".into(),
26707            ..LanguageConfig::default()
26708        },
26709        None,
26710    ));
26711
26712    let mut cx = EditorTestContext::new(cx).await;
26713    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26714    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26715
26716    cx.update_editor(|editor, window, cx| {
26717        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26718        editor.paste(&Paste, window, cx);
26719    });
26720
26721    cx.assert_editor_state(&format!(
26722        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26723    ));
26724}
26725
26726#[gpui::test]
26727async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26728    cx: &mut TestAppContext,
26729) {
26730    init_test(cx, |_| {});
26731
26732    let url = "https://zed.dev";
26733
26734    let markdown_language = Arc::new(Language::new(
26735        LanguageConfig {
26736            name: "Markdown".into(),
26737            ..LanguageConfig::default()
26738        },
26739        None,
26740    ));
26741
26742    let (editor, cx) = cx.add_window_view(|window, cx| {
26743        let multi_buffer = MultiBuffer::build_multi(
26744            [
26745                ("this will embed -> link", vec![Point::row_range(0..1)]),
26746                ("this will replace -> link", vec![Point::row_range(0..1)]),
26747            ],
26748            cx,
26749        );
26750        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26752            s.select_ranges(vec![
26753                Point::new(0, 19)..Point::new(0, 23),
26754                Point::new(1, 21)..Point::new(1, 25),
26755            ])
26756        });
26757        let first_buffer_id = multi_buffer
26758            .read(cx)
26759            .excerpt_buffer_ids()
26760            .into_iter()
26761            .next()
26762            .unwrap();
26763        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26764        first_buffer.update(cx, |buffer, cx| {
26765            buffer.set_language(Some(markdown_language.clone()), cx);
26766        });
26767
26768        editor
26769    });
26770    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26771
26772    cx.update_editor(|editor, window, cx| {
26773        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26774        editor.paste(&Paste, window, cx);
26775    });
26776
26777    cx.assert_editor_state(&format!(
26778        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26779    ));
26780}
26781
26782#[gpui::test]
26783async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26784    init_test(cx, |_| {});
26785
26786    let fs = FakeFs::new(cx.executor());
26787    fs.insert_tree(
26788        path!("/project"),
26789        json!({
26790            "first.rs": "# First Document\nSome content here.",
26791            "second.rs": "Plain text content for second file.",
26792        }),
26793    )
26794    .await;
26795
26796    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26797    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26798    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26799
26800    let language = rust_lang();
26801    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26802    language_registry.add(language.clone());
26803    let mut fake_servers = language_registry.register_fake_lsp(
26804        "Rust",
26805        FakeLspAdapter {
26806            ..FakeLspAdapter::default()
26807        },
26808    );
26809
26810    let buffer1 = project
26811        .update(cx, |project, cx| {
26812            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26813        })
26814        .await
26815        .unwrap();
26816    let buffer2 = project
26817        .update(cx, |project, cx| {
26818            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26819        })
26820        .await
26821        .unwrap();
26822
26823    let multi_buffer = cx.new(|cx| {
26824        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26825        multi_buffer.set_excerpts_for_path(
26826            PathKey::for_buffer(&buffer1, cx),
26827            buffer1.clone(),
26828            [Point::zero()..buffer1.read(cx).max_point()],
26829            3,
26830            cx,
26831        );
26832        multi_buffer.set_excerpts_for_path(
26833            PathKey::for_buffer(&buffer2, cx),
26834            buffer2.clone(),
26835            [Point::zero()..buffer1.read(cx).max_point()],
26836            3,
26837            cx,
26838        );
26839        multi_buffer
26840    });
26841
26842    let (editor, cx) = cx.add_window_view(|window, cx| {
26843        Editor::new(
26844            EditorMode::full(),
26845            multi_buffer,
26846            Some(project.clone()),
26847            window,
26848            cx,
26849        )
26850    });
26851
26852    let fake_language_server = fake_servers.next().await.unwrap();
26853
26854    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26855
26856    let save = editor.update_in(cx, |editor, window, cx| {
26857        assert!(editor.is_dirty(cx));
26858
26859        editor.save(
26860            SaveOptions {
26861                format: true,
26862                autosave: true,
26863            },
26864            project,
26865            window,
26866            cx,
26867        )
26868    });
26869    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26870    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26871    let mut done_edit_rx = Some(done_edit_rx);
26872    let mut start_edit_tx = Some(start_edit_tx);
26873
26874    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26875        start_edit_tx.take().unwrap().send(()).unwrap();
26876        let done_edit_rx = done_edit_rx.take().unwrap();
26877        async move {
26878            done_edit_rx.await.unwrap();
26879            Ok(None)
26880        }
26881    });
26882
26883    start_edit_rx.await.unwrap();
26884    buffer2
26885        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26886        .unwrap();
26887
26888    done_edit_tx.send(()).unwrap();
26889
26890    save.await.unwrap();
26891    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26892}
26893
26894#[track_caller]
26895fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26896    editor
26897        .all_inlays(cx)
26898        .into_iter()
26899        .filter_map(|inlay| inlay.get_color())
26900        .map(Rgba::from)
26901        .collect()
26902}
26903
26904#[gpui::test]
26905fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26906    init_test(cx, |_| {});
26907
26908    let editor = cx.add_window(|window, cx| {
26909        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26910        build_editor(buffer, window, cx)
26911    });
26912
26913    editor
26914        .update(cx, |editor, window, cx| {
26915            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26916                s.select_display_ranges([
26917                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26918                ])
26919            });
26920
26921            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26922
26923            assert_eq!(
26924                editor.display_text(cx),
26925                "line1\nline2\nline2",
26926                "Duplicating last line upward should create duplicate above, not on same line"
26927            );
26928
26929            assert_eq!(
26930                editor.selections.display_ranges(cx),
26931                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26932                "Selection should move to the duplicated line"
26933            );
26934        })
26935        .unwrap();
26936}
26937
26938#[gpui::test]
26939async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26940    init_test(cx, |_| {});
26941
26942    let mut cx = EditorTestContext::new(cx).await;
26943
26944    cx.set_state("line1\nline2ˇ");
26945
26946    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26947
26948    let clipboard_text = cx
26949        .read_from_clipboard()
26950        .and_then(|item| item.text().as_deref().map(str::to_string));
26951
26952    assert_eq!(
26953        clipboard_text,
26954        Some("line2\n".to_string()),
26955        "Copying a line without trailing newline should include a newline"
26956    );
26957
26958    cx.set_state("line1\nˇ");
26959
26960    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26961
26962    cx.assert_editor_state("line1\nline2\nˇ");
26963}
26964
26965#[gpui::test]
26966async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26967    init_test(cx, |_| {});
26968
26969    let mut cx = EditorTestContext::new(cx).await;
26970
26971    cx.set_state("line1\nline2ˇ");
26972    cx.update_editor(|e, window, cx| {
26973        e.set_mode(EditorMode::SingleLine);
26974        assert!(e.key_context(window, cx).contains("end_of_input"));
26975    });
26976    cx.set_state("ˇline1\nline2");
26977    cx.update_editor(|e, window, cx| {
26978        assert!(!e.key_context(window, cx).contains("end_of_input"));
26979    });
26980    cx.set_state("line1ˇ\nline2");
26981    cx.update_editor(|e, window, cx| {
26982        assert!(!e.key_context(window, cx).contains("end_of_input"));
26983    });
26984}
26985
26986#[gpui::test]
26987async fn test_next_prev_reference(cx: &mut TestAppContext) {
26988    const CYCLE_POSITIONS: &[&'static str] = &[
26989        indoc! {"
26990            fn foo() {
26991                let ˇabc = 123;
26992                let x = abc + 1;
26993                let y = abc + 2;
26994                let z = abc + 2;
26995            }
26996        "},
26997        indoc! {"
26998            fn foo() {
26999                let abc = 123;
27000                let x = ˇabc + 1;
27001                let y = abc + 2;
27002                let z = abc + 2;
27003            }
27004        "},
27005        indoc! {"
27006            fn foo() {
27007                let abc = 123;
27008                let x = abc + 1;
27009                let y = ˇabc + 2;
27010                let z = abc + 2;
27011            }
27012        "},
27013        indoc! {"
27014            fn foo() {
27015                let abc = 123;
27016                let x = abc + 1;
27017                let y = abc + 2;
27018                let z = ˇabc + 2;
27019            }
27020        "},
27021    ];
27022
27023    init_test(cx, |_| {});
27024
27025    let mut cx = EditorLspTestContext::new_rust(
27026        lsp::ServerCapabilities {
27027            references_provider: Some(lsp::OneOf::Left(true)),
27028            ..Default::default()
27029        },
27030        cx,
27031    )
27032    .await;
27033
27034    // importantly, the cursor is in the middle
27035    cx.set_state(indoc! {"
27036        fn foo() {
27037            let aˇbc = 123;
27038            let x = abc + 1;
27039            let y = abc + 2;
27040            let z = abc + 2;
27041        }
27042    "});
27043
27044    let reference_ranges = [
27045        lsp::Position::new(1, 8),
27046        lsp::Position::new(2, 12),
27047        lsp::Position::new(3, 12),
27048        lsp::Position::new(4, 12),
27049    ]
27050    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27051
27052    cx.lsp
27053        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27054            Ok(Some(
27055                reference_ranges
27056                    .map(|range| lsp::Location {
27057                        uri: params.text_document_position.text_document.uri.clone(),
27058                        range,
27059                    })
27060                    .to_vec(),
27061            ))
27062        });
27063
27064    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27065        cx.update_editor(|editor, window, cx| {
27066            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27067        })
27068        .unwrap()
27069        .await
27070        .unwrap()
27071    };
27072
27073    _move(Direction::Next, 1, &mut cx).await;
27074    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27075
27076    _move(Direction::Next, 1, &mut cx).await;
27077    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27078
27079    _move(Direction::Next, 1, &mut cx).await;
27080    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27081
27082    // loops back to the start
27083    _move(Direction::Next, 1, &mut cx).await;
27084    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27085
27086    // loops back to the end
27087    _move(Direction::Prev, 1, &mut cx).await;
27088    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27089
27090    _move(Direction::Prev, 1, &mut cx).await;
27091    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27092
27093    _move(Direction::Prev, 1, &mut cx).await;
27094    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27095
27096    _move(Direction::Prev, 1, &mut cx).await;
27097    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27098
27099    _move(Direction::Next, 3, &mut cx).await;
27100    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27101
27102    _move(Direction::Prev, 2, &mut cx).await;
27103    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27104}