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_snippets(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let mut cx = EditorTestContext::new(cx).await;
11074
11075    cx.set_state(indoc! {"
11076        a.ˇ b
11077        a.ˇ b
11078        a.ˇ b
11079    "});
11080
11081    cx.update_editor(|editor, window, cx| {
11082        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11083        let insertion_ranges = editor
11084            .selections
11085            .all(&editor.display_snapshot(cx))
11086            .iter()
11087            .map(|s| s.range())
11088            .collect::<Vec<_>>();
11089        editor
11090            .insert_snippet(&insertion_ranges, snippet, window, cx)
11091            .unwrap();
11092    });
11093
11094    cx.assert_editor_state(indoc! {"
11095        a.f(«oneˇ», two, «threeˇ») b
11096        a.f(«oneˇ», two, «threeˇ») b
11097        a.f(«oneˇ», two, «threeˇ») b
11098    "});
11099
11100    // Can't move earlier than the first tab stop
11101    cx.update_editor(|editor, window, cx| {
11102        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11103    });
11104    cx.assert_editor_state(indoc! {"
11105        a.f(«oneˇ», two, «threeˇ») b
11106        a.f(«oneˇ», two, «threeˇ») b
11107        a.f(«oneˇ», two, «threeˇ») b
11108    "});
11109
11110    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11111    cx.assert_editor_state(indoc! {"
11112        a.f(one, «twoˇ», three) b
11113        a.f(one, «twoˇ», three) b
11114        a.f(one, «twoˇ», three) b
11115    "});
11116
11117    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11118    cx.assert_editor_state(indoc! {"
11119        a.f(«oneˇ», two, «threeˇ») b
11120        a.f(«oneˇ», two, «threeˇ») b
11121        a.f(«oneˇ», two, «threeˇ») b
11122    "});
11123
11124    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11125    cx.assert_editor_state(indoc! {"
11126        a.f(one, «twoˇ», three) b
11127        a.f(one, «twoˇ», three) b
11128        a.f(one, «twoˇ», three) b
11129    "});
11130    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11131    cx.assert_editor_state(indoc! {"
11132        a.f(one, two, three)ˇ b
11133        a.f(one, two, three)ˇ b
11134        a.f(one, two, three)ˇ b
11135    "});
11136
11137    // As soon as the last tab stop is reached, snippet state is gone
11138    cx.update_editor(|editor, window, cx| {
11139        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11140    });
11141    cx.assert_editor_state(indoc! {"
11142        a.f(one, two, three)ˇ b
11143        a.f(one, two, three)ˇ b
11144        a.f(one, two, three)ˇ b
11145    "});
11146}
11147
11148#[gpui::test]
11149async fn test_snippet_indentation(cx: &mut TestAppContext) {
11150    init_test(cx, |_| {});
11151
11152    let mut cx = EditorTestContext::new(cx).await;
11153
11154    cx.update_editor(|editor, window, cx| {
11155        let snippet = Snippet::parse(indoc! {"
11156            /*
11157             * Multiline comment with leading indentation
11158             *
11159             * $1
11160             */
11161            $0"})
11162        .unwrap();
11163        let insertion_ranges = editor
11164            .selections
11165            .all(&editor.display_snapshot(cx))
11166            .iter()
11167            .map(|s| s.range())
11168            .collect::<Vec<_>>();
11169        editor
11170            .insert_snippet(&insertion_ranges, snippet, window, cx)
11171            .unwrap();
11172    });
11173
11174    cx.assert_editor_state(indoc! {"
11175        /*
11176         * Multiline comment with leading indentation
11177         *
11178         * ˇ
11179         */
11180    "});
11181
11182    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11183    cx.assert_editor_state(indoc! {"
11184        /*
11185         * Multiline comment with leading indentation
11186         *
11187         *•
11188         */
11189        ˇ"});
11190}
11191
11192#[gpui::test]
11193async fn test_document_format_during_save(cx: &mut TestAppContext) {
11194    init_test(cx, |_| {});
11195
11196    let fs = FakeFs::new(cx.executor());
11197    fs.insert_file(path!("/file.rs"), Default::default()).await;
11198
11199    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11200
11201    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11202    language_registry.add(rust_lang());
11203    let mut fake_servers = language_registry.register_fake_lsp(
11204        "Rust",
11205        FakeLspAdapter {
11206            capabilities: lsp::ServerCapabilities {
11207                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11208                ..Default::default()
11209            },
11210            ..Default::default()
11211        },
11212    );
11213
11214    let buffer = project
11215        .update(cx, |project, cx| {
11216            project.open_local_buffer(path!("/file.rs"), cx)
11217        })
11218        .await
11219        .unwrap();
11220
11221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11222    let (editor, cx) = cx.add_window_view(|window, cx| {
11223        build_editor_with_project(project.clone(), buffer, window, cx)
11224    });
11225    editor.update_in(cx, |editor, window, cx| {
11226        editor.set_text("one\ntwo\nthree\n", window, cx)
11227    });
11228    assert!(cx.read(|cx| editor.is_dirty(cx)));
11229
11230    cx.executor().start_waiting();
11231    let fake_server = fake_servers.next().await.unwrap();
11232
11233    {
11234        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11235            move |params, _| async move {
11236                assert_eq!(
11237                    params.text_document.uri,
11238                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11239                );
11240                assert_eq!(params.options.tab_size, 4);
11241                Ok(Some(vec![lsp::TextEdit::new(
11242                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11243                    ", ".to_string(),
11244                )]))
11245            },
11246        );
11247        let save = editor
11248            .update_in(cx, |editor, window, cx| {
11249                editor.save(
11250                    SaveOptions {
11251                        format: true,
11252                        autosave: false,
11253                    },
11254                    project.clone(),
11255                    window,
11256                    cx,
11257                )
11258            })
11259            .unwrap();
11260        cx.executor().start_waiting();
11261        save.await;
11262
11263        assert_eq!(
11264            editor.update(cx, |editor, cx| editor.text(cx)),
11265            "one, two\nthree\n"
11266        );
11267        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11268    }
11269
11270    {
11271        editor.update_in(cx, |editor, window, cx| {
11272            editor.set_text("one\ntwo\nthree\n", window, cx)
11273        });
11274        assert!(cx.read(|cx| editor.is_dirty(cx)));
11275
11276        // Ensure we can still save even if formatting hangs.
11277        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11278            move |params, _| async move {
11279                assert_eq!(
11280                    params.text_document.uri,
11281                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11282                );
11283                futures::future::pending::<()>().await;
11284                unreachable!()
11285            },
11286        );
11287        let save = editor
11288            .update_in(cx, |editor, window, cx| {
11289                editor.save(
11290                    SaveOptions {
11291                        format: true,
11292                        autosave: false,
11293                    },
11294                    project.clone(),
11295                    window,
11296                    cx,
11297                )
11298            })
11299            .unwrap();
11300        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11301        cx.executor().start_waiting();
11302        save.await;
11303        assert_eq!(
11304            editor.update(cx, |editor, cx| editor.text(cx)),
11305            "one\ntwo\nthree\n"
11306        );
11307    }
11308
11309    // Set rust language override and assert overridden tabsize is sent to language server
11310    update_test_language_settings(cx, |settings| {
11311        settings.languages.0.insert(
11312            "Rust".into(),
11313            LanguageSettingsContent {
11314                tab_size: NonZeroU32::new(8),
11315                ..Default::default()
11316            },
11317        );
11318    });
11319
11320    {
11321        editor.update_in(cx, |editor, window, cx| {
11322            editor.set_text("somehting_new\n", window, cx)
11323        });
11324        assert!(cx.read(|cx| editor.is_dirty(cx)));
11325        let _formatting_request_signal = fake_server
11326            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11327                assert_eq!(
11328                    params.text_document.uri,
11329                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11330                );
11331                assert_eq!(params.options.tab_size, 8);
11332                Ok(Some(vec![]))
11333            });
11334        let save = editor
11335            .update_in(cx, |editor, window, cx| {
11336                editor.save(
11337                    SaveOptions {
11338                        format: true,
11339                        autosave: false,
11340                    },
11341                    project.clone(),
11342                    window,
11343                    cx,
11344                )
11345            })
11346            .unwrap();
11347        cx.executor().start_waiting();
11348        save.await;
11349    }
11350}
11351
11352#[gpui::test]
11353async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11354    init_test(cx, |settings| {
11355        settings.defaults.ensure_final_newline_on_save = Some(false);
11356    });
11357
11358    let fs = FakeFs::new(cx.executor());
11359    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11360
11361    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11362
11363    let buffer = project
11364        .update(cx, |project, cx| {
11365            project.open_local_buffer(path!("/file.txt"), cx)
11366        })
11367        .await
11368        .unwrap();
11369
11370    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11371    let (editor, cx) = cx.add_window_view(|window, cx| {
11372        build_editor_with_project(project.clone(), buffer, window, cx)
11373    });
11374    editor.update_in(cx, |editor, window, cx| {
11375        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11376            s.select_ranges([0..0])
11377        });
11378    });
11379    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11380
11381    editor.update_in(cx, |editor, window, cx| {
11382        editor.handle_input("\n", window, cx)
11383    });
11384    cx.run_until_parked();
11385    save(&editor, &project, cx).await;
11386    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11387
11388    editor.update_in(cx, |editor, window, cx| {
11389        editor.undo(&Default::default(), window, cx);
11390    });
11391    save(&editor, &project, cx).await;
11392    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11393
11394    editor.update_in(cx, |editor, window, cx| {
11395        editor.redo(&Default::default(), window, cx);
11396    });
11397    cx.run_until_parked();
11398    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11399
11400    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11401        let save = editor
11402            .update_in(cx, |editor, window, cx| {
11403                editor.save(
11404                    SaveOptions {
11405                        format: true,
11406                        autosave: false,
11407                    },
11408                    project.clone(),
11409                    window,
11410                    cx,
11411                )
11412            })
11413            .unwrap();
11414        cx.executor().start_waiting();
11415        save.await;
11416        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11417    }
11418}
11419
11420#[gpui::test]
11421async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11422    init_test(cx, |_| {});
11423
11424    let cols = 4;
11425    let rows = 10;
11426    let sample_text_1 = sample_text(rows, cols, 'a');
11427    assert_eq!(
11428        sample_text_1,
11429        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11430    );
11431    let sample_text_2 = sample_text(rows, cols, 'l');
11432    assert_eq!(
11433        sample_text_2,
11434        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11435    );
11436    let sample_text_3 = sample_text(rows, cols, 'v');
11437    assert_eq!(
11438        sample_text_3,
11439        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11440    );
11441
11442    let fs = FakeFs::new(cx.executor());
11443    fs.insert_tree(
11444        path!("/a"),
11445        json!({
11446            "main.rs": sample_text_1,
11447            "other.rs": sample_text_2,
11448            "lib.rs": sample_text_3,
11449        }),
11450    )
11451    .await;
11452
11453    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11454    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11455    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11456
11457    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11458    language_registry.add(rust_lang());
11459    let mut fake_servers = language_registry.register_fake_lsp(
11460        "Rust",
11461        FakeLspAdapter {
11462            capabilities: lsp::ServerCapabilities {
11463                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11464                ..Default::default()
11465            },
11466            ..Default::default()
11467        },
11468    );
11469
11470    let worktree = project.update(cx, |project, cx| {
11471        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11472        assert_eq!(worktrees.len(), 1);
11473        worktrees.pop().unwrap()
11474    });
11475    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11476
11477    let buffer_1 = project
11478        .update(cx, |project, cx| {
11479            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11480        })
11481        .await
11482        .unwrap();
11483    let buffer_2 = project
11484        .update(cx, |project, cx| {
11485            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11486        })
11487        .await
11488        .unwrap();
11489    let buffer_3 = project
11490        .update(cx, |project, cx| {
11491            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11492        })
11493        .await
11494        .unwrap();
11495
11496    let multi_buffer = cx.new(|cx| {
11497        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11498        multi_buffer.push_excerpts(
11499            buffer_1.clone(),
11500            [
11501                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11502                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11503                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11504            ],
11505            cx,
11506        );
11507        multi_buffer.push_excerpts(
11508            buffer_2.clone(),
11509            [
11510                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11511                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11512                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11513            ],
11514            cx,
11515        );
11516        multi_buffer.push_excerpts(
11517            buffer_3.clone(),
11518            [
11519                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11520                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11521                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11522            ],
11523            cx,
11524        );
11525        multi_buffer
11526    });
11527    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11528        Editor::new(
11529            EditorMode::full(),
11530            multi_buffer,
11531            Some(project.clone()),
11532            window,
11533            cx,
11534        )
11535    });
11536
11537    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11538        editor.change_selections(
11539            SelectionEffects::scroll(Autoscroll::Next),
11540            window,
11541            cx,
11542            |s| s.select_ranges(Some(1..2)),
11543        );
11544        editor.insert("|one|two|three|", window, cx);
11545    });
11546    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11547    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11548        editor.change_selections(
11549            SelectionEffects::scroll(Autoscroll::Next),
11550            window,
11551            cx,
11552            |s| s.select_ranges(Some(60..70)),
11553        );
11554        editor.insert("|four|five|six|", window, cx);
11555    });
11556    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11557
11558    // First two buffers should be edited, but not the third one.
11559    assert_eq!(
11560        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11561        "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}",
11562    );
11563    buffer_1.update(cx, |buffer, _| {
11564        assert!(buffer.is_dirty());
11565        assert_eq!(
11566            buffer.text(),
11567            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11568        )
11569    });
11570    buffer_2.update(cx, |buffer, _| {
11571        assert!(buffer.is_dirty());
11572        assert_eq!(
11573            buffer.text(),
11574            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11575        )
11576    });
11577    buffer_3.update(cx, |buffer, _| {
11578        assert!(!buffer.is_dirty());
11579        assert_eq!(buffer.text(), sample_text_3,)
11580    });
11581    cx.executor().run_until_parked();
11582
11583    cx.executor().start_waiting();
11584    let save = multi_buffer_editor
11585        .update_in(cx, |editor, window, cx| {
11586            editor.save(
11587                SaveOptions {
11588                    format: true,
11589                    autosave: false,
11590                },
11591                project.clone(),
11592                window,
11593                cx,
11594            )
11595        })
11596        .unwrap();
11597
11598    let fake_server = fake_servers.next().await.unwrap();
11599    fake_server
11600        .server
11601        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11602            Ok(Some(vec![lsp::TextEdit::new(
11603                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11604                format!("[{} formatted]", params.text_document.uri),
11605            )]))
11606        })
11607        .detach();
11608    save.await;
11609
11610    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11611    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11612    assert_eq!(
11613        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11614        uri!(
11615            "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}"
11616        ),
11617    );
11618    buffer_1.update(cx, |buffer, _| {
11619        assert!(!buffer.is_dirty());
11620        assert_eq!(
11621            buffer.text(),
11622            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11623        )
11624    });
11625    buffer_2.update(cx, |buffer, _| {
11626        assert!(!buffer.is_dirty());
11627        assert_eq!(
11628            buffer.text(),
11629            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11630        )
11631    });
11632    buffer_3.update(cx, |buffer, _| {
11633        assert!(!buffer.is_dirty());
11634        assert_eq!(buffer.text(), sample_text_3,)
11635    });
11636}
11637
11638#[gpui::test]
11639async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11640    init_test(cx, |_| {});
11641
11642    let fs = FakeFs::new(cx.executor());
11643    fs.insert_tree(
11644        path!("/dir"),
11645        json!({
11646            "file1.rs": "fn main() { println!(\"hello\"); }",
11647            "file2.rs": "fn test() { println!(\"test\"); }",
11648            "file3.rs": "fn other() { println!(\"other\"); }\n",
11649        }),
11650    )
11651    .await;
11652
11653    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11654    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11655    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11656
11657    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11658    language_registry.add(rust_lang());
11659
11660    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11661    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11662
11663    // Open three buffers
11664    let buffer_1 = project
11665        .update(cx, |project, cx| {
11666            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11667        })
11668        .await
11669        .unwrap();
11670    let buffer_2 = project
11671        .update(cx, |project, cx| {
11672            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11673        })
11674        .await
11675        .unwrap();
11676    let buffer_3 = project
11677        .update(cx, |project, cx| {
11678            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11679        })
11680        .await
11681        .unwrap();
11682
11683    // Create a multi-buffer with all three buffers
11684    let multi_buffer = cx.new(|cx| {
11685        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11686        multi_buffer.push_excerpts(
11687            buffer_1.clone(),
11688            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11689            cx,
11690        );
11691        multi_buffer.push_excerpts(
11692            buffer_2.clone(),
11693            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11694            cx,
11695        );
11696        multi_buffer.push_excerpts(
11697            buffer_3.clone(),
11698            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11699            cx,
11700        );
11701        multi_buffer
11702    });
11703
11704    let editor = cx.new_window_entity(|window, cx| {
11705        Editor::new(
11706            EditorMode::full(),
11707            multi_buffer,
11708            Some(project.clone()),
11709            window,
11710            cx,
11711        )
11712    });
11713
11714    // Edit only the first buffer
11715    editor.update_in(cx, |editor, window, cx| {
11716        editor.change_selections(
11717            SelectionEffects::scroll(Autoscroll::Next),
11718            window,
11719            cx,
11720            |s| s.select_ranges(Some(10..10)),
11721        );
11722        editor.insert("// edited", window, cx);
11723    });
11724
11725    // Verify that only buffer 1 is dirty
11726    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11727    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11728    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11729
11730    // Get write counts after file creation (files were created with initial content)
11731    // We expect each file to have been written once during creation
11732    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11733    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11734    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11735
11736    // Perform autosave
11737    let save_task = editor.update_in(cx, |editor, window, cx| {
11738        editor.save(
11739            SaveOptions {
11740                format: true,
11741                autosave: true,
11742            },
11743            project.clone(),
11744            window,
11745            cx,
11746        )
11747    });
11748    save_task.await.unwrap();
11749
11750    // Only the dirty buffer should have been saved
11751    assert_eq!(
11752        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11753        1,
11754        "Buffer 1 was dirty, so it should have been written once during autosave"
11755    );
11756    assert_eq!(
11757        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11758        0,
11759        "Buffer 2 was clean, so it should not have been written during autosave"
11760    );
11761    assert_eq!(
11762        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11763        0,
11764        "Buffer 3 was clean, so it should not have been written during autosave"
11765    );
11766
11767    // Verify buffer states after autosave
11768    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11769    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11770    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11771
11772    // Now perform a manual save (format = true)
11773    let save_task = editor.update_in(cx, |editor, window, cx| {
11774        editor.save(
11775            SaveOptions {
11776                format: true,
11777                autosave: false,
11778            },
11779            project.clone(),
11780            window,
11781            cx,
11782        )
11783    });
11784    save_task.await.unwrap();
11785
11786    // During manual save, clean buffers don't get written to disk
11787    // They just get did_save called for language server notifications
11788    assert_eq!(
11789        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11790        1,
11791        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11792    );
11793    assert_eq!(
11794        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11795        0,
11796        "Buffer 2 should not have been written at all"
11797    );
11798    assert_eq!(
11799        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11800        0,
11801        "Buffer 3 should not have been written at all"
11802    );
11803}
11804
11805async fn setup_range_format_test(
11806    cx: &mut TestAppContext,
11807) -> (
11808    Entity<Project>,
11809    Entity<Editor>,
11810    &mut gpui::VisualTestContext,
11811    lsp::FakeLanguageServer,
11812) {
11813    init_test(cx, |_| {});
11814
11815    let fs = FakeFs::new(cx.executor());
11816    fs.insert_file(path!("/file.rs"), Default::default()).await;
11817
11818    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11819
11820    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11821    language_registry.add(rust_lang());
11822    let mut fake_servers = language_registry.register_fake_lsp(
11823        "Rust",
11824        FakeLspAdapter {
11825            capabilities: lsp::ServerCapabilities {
11826                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11827                ..lsp::ServerCapabilities::default()
11828            },
11829            ..FakeLspAdapter::default()
11830        },
11831    );
11832
11833    let buffer = project
11834        .update(cx, |project, cx| {
11835            project.open_local_buffer(path!("/file.rs"), cx)
11836        })
11837        .await
11838        .unwrap();
11839
11840    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11841    let (editor, cx) = cx.add_window_view(|window, cx| {
11842        build_editor_with_project(project.clone(), buffer, window, cx)
11843    });
11844
11845    cx.executor().start_waiting();
11846    let fake_server = fake_servers.next().await.unwrap();
11847
11848    (project, editor, cx, fake_server)
11849}
11850
11851#[gpui::test]
11852async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11853    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11854
11855    editor.update_in(cx, |editor, window, cx| {
11856        editor.set_text("one\ntwo\nthree\n", window, cx)
11857    });
11858    assert!(cx.read(|cx| editor.is_dirty(cx)));
11859
11860    let save = editor
11861        .update_in(cx, |editor, window, cx| {
11862            editor.save(
11863                SaveOptions {
11864                    format: true,
11865                    autosave: false,
11866                },
11867                project.clone(),
11868                window,
11869                cx,
11870            )
11871        })
11872        .unwrap();
11873    fake_server
11874        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11875            assert_eq!(
11876                params.text_document.uri,
11877                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11878            );
11879            assert_eq!(params.options.tab_size, 4);
11880            Ok(Some(vec![lsp::TextEdit::new(
11881                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11882                ", ".to_string(),
11883            )]))
11884        })
11885        .next()
11886        .await;
11887    cx.executor().start_waiting();
11888    save.await;
11889    assert_eq!(
11890        editor.update(cx, |editor, cx| editor.text(cx)),
11891        "one, two\nthree\n"
11892    );
11893    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11894}
11895
11896#[gpui::test]
11897async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11898    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11899
11900    editor.update_in(cx, |editor, window, cx| {
11901        editor.set_text("one\ntwo\nthree\n", window, cx)
11902    });
11903    assert!(cx.read(|cx| editor.is_dirty(cx)));
11904
11905    // Test that save still works when formatting hangs
11906    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11907        move |params, _| async move {
11908            assert_eq!(
11909                params.text_document.uri,
11910                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11911            );
11912            futures::future::pending::<()>().await;
11913            unreachable!()
11914        },
11915    );
11916    let save = editor
11917        .update_in(cx, |editor, window, cx| {
11918            editor.save(
11919                SaveOptions {
11920                    format: true,
11921                    autosave: false,
11922                },
11923                project.clone(),
11924                window,
11925                cx,
11926            )
11927        })
11928        .unwrap();
11929    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11930    cx.executor().start_waiting();
11931    save.await;
11932    assert_eq!(
11933        editor.update(cx, |editor, cx| editor.text(cx)),
11934        "one\ntwo\nthree\n"
11935    );
11936    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11937}
11938
11939#[gpui::test]
11940async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11941    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11942
11943    // Buffer starts clean, no formatting should be requested
11944    let save = editor
11945        .update_in(cx, |editor, window, cx| {
11946            editor.save(
11947                SaveOptions {
11948                    format: false,
11949                    autosave: false,
11950                },
11951                project.clone(),
11952                window,
11953                cx,
11954            )
11955        })
11956        .unwrap();
11957    let _pending_format_request = fake_server
11958        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11959            panic!("Should not be invoked");
11960        })
11961        .next();
11962    cx.executor().start_waiting();
11963    save.await;
11964    cx.run_until_parked();
11965}
11966
11967#[gpui::test]
11968async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11969    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11970
11971    // Set Rust language override and assert overridden tabsize is sent to language server
11972    update_test_language_settings(cx, |settings| {
11973        settings.languages.0.insert(
11974            "Rust".into(),
11975            LanguageSettingsContent {
11976                tab_size: NonZeroU32::new(8),
11977                ..Default::default()
11978            },
11979        );
11980    });
11981
11982    editor.update_in(cx, |editor, window, cx| {
11983        editor.set_text("something_new\n", window, cx)
11984    });
11985    assert!(cx.read(|cx| editor.is_dirty(cx)));
11986    let save = editor
11987        .update_in(cx, |editor, window, cx| {
11988            editor.save(
11989                SaveOptions {
11990                    format: true,
11991                    autosave: false,
11992                },
11993                project.clone(),
11994                window,
11995                cx,
11996            )
11997        })
11998        .unwrap();
11999    fake_server
12000        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12001            assert_eq!(
12002                params.text_document.uri,
12003                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12004            );
12005            assert_eq!(params.options.tab_size, 8);
12006            Ok(Some(Vec::new()))
12007        })
12008        .next()
12009        .await;
12010    save.await;
12011}
12012
12013#[gpui::test]
12014async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12015    init_test(cx, |settings| {
12016        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12017            settings::LanguageServerFormatterSpecifier::Current,
12018        )))
12019    });
12020
12021    let fs = FakeFs::new(cx.executor());
12022    fs.insert_file(path!("/file.rs"), Default::default()).await;
12023
12024    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12025
12026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12027    language_registry.add(Arc::new(Language::new(
12028        LanguageConfig {
12029            name: "Rust".into(),
12030            matcher: LanguageMatcher {
12031                path_suffixes: vec!["rs".to_string()],
12032                ..Default::default()
12033            },
12034            ..LanguageConfig::default()
12035        },
12036        Some(tree_sitter_rust::LANGUAGE.into()),
12037    )));
12038    update_test_language_settings(cx, |settings| {
12039        // Enable Prettier formatting for the same buffer, and ensure
12040        // LSP is called instead of Prettier.
12041        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12042    });
12043    let mut fake_servers = language_registry.register_fake_lsp(
12044        "Rust",
12045        FakeLspAdapter {
12046            capabilities: lsp::ServerCapabilities {
12047                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12048                ..Default::default()
12049            },
12050            ..Default::default()
12051        },
12052    );
12053
12054    let buffer = project
12055        .update(cx, |project, cx| {
12056            project.open_local_buffer(path!("/file.rs"), cx)
12057        })
12058        .await
12059        .unwrap();
12060
12061    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12062    let (editor, cx) = cx.add_window_view(|window, cx| {
12063        build_editor_with_project(project.clone(), buffer, window, cx)
12064    });
12065    editor.update_in(cx, |editor, window, cx| {
12066        editor.set_text("one\ntwo\nthree\n", window, cx)
12067    });
12068
12069    cx.executor().start_waiting();
12070    let fake_server = fake_servers.next().await.unwrap();
12071
12072    let format = editor
12073        .update_in(cx, |editor, window, cx| {
12074            editor.perform_format(
12075                project.clone(),
12076                FormatTrigger::Manual,
12077                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12078                window,
12079                cx,
12080            )
12081        })
12082        .unwrap();
12083    fake_server
12084        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12085            assert_eq!(
12086                params.text_document.uri,
12087                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12088            );
12089            assert_eq!(params.options.tab_size, 4);
12090            Ok(Some(vec![lsp::TextEdit::new(
12091                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12092                ", ".to_string(),
12093            )]))
12094        })
12095        .next()
12096        .await;
12097    cx.executor().start_waiting();
12098    format.await;
12099    assert_eq!(
12100        editor.update(cx, |editor, cx| editor.text(cx)),
12101        "one, two\nthree\n"
12102    );
12103
12104    editor.update_in(cx, |editor, window, cx| {
12105        editor.set_text("one\ntwo\nthree\n", window, cx)
12106    });
12107    // Ensure we don't lock if formatting hangs.
12108    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12109        move |params, _| async move {
12110            assert_eq!(
12111                params.text_document.uri,
12112                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12113            );
12114            futures::future::pending::<()>().await;
12115            unreachable!()
12116        },
12117    );
12118    let format = editor
12119        .update_in(cx, |editor, window, cx| {
12120            editor.perform_format(
12121                project,
12122                FormatTrigger::Manual,
12123                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12124                window,
12125                cx,
12126            )
12127        })
12128        .unwrap();
12129    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12130    cx.executor().start_waiting();
12131    format.await;
12132    assert_eq!(
12133        editor.update(cx, |editor, cx| editor.text(cx)),
12134        "one\ntwo\nthree\n"
12135    );
12136}
12137
12138#[gpui::test]
12139async fn test_multiple_formatters(cx: &mut TestAppContext) {
12140    init_test(cx, |settings| {
12141        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12142        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12143            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12144            Formatter::CodeAction("code-action-1".into()),
12145            Formatter::CodeAction("code-action-2".into()),
12146        ]))
12147    });
12148
12149    let fs = FakeFs::new(cx.executor());
12150    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12151        .await;
12152
12153    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12154    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12155    language_registry.add(rust_lang());
12156
12157    let mut fake_servers = language_registry.register_fake_lsp(
12158        "Rust",
12159        FakeLspAdapter {
12160            capabilities: lsp::ServerCapabilities {
12161                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12162                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12163                    commands: vec!["the-command-for-code-action-1".into()],
12164                    ..Default::default()
12165                }),
12166                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12167                ..Default::default()
12168            },
12169            ..Default::default()
12170        },
12171    );
12172
12173    let buffer = project
12174        .update(cx, |project, cx| {
12175            project.open_local_buffer(path!("/file.rs"), cx)
12176        })
12177        .await
12178        .unwrap();
12179
12180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12181    let (editor, cx) = cx.add_window_view(|window, cx| {
12182        build_editor_with_project(project.clone(), buffer, window, cx)
12183    });
12184
12185    cx.executor().start_waiting();
12186
12187    let fake_server = fake_servers.next().await.unwrap();
12188    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12189        move |_params, _| async move {
12190            Ok(Some(vec![lsp::TextEdit::new(
12191                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12192                "applied-formatting\n".to_string(),
12193            )]))
12194        },
12195    );
12196    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12197        move |params, _| async move {
12198            let requested_code_actions = params.context.only.expect("Expected code action request");
12199            assert_eq!(requested_code_actions.len(), 1);
12200
12201            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12202            let code_action = match requested_code_actions[0].as_str() {
12203                "code-action-1" => lsp::CodeAction {
12204                    kind: Some("code-action-1".into()),
12205                    edit: Some(lsp::WorkspaceEdit::new(
12206                        [(
12207                            uri,
12208                            vec![lsp::TextEdit::new(
12209                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12210                                "applied-code-action-1-edit\n".to_string(),
12211                            )],
12212                        )]
12213                        .into_iter()
12214                        .collect(),
12215                    )),
12216                    command: Some(lsp::Command {
12217                        command: "the-command-for-code-action-1".into(),
12218                        ..Default::default()
12219                    }),
12220                    ..Default::default()
12221                },
12222                "code-action-2" => lsp::CodeAction {
12223                    kind: Some("code-action-2".into()),
12224                    edit: Some(lsp::WorkspaceEdit::new(
12225                        [(
12226                            uri,
12227                            vec![lsp::TextEdit::new(
12228                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12229                                "applied-code-action-2-edit\n".to_string(),
12230                            )],
12231                        )]
12232                        .into_iter()
12233                        .collect(),
12234                    )),
12235                    ..Default::default()
12236                },
12237                req => panic!("Unexpected code action request: {:?}", req),
12238            };
12239            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12240                code_action,
12241            )]))
12242        },
12243    );
12244
12245    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12246        move |params, _| async move { Ok(params) }
12247    });
12248
12249    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12250    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12251        let fake = fake_server.clone();
12252        let lock = command_lock.clone();
12253        move |params, _| {
12254            assert_eq!(params.command, "the-command-for-code-action-1");
12255            let fake = fake.clone();
12256            let lock = lock.clone();
12257            async move {
12258                lock.lock().await;
12259                fake.server
12260                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12261                        label: None,
12262                        edit: lsp::WorkspaceEdit {
12263                            changes: Some(
12264                                [(
12265                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12266                                    vec![lsp::TextEdit {
12267                                        range: lsp::Range::new(
12268                                            lsp::Position::new(0, 0),
12269                                            lsp::Position::new(0, 0),
12270                                        ),
12271                                        new_text: "applied-code-action-1-command\n".into(),
12272                                    }],
12273                                )]
12274                                .into_iter()
12275                                .collect(),
12276                            ),
12277                            ..Default::default()
12278                        },
12279                    })
12280                    .await
12281                    .into_response()
12282                    .unwrap();
12283                Ok(Some(json!(null)))
12284            }
12285        }
12286    });
12287
12288    cx.executor().start_waiting();
12289    editor
12290        .update_in(cx, |editor, window, cx| {
12291            editor.perform_format(
12292                project.clone(),
12293                FormatTrigger::Manual,
12294                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12295                window,
12296                cx,
12297            )
12298        })
12299        .unwrap()
12300        .await;
12301    editor.update(cx, |editor, cx| {
12302        assert_eq!(
12303            editor.text(cx),
12304            r#"
12305                applied-code-action-2-edit
12306                applied-code-action-1-command
12307                applied-code-action-1-edit
12308                applied-formatting
12309                one
12310                two
12311                three
12312            "#
12313            .unindent()
12314        );
12315    });
12316
12317    editor.update_in(cx, |editor, window, cx| {
12318        editor.undo(&Default::default(), window, cx);
12319        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12320    });
12321
12322    // Perform a manual edit while waiting for an LSP command
12323    // that's being run as part of a formatting code action.
12324    let lock_guard = command_lock.lock().await;
12325    let format = editor
12326        .update_in(cx, |editor, window, cx| {
12327            editor.perform_format(
12328                project.clone(),
12329                FormatTrigger::Manual,
12330                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12331                window,
12332                cx,
12333            )
12334        })
12335        .unwrap();
12336    cx.run_until_parked();
12337    editor.update(cx, |editor, cx| {
12338        assert_eq!(
12339            editor.text(cx),
12340            r#"
12341                applied-code-action-1-edit
12342                applied-formatting
12343                one
12344                two
12345                three
12346            "#
12347            .unindent()
12348        );
12349
12350        editor.buffer.update(cx, |buffer, cx| {
12351            let ix = buffer.len(cx);
12352            buffer.edit([(ix..ix, "edited\n")], None, cx);
12353        });
12354    });
12355
12356    // Allow the LSP command to proceed. Because the buffer was edited,
12357    // the second code action will not be run.
12358    drop(lock_guard);
12359    format.await;
12360    editor.update_in(cx, |editor, window, cx| {
12361        assert_eq!(
12362            editor.text(cx),
12363            r#"
12364                applied-code-action-1-command
12365                applied-code-action-1-edit
12366                applied-formatting
12367                one
12368                two
12369                three
12370                edited
12371            "#
12372            .unindent()
12373        );
12374
12375        // The manual edit is undone first, because it is the last thing the user did
12376        // (even though the command completed afterwards).
12377        editor.undo(&Default::default(), window, cx);
12378        assert_eq!(
12379            editor.text(cx),
12380            r#"
12381                applied-code-action-1-command
12382                applied-code-action-1-edit
12383                applied-formatting
12384                one
12385                two
12386                three
12387            "#
12388            .unindent()
12389        );
12390
12391        // All the formatting (including the command, which completed after the manual edit)
12392        // is undone together.
12393        editor.undo(&Default::default(), window, cx);
12394        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12395    });
12396}
12397
12398#[gpui::test]
12399async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12400    init_test(cx, |settings| {
12401        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12402            settings::LanguageServerFormatterSpecifier::Current,
12403        )]))
12404    });
12405
12406    let fs = FakeFs::new(cx.executor());
12407    fs.insert_file(path!("/file.ts"), Default::default()).await;
12408
12409    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12410
12411    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12412    language_registry.add(Arc::new(Language::new(
12413        LanguageConfig {
12414            name: "TypeScript".into(),
12415            matcher: LanguageMatcher {
12416                path_suffixes: vec!["ts".to_string()],
12417                ..Default::default()
12418            },
12419            ..LanguageConfig::default()
12420        },
12421        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12422    )));
12423    update_test_language_settings(cx, |settings| {
12424        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12425    });
12426    let mut fake_servers = language_registry.register_fake_lsp(
12427        "TypeScript",
12428        FakeLspAdapter {
12429            capabilities: lsp::ServerCapabilities {
12430                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12431                ..Default::default()
12432            },
12433            ..Default::default()
12434        },
12435    );
12436
12437    let buffer = project
12438        .update(cx, |project, cx| {
12439            project.open_local_buffer(path!("/file.ts"), cx)
12440        })
12441        .await
12442        .unwrap();
12443
12444    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12445    let (editor, cx) = cx.add_window_view(|window, cx| {
12446        build_editor_with_project(project.clone(), buffer, window, cx)
12447    });
12448    editor.update_in(cx, |editor, window, cx| {
12449        editor.set_text(
12450            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12451            window,
12452            cx,
12453        )
12454    });
12455
12456    cx.executor().start_waiting();
12457    let fake_server = fake_servers.next().await.unwrap();
12458
12459    let format = editor
12460        .update_in(cx, |editor, window, cx| {
12461            editor.perform_code_action_kind(
12462                project.clone(),
12463                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12464                window,
12465                cx,
12466            )
12467        })
12468        .unwrap();
12469    fake_server
12470        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12471            assert_eq!(
12472                params.text_document.uri,
12473                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12474            );
12475            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12476                lsp::CodeAction {
12477                    title: "Organize Imports".to_string(),
12478                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12479                    edit: Some(lsp::WorkspaceEdit {
12480                        changes: Some(
12481                            [(
12482                                params.text_document.uri.clone(),
12483                                vec![lsp::TextEdit::new(
12484                                    lsp::Range::new(
12485                                        lsp::Position::new(1, 0),
12486                                        lsp::Position::new(2, 0),
12487                                    ),
12488                                    "".to_string(),
12489                                )],
12490                            )]
12491                            .into_iter()
12492                            .collect(),
12493                        ),
12494                        ..Default::default()
12495                    }),
12496                    ..Default::default()
12497                },
12498            )]))
12499        })
12500        .next()
12501        .await;
12502    cx.executor().start_waiting();
12503    format.await;
12504    assert_eq!(
12505        editor.update(cx, |editor, cx| editor.text(cx)),
12506        "import { a } from 'module';\n\nconst x = a;\n"
12507    );
12508
12509    editor.update_in(cx, |editor, window, cx| {
12510        editor.set_text(
12511            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12512            window,
12513            cx,
12514        )
12515    });
12516    // Ensure we don't lock if code action hangs.
12517    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12518        move |params, _| async move {
12519            assert_eq!(
12520                params.text_document.uri,
12521                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12522            );
12523            futures::future::pending::<()>().await;
12524            unreachable!()
12525        },
12526    );
12527    let format = editor
12528        .update_in(cx, |editor, window, cx| {
12529            editor.perform_code_action_kind(
12530                project,
12531                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12532                window,
12533                cx,
12534            )
12535        })
12536        .unwrap();
12537    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12538    cx.executor().start_waiting();
12539    format.await;
12540    assert_eq!(
12541        editor.update(cx, |editor, cx| editor.text(cx)),
12542        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12543    );
12544}
12545
12546#[gpui::test]
12547async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12548    init_test(cx, |_| {});
12549
12550    let mut cx = EditorLspTestContext::new_rust(
12551        lsp::ServerCapabilities {
12552            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12553            ..Default::default()
12554        },
12555        cx,
12556    )
12557    .await;
12558
12559    cx.set_state(indoc! {"
12560        one.twoˇ
12561    "});
12562
12563    // The format request takes a long time. When it completes, it inserts
12564    // a newline and an indent before the `.`
12565    cx.lsp
12566        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12567            let executor = cx.background_executor().clone();
12568            async move {
12569                executor.timer(Duration::from_millis(100)).await;
12570                Ok(Some(vec![lsp::TextEdit {
12571                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12572                    new_text: "\n    ".into(),
12573                }]))
12574            }
12575        });
12576
12577    // Submit a format request.
12578    let format_1 = cx
12579        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12580        .unwrap();
12581    cx.executor().run_until_parked();
12582
12583    // Submit a second format request.
12584    let format_2 = cx
12585        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12586        .unwrap();
12587    cx.executor().run_until_parked();
12588
12589    // Wait for both format requests to complete
12590    cx.executor().advance_clock(Duration::from_millis(200));
12591    cx.executor().start_waiting();
12592    format_1.await.unwrap();
12593    cx.executor().start_waiting();
12594    format_2.await.unwrap();
12595
12596    // The formatting edits only happens once.
12597    cx.assert_editor_state(indoc! {"
12598        one
12599            .twoˇ
12600    "});
12601}
12602
12603#[gpui::test]
12604async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12605    init_test(cx, |settings| {
12606        settings.defaults.formatter = Some(FormatterList::default())
12607    });
12608
12609    let mut cx = EditorLspTestContext::new_rust(
12610        lsp::ServerCapabilities {
12611            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12612            ..Default::default()
12613        },
12614        cx,
12615    )
12616    .await;
12617
12618    // Record which buffer changes have been sent to the language server
12619    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12620    cx.lsp
12621        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12622            let buffer_changes = buffer_changes.clone();
12623            move |params, _| {
12624                buffer_changes.lock().extend(
12625                    params
12626                        .content_changes
12627                        .into_iter()
12628                        .map(|e| (e.range.unwrap(), e.text)),
12629                );
12630            }
12631        });
12632
12633    #[cfg(target_os = "windows")]
12634    let line_ending = "\r\n";
12635    #[cfg(not(target_os = "windows"))]
12636    let line_ending = "\n";
12637
12638    // Handle formatting requests to the language server.
12639    cx.lsp
12640        .set_request_handler::<lsp::request::Formatting, _, _>({
12641            let buffer_changes = buffer_changes.clone();
12642            move |_, _| {
12643                let buffer_changes = buffer_changes.clone();
12644                // Insert blank lines between each line of the buffer.
12645                async move {
12646                    // When formatting is requested, trailing whitespace has already been stripped,
12647                    // and the trailing newline has already been added.
12648                    assert_eq!(
12649                        &buffer_changes.lock()[1..],
12650                        &[
12651                            (
12652                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12653                                "".into()
12654                            ),
12655                            (
12656                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12657                                "".into()
12658                            ),
12659                            (
12660                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12661                                line_ending.into()
12662                            ),
12663                        ]
12664                    );
12665
12666                    Ok(Some(vec![
12667                        lsp::TextEdit {
12668                            range: lsp::Range::new(
12669                                lsp::Position::new(1, 0),
12670                                lsp::Position::new(1, 0),
12671                            ),
12672                            new_text: line_ending.into(),
12673                        },
12674                        lsp::TextEdit {
12675                            range: lsp::Range::new(
12676                                lsp::Position::new(2, 0),
12677                                lsp::Position::new(2, 0),
12678                            ),
12679                            new_text: line_ending.into(),
12680                        },
12681                    ]))
12682                }
12683            }
12684        });
12685
12686    // Set up a buffer white some trailing whitespace and no trailing newline.
12687    cx.set_state(
12688        &[
12689            "one ",   //
12690            "twoˇ",   //
12691            "three ", //
12692            "four",   //
12693        ]
12694        .join("\n"),
12695    );
12696    cx.run_until_parked();
12697
12698    // Submit a format request.
12699    let format = cx
12700        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12701        .unwrap();
12702
12703    cx.run_until_parked();
12704    // After formatting the buffer, the trailing whitespace is stripped,
12705    // a newline is appended, and the edits provided by the language server
12706    // have been applied.
12707    format.await.unwrap();
12708
12709    cx.assert_editor_state(
12710        &[
12711            "one",   //
12712            "",      //
12713            "twoˇ",  //
12714            "",      //
12715            "three", //
12716            "four",  //
12717            "",      //
12718        ]
12719        .join("\n"),
12720    );
12721
12722    // Undoing the formatting undoes the trailing whitespace removal, the
12723    // trailing newline, and the LSP edits.
12724    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12725    cx.assert_editor_state(
12726        &[
12727            "one ",   //
12728            "twoˇ",   //
12729            "three ", //
12730            "four",   //
12731        ]
12732        .join("\n"),
12733    );
12734}
12735
12736#[gpui::test]
12737async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12738    cx: &mut TestAppContext,
12739) {
12740    init_test(cx, |_| {});
12741
12742    cx.update(|cx| {
12743        cx.update_global::<SettingsStore, _>(|settings, cx| {
12744            settings.update_user_settings(cx, |settings| {
12745                settings.editor.auto_signature_help = Some(true);
12746            });
12747        });
12748    });
12749
12750    let mut cx = EditorLspTestContext::new_rust(
12751        lsp::ServerCapabilities {
12752            signature_help_provider: Some(lsp::SignatureHelpOptions {
12753                ..Default::default()
12754            }),
12755            ..Default::default()
12756        },
12757        cx,
12758    )
12759    .await;
12760
12761    let language = Language::new(
12762        LanguageConfig {
12763            name: "Rust".into(),
12764            brackets: BracketPairConfig {
12765                pairs: vec![
12766                    BracketPair {
12767                        start: "{".to_string(),
12768                        end: "}".to_string(),
12769                        close: true,
12770                        surround: true,
12771                        newline: true,
12772                    },
12773                    BracketPair {
12774                        start: "(".to_string(),
12775                        end: ")".to_string(),
12776                        close: true,
12777                        surround: true,
12778                        newline: true,
12779                    },
12780                    BracketPair {
12781                        start: "/*".to_string(),
12782                        end: " */".to_string(),
12783                        close: true,
12784                        surround: true,
12785                        newline: true,
12786                    },
12787                    BracketPair {
12788                        start: "[".to_string(),
12789                        end: "]".to_string(),
12790                        close: false,
12791                        surround: false,
12792                        newline: true,
12793                    },
12794                    BracketPair {
12795                        start: "\"".to_string(),
12796                        end: "\"".to_string(),
12797                        close: true,
12798                        surround: true,
12799                        newline: false,
12800                    },
12801                    BracketPair {
12802                        start: "<".to_string(),
12803                        end: ">".to_string(),
12804                        close: false,
12805                        surround: true,
12806                        newline: true,
12807                    },
12808                ],
12809                ..Default::default()
12810            },
12811            autoclose_before: "})]".to_string(),
12812            ..Default::default()
12813        },
12814        Some(tree_sitter_rust::LANGUAGE.into()),
12815    );
12816    let language = Arc::new(language);
12817
12818    cx.language_registry().add(language.clone());
12819    cx.update_buffer(|buffer, cx| {
12820        buffer.set_language(Some(language), cx);
12821    });
12822
12823    cx.set_state(
12824        &r#"
12825            fn main() {
12826                sampleˇ
12827            }
12828        "#
12829        .unindent(),
12830    );
12831
12832    cx.update_editor(|editor, window, cx| {
12833        editor.handle_input("(", window, cx);
12834    });
12835    cx.assert_editor_state(
12836        &"
12837            fn main() {
12838                sample(ˇ)
12839            }
12840        "
12841        .unindent(),
12842    );
12843
12844    let mocked_response = lsp::SignatureHelp {
12845        signatures: vec![lsp::SignatureInformation {
12846            label: "fn sample(param1: u8, param2: u8)".to_string(),
12847            documentation: None,
12848            parameters: Some(vec![
12849                lsp::ParameterInformation {
12850                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12851                    documentation: None,
12852                },
12853                lsp::ParameterInformation {
12854                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12855                    documentation: None,
12856                },
12857            ]),
12858            active_parameter: None,
12859        }],
12860        active_signature: Some(0),
12861        active_parameter: Some(0),
12862    };
12863    handle_signature_help_request(&mut cx, mocked_response).await;
12864
12865    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12866        .await;
12867
12868    cx.editor(|editor, _, _| {
12869        let signature_help_state = editor.signature_help_state.popover().cloned();
12870        let signature = signature_help_state.unwrap();
12871        assert_eq!(
12872            signature.signatures[signature.current_signature].label,
12873            "fn sample(param1: u8, param2: u8)"
12874        );
12875    });
12876}
12877
12878#[gpui::test]
12879async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12880    init_test(cx, |_| {});
12881
12882    cx.update(|cx| {
12883        cx.update_global::<SettingsStore, _>(|settings, cx| {
12884            settings.update_user_settings(cx, |settings| {
12885                settings.editor.auto_signature_help = Some(false);
12886                settings.editor.show_signature_help_after_edits = Some(false);
12887            });
12888        });
12889    });
12890
12891    let mut cx = EditorLspTestContext::new_rust(
12892        lsp::ServerCapabilities {
12893            signature_help_provider: Some(lsp::SignatureHelpOptions {
12894                ..Default::default()
12895            }),
12896            ..Default::default()
12897        },
12898        cx,
12899    )
12900    .await;
12901
12902    let language = Language::new(
12903        LanguageConfig {
12904            name: "Rust".into(),
12905            brackets: BracketPairConfig {
12906                pairs: vec![
12907                    BracketPair {
12908                        start: "{".to_string(),
12909                        end: "}".to_string(),
12910                        close: true,
12911                        surround: true,
12912                        newline: true,
12913                    },
12914                    BracketPair {
12915                        start: "(".to_string(),
12916                        end: ")".to_string(),
12917                        close: true,
12918                        surround: true,
12919                        newline: true,
12920                    },
12921                    BracketPair {
12922                        start: "/*".to_string(),
12923                        end: " */".to_string(),
12924                        close: true,
12925                        surround: true,
12926                        newline: true,
12927                    },
12928                    BracketPair {
12929                        start: "[".to_string(),
12930                        end: "]".to_string(),
12931                        close: false,
12932                        surround: false,
12933                        newline: true,
12934                    },
12935                    BracketPair {
12936                        start: "\"".to_string(),
12937                        end: "\"".to_string(),
12938                        close: true,
12939                        surround: true,
12940                        newline: false,
12941                    },
12942                    BracketPair {
12943                        start: "<".to_string(),
12944                        end: ">".to_string(),
12945                        close: false,
12946                        surround: true,
12947                        newline: true,
12948                    },
12949                ],
12950                ..Default::default()
12951            },
12952            autoclose_before: "})]".to_string(),
12953            ..Default::default()
12954        },
12955        Some(tree_sitter_rust::LANGUAGE.into()),
12956    );
12957    let language = Arc::new(language);
12958
12959    cx.language_registry().add(language.clone());
12960    cx.update_buffer(|buffer, cx| {
12961        buffer.set_language(Some(language), cx);
12962    });
12963
12964    // Ensure that signature_help is not called when no signature help is enabled.
12965    cx.set_state(
12966        &r#"
12967            fn main() {
12968                sampleˇ
12969            }
12970        "#
12971        .unindent(),
12972    );
12973    cx.update_editor(|editor, window, cx| {
12974        editor.handle_input("(", window, cx);
12975    });
12976    cx.assert_editor_state(
12977        &"
12978            fn main() {
12979                sample(ˇ)
12980            }
12981        "
12982        .unindent(),
12983    );
12984    cx.editor(|editor, _, _| {
12985        assert!(editor.signature_help_state.task().is_none());
12986    });
12987
12988    let mocked_response = lsp::SignatureHelp {
12989        signatures: vec![lsp::SignatureInformation {
12990            label: "fn sample(param1: u8, param2: u8)".to_string(),
12991            documentation: None,
12992            parameters: Some(vec![
12993                lsp::ParameterInformation {
12994                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12995                    documentation: None,
12996                },
12997                lsp::ParameterInformation {
12998                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12999                    documentation: None,
13000                },
13001            ]),
13002            active_parameter: None,
13003        }],
13004        active_signature: Some(0),
13005        active_parameter: Some(0),
13006    };
13007
13008    // Ensure that signature_help is called when enabled afte edits
13009    cx.update(|_, cx| {
13010        cx.update_global::<SettingsStore, _>(|settings, cx| {
13011            settings.update_user_settings(cx, |settings| {
13012                settings.editor.auto_signature_help = Some(false);
13013                settings.editor.show_signature_help_after_edits = Some(true);
13014            });
13015        });
13016    });
13017    cx.set_state(
13018        &r#"
13019            fn main() {
13020                sampleˇ
13021            }
13022        "#
13023        .unindent(),
13024    );
13025    cx.update_editor(|editor, window, cx| {
13026        editor.handle_input("(", window, cx);
13027    });
13028    cx.assert_editor_state(
13029        &"
13030            fn main() {
13031                sample(ˇ)
13032            }
13033        "
13034        .unindent(),
13035    );
13036    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13037    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13038        .await;
13039    cx.update_editor(|editor, _, _| {
13040        let signature_help_state = editor.signature_help_state.popover().cloned();
13041        assert!(signature_help_state.is_some());
13042        let signature = signature_help_state.unwrap();
13043        assert_eq!(
13044            signature.signatures[signature.current_signature].label,
13045            "fn sample(param1: u8, param2: u8)"
13046        );
13047        editor.signature_help_state = SignatureHelpState::default();
13048    });
13049
13050    // Ensure that signature_help is called when auto signature help override is enabled
13051    cx.update(|_, cx| {
13052        cx.update_global::<SettingsStore, _>(|settings, cx| {
13053            settings.update_user_settings(cx, |settings| {
13054                settings.editor.auto_signature_help = Some(true);
13055                settings.editor.show_signature_help_after_edits = Some(false);
13056            });
13057        });
13058    });
13059    cx.set_state(
13060        &r#"
13061            fn main() {
13062                sampleˇ
13063            }
13064        "#
13065        .unindent(),
13066    );
13067    cx.update_editor(|editor, window, cx| {
13068        editor.handle_input("(", window, cx);
13069    });
13070    cx.assert_editor_state(
13071        &"
13072            fn main() {
13073                sample(ˇ)
13074            }
13075        "
13076        .unindent(),
13077    );
13078    handle_signature_help_request(&mut cx, mocked_response).await;
13079    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13080        .await;
13081    cx.editor(|editor, _, _| {
13082        let signature_help_state = editor.signature_help_state.popover().cloned();
13083        assert!(signature_help_state.is_some());
13084        let signature = signature_help_state.unwrap();
13085        assert_eq!(
13086            signature.signatures[signature.current_signature].label,
13087            "fn sample(param1: u8, param2: u8)"
13088        );
13089    });
13090}
13091
13092#[gpui::test]
13093async fn test_signature_help(cx: &mut TestAppContext) {
13094    init_test(cx, |_| {});
13095    cx.update(|cx| {
13096        cx.update_global::<SettingsStore, _>(|settings, cx| {
13097            settings.update_user_settings(cx, |settings| {
13098                settings.editor.auto_signature_help = Some(true);
13099            });
13100        });
13101    });
13102
13103    let mut cx = EditorLspTestContext::new_rust(
13104        lsp::ServerCapabilities {
13105            signature_help_provider: Some(lsp::SignatureHelpOptions {
13106                ..Default::default()
13107            }),
13108            ..Default::default()
13109        },
13110        cx,
13111    )
13112    .await;
13113
13114    // A test that directly calls `show_signature_help`
13115    cx.update_editor(|editor, window, cx| {
13116        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13117    });
13118
13119    let mocked_response = lsp::SignatureHelp {
13120        signatures: vec![lsp::SignatureInformation {
13121            label: "fn sample(param1: u8, param2: u8)".to_string(),
13122            documentation: None,
13123            parameters: Some(vec![
13124                lsp::ParameterInformation {
13125                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13126                    documentation: None,
13127                },
13128                lsp::ParameterInformation {
13129                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13130                    documentation: None,
13131                },
13132            ]),
13133            active_parameter: None,
13134        }],
13135        active_signature: Some(0),
13136        active_parameter: Some(0),
13137    };
13138    handle_signature_help_request(&mut cx, mocked_response).await;
13139
13140    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13141        .await;
13142
13143    cx.editor(|editor, _, _| {
13144        let signature_help_state = editor.signature_help_state.popover().cloned();
13145        assert!(signature_help_state.is_some());
13146        let signature = signature_help_state.unwrap();
13147        assert_eq!(
13148            signature.signatures[signature.current_signature].label,
13149            "fn sample(param1: u8, param2: u8)"
13150        );
13151    });
13152
13153    // When exiting outside from inside the brackets, `signature_help` is closed.
13154    cx.set_state(indoc! {"
13155        fn main() {
13156            sample(ˇ);
13157        }
13158
13159        fn sample(param1: u8, param2: u8) {}
13160    "});
13161
13162    cx.update_editor(|editor, window, cx| {
13163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13164            s.select_ranges([0..0])
13165        });
13166    });
13167
13168    let mocked_response = lsp::SignatureHelp {
13169        signatures: Vec::new(),
13170        active_signature: None,
13171        active_parameter: None,
13172    };
13173    handle_signature_help_request(&mut cx, mocked_response).await;
13174
13175    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13176        .await;
13177
13178    cx.editor(|editor, _, _| {
13179        assert!(!editor.signature_help_state.is_shown());
13180    });
13181
13182    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13183    cx.set_state(indoc! {"
13184        fn main() {
13185            sample(ˇ);
13186        }
13187
13188        fn sample(param1: u8, param2: u8) {}
13189    "});
13190
13191    let mocked_response = lsp::SignatureHelp {
13192        signatures: vec![lsp::SignatureInformation {
13193            label: "fn sample(param1: u8, param2: u8)".to_string(),
13194            documentation: None,
13195            parameters: Some(vec![
13196                lsp::ParameterInformation {
13197                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13198                    documentation: None,
13199                },
13200                lsp::ParameterInformation {
13201                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13202                    documentation: None,
13203                },
13204            ]),
13205            active_parameter: None,
13206        }],
13207        active_signature: Some(0),
13208        active_parameter: Some(0),
13209    };
13210    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13212        .await;
13213    cx.editor(|editor, _, _| {
13214        assert!(editor.signature_help_state.is_shown());
13215    });
13216
13217    // Restore the popover with more parameter input
13218    cx.set_state(indoc! {"
13219        fn main() {
13220            sample(param1, param2ˇ);
13221        }
13222
13223        fn sample(param1: u8, param2: u8) {}
13224    "});
13225
13226    let mocked_response = lsp::SignatureHelp {
13227        signatures: vec![lsp::SignatureInformation {
13228            label: "fn sample(param1: u8, param2: u8)".to_string(),
13229            documentation: None,
13230            parameters: Some(vec![
13231                lsp::ParameterInformation {
13232                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13233                    documentation: None,
13234                },
13235                lsp::ParameterInformation {
13236                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13237                    documentation: None,
13238                },
13239            ]),
13240            active_parameter: None,
13241        }],
13242        active_signature: Some(0),
13243        active_parameter: Some(1),
13244    };
13245    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13246    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13247        .await;
13248
13249    // When selecting a range, the popover is gone.
13250    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13251    cx.update_editor(|editor, window, cx| {
13252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13253            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13254        })
13255    });
13256    cx.assert_editor_state(indoc! {"
13257        fn main() {
13258            sample(param1, «ˇparam2»);
13259        }
13260
13261        fn sample(param1: u8, param2: u8) {}
13262    "});
13263    cx.editor(|editor, _, _| {
13264        assert!(!editor.signature_help_state.is_shown());
13265    });
13266
13267    // When unselecting again, the popover is back if within the brackets.
13268    cx.update_editor(|editor, window, cx| {
13269        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13270            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13271        })
13272    });
13273    cx.assert_editor_state(indoc! {"
13274        fn main() {
13275            sample(param1, ˇparam2);
13276        }
13277
13278        fn sample(param1: u8, param2: u8) {}
13279    "});
13280    handle_signature_help_request(&mut cx, mocked_response).await;
13281    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13282        .await;
13283    cx.editor(|editor, _, _| {
13284        assert!(editor.signature_help_state.is_shown());
13285    });
13286
13287    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13288    cx.update_editor(|editor, window, cx| {
13289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13290            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13291            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13292        })
13293    });
13294    cx.assert_editor_state(indoc! {"
13295        fn main() {
13296            sample(param1, ˇparam2);
13297        }
13298
13299        fn sample(param1: u8, param2: u8) {}
13300    "});
13301
13302    let mocked_response = lsp::SignatureHelp {
13303        signatures: vec![lsp::SignatureInformation {
13304            label: "fn sample(param1: u8, param2: u8)".to_string(),
13305            documentation: None,
13306            parameters: Some(vec![
13307                lsp::ParameterInformation {
13308                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13309                    documentation: None,
13310                },
13311                lsp::ParameterInformation {
13312                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13313                    documentation: None,
13314                },
13315            ]),
13316            active_parameter: None,
13317        }],
13318        active_signature: Some(0),
13319        active_parameter: Some(1),
13320    };
13321    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13322    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13323        .await;
13324    cx.update_editor(|editor, _, cx| {
13325        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13326    });
13327    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13328        .await;
13329    cx.update_editor(|editor, window, cx| {
13330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13331            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13332        })
13333    });
13334    cx.assert_editor_state(indoc! {"
13335        fn main() {
13336            sample(param1, «ˇparam2»);
13337        }
13338
13339        fn sample(param1: u8, param2: u8) {}
13340    "});
13341    cx.update_editor(|editor, window, cx| {
13342        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13343            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13344        })
13345    });
13346    cx.assert_editor_state(indoc! {"
13347        fn main() {
13348            sample(param1, ˇparam2);
13349        }
13350
13351        fn sample(param1: u8, param2: u8) {}
13352    "});
13353    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13354        .await;
13355}
13356
13357#[gpui::test]
13358async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13359    init_test(cx, |_| {});
13360
13361    let mut cx = EditorLspTestContext::new_rust(
13362        lsp::ServerCapabilities {
13363            signature_help_provider: Some(lsp::SignatureHelpOptions {
13364                ..Default::default()
13365            }),
13366            ..Default::default()
13367        },
13368        cx,
13369    )
13370    .await;
13371
13372    cx.set_state(indoc! {"
13373        fn main() {
13374            overloadedˇ
13375        }
13376    "});
13377
13378    cx.update_editor(|editor, window, cx| {
13379        editor.handle_input("(", window, cx);
13380        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13381    });
13382
13383    // Mock response with 3 signatures
13384    let mocked_response = lsp::SignatureHelp {
13385        signatures: vec![
13386            lsp::SignatureInformation {
13387                label: "fn overloaded(x: i32)".to_string(),
13388                documentation: None,
13389                parameters: Some(vec![lsp::ParameterInformation {
13390                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13391                    documentation: None,
13392                }]),
13393                active_parameter: None,
13394            },
13395            lsp::SignatureInformation {
13396                label: "fn overloaded(x: i32, y: i32)".to_string(),
13397                documentation: None,
13398                parameters: Some(vec![
13399                    lsp::ParameterInformation {
13400                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13401                        documentation: None,
13402                    },
13403                    lsp::ParameterInformation {
13404                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13405                        documentation: None,
13406                    },
13407                ]),
13408                active_parameter: None,
13409            },
13410            lsp::SignatureInformation {
13411                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13412                documentation: None,
13413                parameters: Some(vec![
13414                    lsp::ParameterInformation {
13415                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13416                        documentation: None,
13417                    },
13418                    lsp::ParameterInformation {
13419                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13420                        documentation: None,
13421                    },
13422                    lsp::ParameterInformation {
13423                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13424                        documentation: None,
13425                    },
13426                ]),
13427                active_parameter: None,
13428            },
13429        ],
13430        active_signature: Some(1),
13431        active_parameter: Some(0),
13432    };
13433    handle_signature_help_request(&mut cx, mocked_response).await;
13434
13435    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13436        .await;
13437
13438    // Verify we have multiple signatures and the right one is selected
13439    cx.editor(|editor, _, _| {
13440        let popover = editor.signature_help_state.popover().cloned().unwrap();
13441        assert_eq!(popover.signatures.len(), 3);
13442        // active_signature was 1, so that should be the current
13443        assert_eq!(popover.current_signature, 1);
13444        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13445        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13446        assert_eq!(
13447            popover.signatures[2].label,
13448            "fn overloaded(x: i32, y: i32, z: i32)"
13449        );
13450    });
13451
13452    // Test navigation functionality
13453    cx.update_editor(|editor, window, cx| {
13454        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13455    });
13456
13457    cx.editor(|editor, _, _| {
13458        let popover = editor.signature_help_state.popover().cloned().unwrap();
13459        assert_eq!(popover.current_signature, 2);
13460    });
13461
13462    // Test wrap around
13463    cx.update_editor(|editor, window, cx| {
13464        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13465    });
13466
13467    cx.editor(|editor, _, _| {
13468        let popover = editor.signature_help_state.popover().cloned().unwrap();
13469        assert_eq!(popover.current_signature, 0);
13470    });
13471
13472    // Test previous navigation
13473    cx.update_editor(|editor, window, cx| {
13474        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13475    });
13476
13477    cx.editor(|editor, _, _| {
13478        let popover = editor.signature_help_state.popover().cloned().unwrap();
13479        assert_eq!(popover.current_signature, 2);
13480    });
13481}
13482
13483#[gpui::test]
13484async fn test_completion_mode(cx: &mut TestAppContext) {
13485    init_test(cx, |_| {});
13486    let mut cx = EditorLspTestContext::new_rust(
13487        lsp::ServerCapabilities {
13488            completion_provider: Some(lsp::CompletionOptions {
13489                resolve_provider: Some(true),
13490                ..Default::default()
13491            }),
13492            ..Default::default()
13493        },
13494        cx,
13495    )
13496    .await;
13497
13498    struct Run {
13499        run_description: &'static str,
13500        initial_state: String,
13501        buffer_marked_text: String,
13502        completion_label: &'static str,
13503        completion_text: &'static str,
13504        expected_with_insert_mode: String,
13505        expected_with_replace_mode: String,
13506        expected_with_replace_subsequence_mode: String,
13507        expected_with_replace_suffix_mode: String,
13508    }
13509
13510    let runs = [
13511        Run {
13512            run_description: "Start of word matches completion text",
13513            initial_state: "before ediˇ after".into(),
13514            buffer_marked_text: "before <edi|> after".into(),
13515            completion_label: "editor",
13516            completion_text: "editor",
13517            expected_with_insert_mode: "before editorˇ after".into(),
13518            expected_with_replace_mode: "before editorˇ after".into(),
13519            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13520            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13521        },
13522        Run {
13523            run_description: "Accept same text at the middle of the word",
13524            initial_state: "before ediˇtor after".into(),
13525            buffer_marked_text: "before <edi|tor> after".into(),
13526            completion_label: "editor",
13527            completion_text: "editor",
13528            expected_with_insert_mode: "before editorˇtor after".into(),
13529            expected_with_replace_mode: "before editorˇ after".into(),
13530            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13531            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13532        },
13533        Run {
13534            run_description: "End of word matches completion text -- cursor at end",
13535            initial_state: "before torˇ after".into(),
13536            buffer_marked_text: "before <tor|> after".into(),
13537            completion_label: "editor",
13538            completion_text: "editor",
13539            expected_with_insert_mode: "before editorˇ after".into(),
13540            expected_with_replace_mode: "before editorˇ after".into(),
13541            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13542            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13543        },
13544        Run {
13545            run_description: "End of word matches completion text -- cursor at start",
13546            initial_state: "before ˇtor after".into(),
13547            buffer_marked_text: "before <|tor> after".into(),
13548            completion_label: "editor",
13549            completion_text: "editor",
13550            expected_with_insert_mode: "before editorˇtor after".into(),
13551            expected_with_replace_mode: "before editorˇ after".into(),
13552            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13553            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13554        },
13555        Run {
13556            run_description: "Prepend text containing whitespace",
13557            initial_state: "pˇfield: bool".into(),
13558            buffer_marked_text: "<p|field>: bool".into(),
13559            completion_label: "pub ",
13560            completion_text: "pub ",
13561            expected_with_insert_mode: "pub ˇfield: bool".into(),
13562            expected_with_replace_mode: "pub ˇ: bool".into(),
13563            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13564            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13565        },
13566        Run {
13567            run_description: "Add element to start of list",
13568            initial_state: "[element_ˇelement_2]".into(),
13569            buffer_marked_text: "[<element_|element_2>]".into(),
13570            completion_label: "element_1",
13571            completion_text: "element_1",
13572            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13573            expected_with_replace_mode: "[element_1ˇ]".into(),
13574            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13575            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13576        },
13577        Run {
13578            run_description: "Add element to start of list -- first and second elements are equal",
13579            initial_state: "[elˇelement]".into(),
13580            buffer_marked_text: "[<el|element>]".into(),
13581            completion_label: "element",
13582            completion_text: "element",
13583            expected_with_insert_mode: "[elementˇelement]".into(),
13584            expected_with_replace_mode: "[elementˇ]".into(),
13585            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13586            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13587        },
13588        Run {
13589            run_description: "Ends with matching suffix",
13590            initial_state: "SubˇError".into(),
13591            buffer_marked_text: "<Sub|Error>".into(),
13592            completion_label: "SubscriptionError",
13593            completion_text: "SubscriptionError",
13594            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13595            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13596            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13597            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13598        },
13599        Run {
13600            run_description: "Suffix is a subsequence -- contiguous",
13601            initial_state: "SubˇErr".into(),
13602            buffer_marked_text: "<Sub|Err>".into(),
13603            completion_label: "SubscriptionError",
13604            completion_text: "SubscriptionError",
13605            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13606            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13607            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13608            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13609        },
13610        Run {
13611            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13612            initial_state: "Suˇscrirr".into(),
13613            buffer_marked_text: "<Su|scrirr>".into(),
13614            completion_label: "SubscriptionError",
13615            completion_text: "SubscriptionError",
13616            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13617            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13618            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13619            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13620        },
13621        Run {
13622            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13623            initial_state: "foo(indˇix)".into(),
13624            buffer_marked_text: "foo(<ind|ix>)".into(),
13625            completion_label: "node_index",
13626            completion_text: "node_index",
13627            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13628            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13629            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13630            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13631        },
13632        Run {
13633            run_description: "Replace range ends before cursor - should extend to cursor",
13634            initial_state: "before editˇo after".into(),
13635            buffer_marked_text: "before <{ed}>it|o after".into(),
13636            completion_label: "editor",
13637            completion_text: "editor",
13638            expected_with_insert_mode: "before editorˇo after".into(),
13639            expected_with_replace_mode: "before editorˇo after".into(),
13640            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13641            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13642        },
13643        Run {
13644            run_description: "Uses label for suffix matching",
13645            initial_state: "before ediˇtor after".into(),
13646            buffer_marked_text: "before <edi|tor> after".into(),
13647            completion_label: "editor",
13648            completion_text: "editor()",
13649            expected_with_insert_mode: "before editor()ˇtor after".into(),
13650            expected_with_replace_mode: "before editor()ˇ after".into(),
13651            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13652            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13653        },
13654        Run {
13655            run_description: "Case insensitive subsequence and suffix matching",
13656            initial_state: "before EDiˇtoR after".into(),
13657            buffer_marked_text: "before <EDi|toR> after".into(),
13658            completion_label: "editor",
13659            completion_text: "editor",
13660            expected_with_insert_mode: "before editorˇtoR after".into(),
13661            expected_with_replace_mode: "before editorˇ after".into(),
13662            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13663            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13664        },
13665    ];
13666
13667    for run in runs {
13668        let run_variations = [
13669            (LspInsertMode::Insert, run.expected_with_insert_mode),
13670            (LspInsertMode::Replace, run.expected_with_replace_mode),
13671            (
13672                LspInsertMode::ReplaceSubsequence,
13673                run.expected_with_replace_subsequence_mode,
13674            ),
13675            (
13676                LspInsertMode::ReplaceSuffix,
13677                run.expected_with_replace_suffix_mode,
13678            ),
13679        ];
13680
13681        for (lsp_insert_mode, expected_text) in run_variations {
13682            eprintln!(
13683                "run = {:?}, mode = {lsp_insert_mode:.?}",
13684                run.run_description,
13685            );
13686
13687            update_test_language_settings(&mut cx, |settings| {
13688                settings.defaults.completions = Some(CompletionSettingsContent {
13689                    lsp_insert_mode: Some(lsp_insert_mode),
13690                    words: Some(WordsCompletionMode::Disabled),
13691                    words_min_length: Some(0),
13692                    ..Default::default()
13693                });
13694            });
13695
13696            cx.set_state(&run.initial_state);
13697            cx.update_editor(|editor, window, cx| {
13698                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13699            });
13700
13701            let counter = Arc::new(AtomicUsize::new(0));
13702            handle_completion_request_with_insert_and_replace(
13703                &mut cx,
13704                &run.buffer_marked_text,
13705                vec![(run.completion_label, run.completion_text)],
13706                counter.clone(),
13707            )
13708            .await;
13709            cx.condition(|editor, _| editor.context_menu_visible())
13710                .await;
13711            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13712
13713            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13714                editor
13715                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13716                    .unwrap()
13717            });
13718            cx.assert_editor_state(&expected_text);
13719            handle_resolve_completion_request(&mut cx, None).await;
13720            apply_additional_edits.await.unwrap();
13721        }
13722    }
13723}
13724
13725#[gpui::test]
13726async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13727    init_test(cx, |_| {});
13728    let mut cx = EditorLspTestContext::new_rust(
13729        lsp::ServerCapabilities {
13730            completion_provider: Some(lsp::CompletionOptions {
13731                resolve_provider: Some(true),
13732                ..Default::default()
13733            }),
13734            ..Default::default()
13735        },
13736        cx,
13737    )
13738    .await;
13739
13740    let initial_state = "SubˇError";
13741    let buffer_marked_text = "<Sub|Error>";
13742    let completion_text = "SubscriptionError";
13743    let expected_with_insert_mode = "SubscriptionErrorˇError";
13744    let expected_with_replace_mode = "SubscriptionErrorˇ";
13745
13746    update_test_language_settings(&mut cx, |settings| {
13747        settings.defaults.completions = Some(CompletionSettingsContent {
13748            words: Some(WordsCompletionMode::Disabled),
13749            words_min_length: Some(0),
13750            // set the opposite here to ensure that the action is overriding the default behavior
13751            lsp_insert_mode: Some(LspInsertMode::Insert),
13752            ..Default::default()
13753        });
13754    });
13755
13756    cx.set_state(initial_state);
13757    cx.update_editor(|editor, window, cx| {
13758        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13759    });
13760
13761    let counter = Arc::new(AtomicUsize::new(0));
13762    handle_completion_request_with_insert_and_replace(
13763        &mut cx,
13764        buffer_marked_text,
13765        vec![(completion_text, completion_text)],
13766        counter.clone(),
13767    )
13768    .await;
13769    cx.condition(|editor, _| editor.context_menu_visible())
13770        .await;
13771    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13772
13773    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13774        editor
13775            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13776            .unwrap()
13777    });
13778    cx.assert_editor_state(expected_with_replace_mode);
13779    handle_resolve_completion_request(&mut cx, None).await;
13780    apply_additional_edits.await.unwrap();
13781
13782    update_test_language_settings(&mut cx, |settings| {
13783        settings.defaults.completions = Some(CompletionSettingsContent {
13784            words: Some(WordsCompletionMode::Disabled),
13785            words_min_length: Some(0),
13786            // set the opposite here to ensure that the action is overriding the default behavior
13787            lsp_insert_mode: Some(LspInsertMode::Replace),
13788            ..Default::default()
13789        });
13790    });
13791
13792    cx.set_state(initial_state);
13793    cx.update_editor(|editor, window, cx| {
13794        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13795    });
13796    handle_completion_request_with_insert_and_replace(
13797        &mut cx,
13798        buffer_marked_text,
13799        vec![(completion_text, completion_text)],
13800        counter.clone(),
13801    )
13802    .await;
13803    cx.condition(|editor, _| editor.context_menu_visible())
13804        .await;
13805    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13806
13807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13808        editor
13809            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13810            .unwrap()
13811    });
13812    cx.assert_editor_state(expected_with_insert_mode);
13813    handle_resolve_completion_request(&mut cx, None).await;
13814    apply_additional_edits.await.unwrap();
13815}
13816
13817#[gpui::test]
13818async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13819    init_test(cx, |_| {});
13820    let mut cx = EditorLspTestContext::new_rust(
13821        lsp::ServerCapabilities {
13822            completion_provider: Some(lsp::CompletionOptions {
13823                resolve_provider: Some(true),
13824                ..Default::default()
13825            }),
13826            ..Default::default()
13827        },
13828        cx,
13829    )
13830    .await;
13831
13832    // scenario: surrounding text matches completion text
13833    let completion_text = "to_offset";
13834    let initial_state = indoc! {"
13835        1. buf.to_offˇsuffix
13836        2. buf.to_offˇsuf
13837        3. buf.to_offˇfix
13838        4. buf.to_offˇ
13839        5. into_offˇensive
13840        6. ˇsuffix
13841        7. let ˇ //
13842        8. aaˇzz
13843        9. buf.to_off«zzzzzˇ»suffix
13844        10. buf.«ˇzzzzz»suffix
13845        11. to_off«ˇzzzzz»
13846
13847        buf.to_offˇsuffix  // newest cursor
13848    "};
13849    let completion_marked_buffer = indoc! {"
13850        1. buf.to_offsuffix
13851        2. buf.to_offsuf
13852        3. buf.to_offfix
13853        4. buf.to_off
13854        5. into_offensive
13855        6. suffix
13856        7. let  //
13857        8. aazz
13858        9. buf.to_offzzzzzsuffix
13859        10. buf.zzzzzsuffix
13860        11. to_offzzzzz
13861
13862        buf.<to_off|suffix>  // newest cursor
13863    "};
13864    let expected = indoc! {"
13865        1. buf.to_offsetˇ
13866        2. buf.to_offsetˇsuf
13867        3. buf.to_offsetˇfix
13868        4. buf.to_offsetˇ
13869        5. into_offsetˇensive
13870        6. to_offsetˇsuffix
13871        7. let to_offsetˇ //
13872        8. aato_offsetˇzz
13873        9. buf.to_offsetˇ
13874        10. buf.to_offsetˇsuffix
13875        11. to_offsetˇ
13876
13877        buf.to_offsetˇ  // newest cursor
13878    "};
13879    cx.set_state(initial_state);
13880    cx.update_editor(|editor, window, cx| {
13881        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13882    });
13883    handle_completion_request_with_insert_and_replace(
13884        &mut cx,
13885        completion_marked_buffer,
13886        vec![(completion_text, completion_text)],
13887        Arc::new(AtomicUsize::new(0)),
13888    )
13889    .await;
13890    cx.condition(|editor, _| editor.context_menu_visible())
13891        .await;
13892    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13893        editor
13894            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13895            .unwrap()
13896    });
13897    cx.assert_editor_state(expected);
13898    handle_resolve_completion_request(&mut cx, None).await;
13899    apply_additional_edits.await.unwrap();
13900
13901    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13902    let completion_text = "foo_and_bar";
13903    let initial_state = indoc! {"
13904        1. ooanbˇ
13905        2. zooanbˇ
13906        3. ooanbˇz
13907        4. zooanbˇz
13908        5. ooanˇ
13909        6. oanbˇ
13910
13911        ooanbˇ
13912    "};
13913    let completion_marked_buffer = indoc! {"
13914        1. ooanb
13915        2. zooanb
13916        3. ooanbz
13917        4. zooanbz
13918        5. ooan
13919        6. oanb
13920
13921        <ooanb|>
13922    "};
13923    let expected = indoc! {"
13924        1. foo_and_barˇ
13925        2. zfoo_and_barˇ
13926        3. foo_and_barˇz
13927        4. zfoo_and_barˇz
13928        5. ooanfoo_and_barˇ
13929        6. oanbfoo_and_barˇ
13930
13931        foo_and_barˇ
13932    "};
13933    cx.set_state(initial_state);
13934    cx.update_editor(|editor, window, cx| {
13935        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13936    });
13937    handle_completion_request_with_insert_and_replace(
13938        &mut cx,
13939        completion_marked_buffer,
13940        vec![(completion_text, completion_text)],
13941        Arc::new(AtomicUsize::new(0)),
13942    )
13943    .await;
13944    cx.condition(|editor, _| editor.context_menu_visible())
13945        .await;
13946    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13947        editor
13948            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13949            .unwrap()
13950    });
13951    cx.assert_editor_state(expected);
13952    handle_resolve_completion_request(&mut cx, None).await;
13953    apply_additional_edits.await.unwrap();
13954
13955    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13956    // (expects the same as if it was inserted at the end)
13957    let completion_text = "foo_and_bar";
13958    let initial_state = indoc! {"
13959        1. ooˇanb
13960        2. zooˇanb
13961        3. ooˇanbz
13962        4. zooˇanbz
13963
13964        ooˇanb
13965    "};
13966    let completion_marked_buffer = indoc! {"
13967        1. ooanb
13968        2. zooanb
13969        3. ooanbz
13970        4. zooanbz
13971
13972        <oo|anb>
13973    "};
13974    let expected = indoc! {"
13975        1. foo_and_barˇ
13976        2. zfoo_and_barˇ
13977        3. foo_and_barˇz
13978        4. zfoo_and_barˇz
13979
13980        foo_and_barˇ
13981    "};
13982    cx.set_state(initial_state);
13983    cx.update_editor(|editor, window, cx| {
13984        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13985    });
13986    handle_completion_request_with_insert_and_replace(
13987        &mut cx,
13988        completion_marked_buffer,
13989        vec![(completion_text, completion_text)],
13990        Arc::new(AtomicUsize::new(0)),
13991    )
13992    .await;
13993    cx.condition(|editor, _| editor.context_menu_visible())
13994        .await;
13995    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13996        editor
13997            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13998            .unwrap()
13999    });
14000    cx.assert_editor_state(expected);
14001    handle_resolve_completion_request(&mut cx, None).await;
14002    apply_additional_edits.await.unwrap();
14003}
14004
14005// This used to crash
14006#[gpui::test]
14007async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14008    init_test(cx, |_| {});
14009
14010    let buffer_text = indoc! {"
14011        fn main() {
14012            10.satu;
14013
14014            //
14015            // separate cursors so they open in different excerpts (manually reproducible)
14016            //
14017
14018            10.satu20;
14019        }
14020    "};
14021    let multibuffer_text_with_selections = indoc! {"
14022        fn main() {
14023            10.satuˇ;
14024
14025            //
14026
14027            //
14028
14029            10.satuˇ20;
14030        }
14031    "};
14032    let expected_multibuffer = indoc! {"
14033        fn main() {
14034            10.saturating_sub()ˇ;
14035
14036            //
14037
14038            //
14039
14040            10.saturating_sub()ˇ;
14041        }
14042    "};
14043
14044    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14045    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14046
14047    let fs = FakeFs::new(cx.executor());
14048    fs.insert_tree(
14049        path!("/a"),
14050        json!({
14051            "main.rs": buffer_text,
14052        }),
14053    )
14054    .await;
14055
14056    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14057    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14058    language_registry.add(rust_lang());
14059    let mut fake_servers = language_registry.register_fake_lsp(
14060        "Rust",
14061        FakeLspAdapter {
14062            capabilities: lsp::ServerCapabilities {
14063                completion_provider: Some(lsp::CompletionOptions {
14064                    resolve_provider: None,
14065                    ..lsp::CompletionOptions::default()
14066                }),
14067                ..lsp::ServerCapabilities::default()
14068            },
14069            ..FakeLspAdapter::default()
14070        },
14071    );
14072    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14073    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14074    let buffer = project
14075        .update(cx, |project, cx| {
14076            project.open_local_buffer(path!("/a/main.rs"), cx)
14077        })
14078        .await
14079        .unwrap();
14080
14081    let multi_buffer = cx.new(|cx| {
14082        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14083        multi_buffer.push_excerpts(
14084            buffer.clone(),
14085            [ExcerptRange::new(0..first_excerpt_end)],
14086            cx,
14087        );
14088        multi_buffer.push_excerpts(
14089            buffer.clone(),
14090            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14091            cx,
14092        );
14093        multi_buffer
14094    });
14095
14096    let editor = workspace
14097        .update(cx, |_, window, cx| {
14098            cx.new(|cx| {
14099                Editor::new(
14100                    EditorMode::Full {
14101                        scale_ui_elements_with_buffer_font_size: false,
14102                        show_active_line_background: false,
14103                        sized_by_content: false,
14104                    },
14105                    multi_buffer.clone(),
14106                    Some(project.clone()),
14107                    window,
14108                    cx,
14109                )
14110            })
14111        })
14112        .unwrap();
14113
14114    let pane = workspace
14115        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14116        .unwrap();
14117    pane.update_in(cx, |pane, window, cx| {
14118        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14119    });
14120
14121    let fake_server = fake_servers.next().await.unwrap();
14122
14123    editor.update_in(cx, |editor, window, cx| {
14124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14125            s.select_ranges([
14126                Point::new(1, 11)..Point::new(1, 11),
14127                Point::new(7, 11)..Point::new(7, 11),
14128            ])
14129        });
14130
14131        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14132    });
14133
14134    editor.update_in(cx, |editor, window, cx| {
14135        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14136    });
14137
14138    fake_server
14139        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14140            let completion_item = lsp::CompletionItem {
14141                label: "saturating_sub()".into(),
14142                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14143                    lsp::InsertReplaceEdit {
14144                        new_text: "saturating_sub()".to_owned(),
14145                        insert: lsp::Range::new(
14146                            lsp::Position::new(7, 7),
14147                            lsp::Position::new(7, 11),
14148                        ),
14149                        replace: lsp::Range::new(
14150                            lsp::Position::new(7, 7),
14151                            lsp::Position::new(7, 13),
14152                        ),
14153                    },
14154                )),
14155                ..lsp::CompletionItem::default()
14156            };
14157
14158            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14159        })
14160        .next()
14161        .await
14162        .unwrap();
14163
14164    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14165        .await;
14166
14167    editor
14168        .update_in(cx, |editor, window, cx| {
14169            editor
14170                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14171                .unwrap()
14172        })
14173        .await
14174        .unwrap();
14175
14176    editor.update(cx, |editor, cx| {
14177        assert_text_with_selections(editor, expected_multibuffer, cx);
14178    })
14179}
14180
14181#[gpui::test]
14182async fn test_completion(cx: &mut TestAppContext) {
14183    init_test(cx, |_| {});
14184
14185    let mut cx = EditorLspTestContext::new_rust(
14186        lsp::ServerCapabilities {
14187            completion_provider: Some(lsp::CompletionOptions {
14188                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14189                resolve_provider: Some(true),
14190                ..Default::default()
14191            }),
14192            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14193            ..Default::default()
14194        },
14195        cx,
14196    )
14197    .await;
14198    let counter = Arc::new(AtomicUsize::new(0));
14199
14200    cx.set_state(indoc! {"
14201        oneˇ
14202        two
14203        three
14204    "});
14205    cx.simulate_keystroke(".");
14206    handle_completion_request(
14207        indoc! {"
14208            one.|<>
14209            two
14210            three
14211        "},
14212        vec!["first_completion", "second_completion"],
14213        true,
14214        counter.clone(),
14215        &mut cx,
14216    )
14217    .await;
14218    cx.condition(|editor, _| editor.context_menu_visible())
14219        .await;
14220    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14221
14222    let _handler = handle_signature_help_request(
14223        &mut cx,
14224        lsp::SignatureHelp {
14225            signatures: vec![lsp::SignatureInformation {
14226                label: "test signature".to_string(),
14227                documentation: None,
14228                parameters: Some(vec![lsp::ParameterInformation {
14229                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14230                    documentation: None,
14231                }]),
14232                active_parameter: None,
14233            }],
14234            active_signature: None,
14235            active_parameter: None,
14236        },
14237    );
14238    cx.update_editor(|editor, window, cx| {
14239        assert!(
14240            !editor.signature_help_state.is_shown(),
14241            "No signature help was called for"
14242        );
14243        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14244    });
14245    cx.run_until_parked();
14246    cx.update_editor(|editor, _, _| {
14247        assert!(
14248            !editor.signature_help_state.is_shown(),
14249            "No signature help should be shown when completions menu is open"
14250        );
14251    });
14252
14253    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14254        editor.context_menu_next(&Default::default(), window, cx);
14255        editor
14256            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14257            .unwrap()
14258    });
14259    cx.assert_editor_state(indoc! {"
14260        one.second_completionˇ
14261        two
14262        three
14263    "});
14264
14265    handle_resolve_completion_request(
14266        &mut cx,
14267        Some(vec![
14268            (
14269                //This overlaps with the primary completion edit which is
14270                //misbehavior from the LSP spec, test that we filter it out
14271                indoc! {"
14272                    one.second_ˇcompletion
14273                    two
14274                    threeˇ
14275                "},
14276                "overlapping additional edit",
14277            ),
14278            (
14279                indoc! {"
14280                    one.second_completion
14281                    two
14282                    threeˇ
14283                "},
14284                "\nadditional edit",
14285            ),
14286        ]),
14287    )
14288    .await;
14289    apply_additional_edits.await.unwrap();
14290    cx.assert_editor_state(indoc! {"
14291        one.second_completionˇ
14292        two
14293        three
14294        additional edit
14295    "});
14296
14297    cx.set_state(indoc! {"
14298        one.second_completion
14299        twoˇ
14300        threeˇ
14301        additional edit
14302    "});
14303    cx.simulate_keystroke(" ");
14304    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14305    cx.simulate_keystroke("s");
14306    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14307
14308    cx.assert_editor_state(indoc! {"
14309        one.second_completion
14310        two sˇ
14311        three sˇ
14312        additional edit
14313    "});
14314    handle_completion_request(
14315        indoc! {"
14316            one.second_completion
14317            two s
14318            three <s|>
14319            additional edit
14320        "},
14321        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14322        true,
14323        counter.clone(),
14324        &mut cx,
14325    )
14326    .await;
14327    cx.condition(|editor, _| editor.context_menu_visible())
14328        .await;
14329    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14330
14331    cx.simulate_keystroke("i");
14332
14333    handle_completion_request(
14334        indoc! {"
14335            one.second_completion
14336            two si
14337            three <si|>
14338            additional edit
14339        "},
14340        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14341        true,
14342        counter.clone(),
14343        &mut cx,
14344    )
14345    .await;
14346    cx.condition(|editor, _| editor.context_menu_visible())
14347        .await;
14348    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14349
14350    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14351        editor
14352            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14353            .unwrap()
14354    });
14355    cx.assert_editor_state(indoc! {"
14356        one.second_completion
14357        two sixth_completionˇ
14358        three sixth_completionˇ
14359        additional edit
14360    "});
14361
14362    apply_additional_edits.await.unwrap();
14363
14364    update_test_language_settings(&mut cx, |settings| {
14365        settings.defaults.show_completions_on_input = Some(false);
14366    });
14367    cx.set_state("editorˇ");
14368    cx.simulate_keystroke(".");
14369    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14370    cx.simulate_keystrokes("c l o");
14371    cx.assert_editor_state("editor.cloˇ");
14372    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14373    cx.update_editor(|editor, window, cx| {
14374        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14375    });
14376    handle_completion_request(
14377        "editor.<clo|>",
14378        vec!["close", "clobber"],
14379        true,
14380        counter.clone(),
14381        &mut cx,
14382    )
14383    .await;
14384    cx.condition(|editor, _| editor.context_menu_visible())
14385        .await;
14386    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14387
14388    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14389        editor
14390            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14391            .unwrap()
14392    });
14393    cx.assert_editor_state("editor.clobberˇ");
14394    handle_resolve_completion_request(&mut cx, None).await;
14395    apply_additional_edits.await.unwrap();
14396}
14397
14398#[gpui::test]
14399async fn test_completion_reuse(cx: &mut TestAppContext) {
14400    init_test(cx, |_| {});
14401
14402    let mut cx = EditorLspTestContext::new_rust(
14403        lsp::ServerCapabilities {
14404            completion_provider: Some(lsp::CompletionOptions {
14405                trigger_characters: Some(vec![".".to_string()]),
14406                ..Default::default()
14407            }),
14408            ..Default::default()
14409        },
14410        cx,
14411    )
14412    .await;
14413
14414    let counter = Arc::new(AtomicUsize::new(0));
14415    cx.set_state("objˇ");
14416    cx.simulate_keystroke(".");
14417
14418    // Initial completion request returns complete results
14419    let is_incomplete = false;
14420    handle_completion_request(
14421        "obj.|<>",
14422        vec!["a", "ab", "abc"],
14423        is_incomplete,
14424        counter.clone(),
14425        &mut cx,
14426    )
14427    .await;
14428    cx.run_until_parked();
14429    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14430    cx.assert_editor_state("obj.ˇ");
14431    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14432
14433    // Type "a" - filters existing completions
14434    cx.simulate_keystroke("a");
14435    cx.run_until_parked();
14436    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14437    cx.assert_editor_state("obj.aˇ");
14438    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14439
14440    // Type "b" - filters existing completions
14441    cx.simulate_keystroke("b");
14442    cx.run_until_parked();
14443    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14444    cx.assert_editor_state("obj.abˇ");
14445    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14446
14447    // Type "c" - filters existing completions
14448    cx.simulate_keystroke("c");
14449    cx.run_until_parked();
14450    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14451    cx.assert_editor_state("obj.abcˇ");
14452    check_displayed_completions(vec!["abc"], &mut cx);
14453
14454    // Backspace to delete "c" - filters existing completions
14455    cx.update_editor(|editor, window, cx| {
14456        editor.backspace(&Backspace, window, cx);
14457    });
14458    cx.run_until_parked();
14459    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14460    cx.assert_editor_state("obj.abˇ");
14461    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14462
14463    // Moving cursor to the left dismisses menu.
14464    cx.update_editor(|editor, window, cx| {
14465        editor.move_left(&MoveLeft, window, cx);
14466    });
14467    cx.run_until_parked();
14468    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14469    cx.assert_editor_state("obj.aˇb");
14470    cx.update_editor(|editor, _, _| {
14471        assert_eq!(editor.context_menu_visible(), false);
14472    });
14473
14474    // Type "b" - new request
14475    cx.simulate_keystroke("b");
14476    let is_incomplete = false;
14477    handle_completion_request(
14478        "obj.<ab|>a",
14479        vec!["ab", "abc"],
14480        is_incomplete,
14481        counter.clone(),
14482        &mut cx,
14483    )
14484    .await;
14485    cx.run_until_parked();
14486    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14487    cx.assert_editor_state("obj.abˇb");
14488    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14489
14490    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14491    cx.update_editor(|editor, window, cx| {
14492        editor.backspace(&Backspace, window, cx);
14493    });
14494    let is_incomplete = false;
14495    handle_completion_request(
14496        "obj.<a|>b",
14497        vec!["a", "ab", "abc"],
14498        is_incomplete,
14499        counter.clone(),
14500        &mut cx,
14501    )
14502    .await;
14503    cx.run_until_parked();
14504    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14505    cx.assert_editor_state("obj.aˇb");
14506    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14507
14508    // Backspace to delete "a" - dismisses menu.
14509    cx.update_editor(|editor, window, cx| {
14510        editor.backspace(&Backspace, window, cx);
14511    });
14512    cx.run_until_parked();
14513    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14514    cx.assert_editor_state("obj.ˇb");
14515    cx.update_editor(|editor, _, _| {
14516        assert_eq!(editor.context_menu_visible(), false);
14517    });
14518}
14519
14520#[gpui::test]
14521async fn test_word_completion(cx: &mut TestAppContext) {
14522    let lsp_fetch_timeout_ms = 10;
14523    init_test(cx, |language_settings| {
14524        language_settings.defaults.completions = Some(CompletionSettingsContent {
14525            words_min_length: Some(0),
14526            lsp_fetch_timeout_ms: Some(10),
14527            lsp_insert_mode: Some(LspInsertMode::Insert),
14528            ..Default::default()
14529        });
14530    });
14531
14532    let mut cx = EditorLspTestContext::new_rust(
14533        lsp::ServerCapabilities {
14534            completion_provider: Some(lsp::CompletionOptions {
14535                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14536                ..lsp::CompletionOptions::default()
14537            }),
14538            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14539            ..lsp::ServerCapabilities::default()
14540        },
14541        cx,
14542    )
14543    .await;
14544
14545    let throttle_completions = Arc::new(AtomicBool::new(false));
14546
14547    let lsp_throttle_completions = throttle_completions.clone();
14548    let _completion_requests_handler =
14549        cx.lsp
14550            .server
14551            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14552                let lsp_throttle_completions = lsp_throttle_completions.clone();
14553                let cx = cx.clone();
14554                async move {
14555                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14556                        cx.background_executor()
14557                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14558                            .await;
14559                    }
14560                    Ok(Some(lsp::CompletionResponse::Array(vec![
14561                        lsp::CompletionItem {
14562                            label: "first".into(),
14563                            ..lsp::CompletionItem::default()
14564                        },
14565                        lsp::CompletionItem {
14566                            label: "last".into(),
14567                            ..lsp::CompletionItem::default()
14568                        },
14569                    ])))
14570                }
14571            });
14572
14573    cx.set_state(indoc! {"
14574        oneˇ
14575        two
14576        three
14577    "});
14578    cx.simulate_keystroke(".");
14579    cx.executor().run_until_parked();
14580    cx.condition(|editor, _| editor.context_menu_visible())
14581        .await;
14582    cx.update_editor(|editor, window, cx| {
14583        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14584        {
14585            assert_eq!(
14586                completion_menu_entries(menu),
14587                &["first", "last"],
14588                "When LSP server is fast to reply, no fallback word completions are used"
14589            );
14590        } else {
14591            panic!("expected completion menu to be open");
14592        }
14593        editor.cancel(&Cancel, window, cx);
14594    });
14595    cx.executor().run_until_parked();
14596    cx.condition(|editor, _| !editor.context_menu_visible())
14597        .await;
14598
14599    throttle_completions.store(true, atomic::Ordering::Release);
14600    cx.simulate_keystroke(".");
14601    cx.executor()
14602        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14603    cx.executor().run_until_parked();
14604    cx.condition(|editor, _| editor.context_menu_visible())
14605        .await;
14606    cx.update_editor(|editor, _, _| {
14607        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14608        {
14609            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14610                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14611        } else {
14612            panic!("expected completion menu to be open");
14613        }
14614    });
14615}
14616
14617#[gpui::test]
14618async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14619    init_test(cx, |language_settings| {
14620        language_settings.defaults.completions = Some(CompletionSettingsContent {
14621            words: Some(WordsCompletionMode::Enabled),
14622            words_min_length: Some(0),
14623            lsp_insert_mode: Some(LspInsertMode::Insert),
14624            ..Default::default()
14625        });
14626    });
14627
14628    let mut cx = EditorLspTestContext::new_rust(
14629        lsp::ServerCapabilities {
14630            completion_provider: Some(lsp::CompletionOptions {
14631                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14632                ..lsp::CompletionOptions::default()
14633            }),
14634            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14635            ..lsp::ServerCapabilities::default()
14636        },
14637        cx,
14638    )
14639    .await;
14640
14641    let _completion_requests_handler =
14642        cx.lsp
14643            .server
14644            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14645                Ok(Some(lsp::CompletionResponse::Array(vec![
14646                    lsp::CompletionItem {
14647                        label: "first".into(),
14648                        ..lsp::CompletionItem::default()
14649                    },
14650                    lsp::CompletionItem {
14651                        label: "last".into(),
14652                        ..lsp::CompletionItem::default()
14653                    },
14654                ])))
14655            });
14656
14657    cx.set_state(indoc! {"ˇ
14658        first
14659        last
14660        second
14661    "});
14662    cx.simulate_keystroke(".");
14663    cx.executor().run_until_parked();
14664    cx.condition(|editor, _| editor.context_menu_visible())
14665        .await;
14666    cx.update_editor(|editor, _, _| {
14667        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14668        {
14669            assert_eq!(
14670                completion_menu_entries(menu),
14671                &["first", "last", "second"],
14672                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14673            );
14674        } else {
14675            panic!("expected completion menu to be open");
14676        }
14677    });
14678}
14679
14680#[gpui::test]
14681async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14682    init_test(cx, |language_settings| {
14683        language_settings.defaults.completions = Some(CompletionSettingsContent {
14684            words: Some(WordsCompletionMode::Disabled),
14685            words_min_length: Some(0),
14686            lsp_insert_mode: Some(LspInsertMode::Insert),
14687            ..Default::default()
14688        });
14689    });
14690
14691    let mut cx = EditorLspTestContext::new_rust(
14692        lsp::ServerCapabilities {
14693            completion_provider: Some(lsp::CompletionOptions {
14694                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14695                ..lsp::CompletionOptions::default()
14696            }),
14697            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14698            ..lsp::ServerCapabilities::default()
14699        },
14700        cx,
14701    )
14702    .await;
14703
14704    let _completion_requests_handler =
14705        cx.lsp
14706            .server
14707            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14708                panic!("LSP completions should not be queried when dealing with word completions")
14709            });
14710
14711    cx.set_state(indoc! {"ˇ
14712        first
14713        last
14714        second
14715    "});
14716    cx.update_editor(|editor, window, cx| {
14717        editor.show_word_completions(&ShowWordCompletions, window, cx);
14718    });
14719    cx.executor().run_until_parked();
14720    cx.condition(|editor, _| editor.context_menu_visible())
14721        .await;
14722    cx.update_editor(|editor, _, _| {
14723        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14724        {
14725            assert_eq!(
14726                completion_menu_entries(menu),
14727                &["first", "last", "second"],
14728                "`ShowWordCompletions` action should show word completions"
14729            );
14730        } else {
14731            panic!("expected completion menu to be open");
14732        }
14733    });
14734
14735    cx.simulate_keystroke("l");
14736    cx.executor().run_until_parked();
14737    cx.condition(|editor, _| editor.context_menu_visible())
14738        .await;
14739    cx.update_editor(|editor, _, _| {
14740        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14741        {
14742            assert_eq!(
14743                completion_menu_entries(menu),
14744                &["last"],
14745                "After showing word completions, further editing should filter them and not query the LSP"
14746            );
14747        } else {
14748            panic!("expected completion menu to be open");
14749        }
14750    });
14751}
14752
14753#[gpui::test]
14754async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14755    init_test(cx, |language_settings| {
14756        language_settings.defaults.completions = Some(CompletionSettingsContent {
14757            words_min_length: Some(0),
14758            lsp: Some(false),
14759            lsp_insert_mode: Some(LspInsertMode::Insert),
14760            ..Default::default()
14761        });
14762    });
14763
14764    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14765
14766    cx.set_state(indoc! {"ˇ
14767        0_usize
14768        let
14769        33
14770        4.5f32
14771    "});
14772    cx.update_editor(|editor, window, cx| {
14773        editor.show_completions(&ShowCompletions::default(), window, cx);
14774    });
14775    cx.executor().run_until_parked();
14776    cx.condition(|editor, _| editor.context_menu_visible())
14777        .await;
14778    cx.update_editor(|editor, window, cx| {
14779        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14780        {
14781            assert_eq!(
14782                completion_menu_entries(menu),
14783                &["let"],
14784                "With no digits in the completion query, no digits should be in the word completions"
14785            );
14786        } else {
14787            panic!("expected completion menu to be open");
14788        }
14789        editor.cancel(&Cancel, window, cx);
14790    });
14791
14792    cx.set_state(indoc! {"14793        0_usize
14794        let
14795        3
14796        33.35f32
14797    "});
14798    cx.update_editor(|editor, window, cx| {
14799        editor.show_completions(&ShowCompletions::default(), window, cx);
14800    });
14801    cx.executor().run_until_parked();
14802    cx.condition(|editor, _| editor.context_menu_visible())
14803        .await;
14804    cx.update_editor(|editor, _, _| {
14805        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14806        {
14807            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14808                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14809        } else {
14810            panic!("expected completion menu to be open");
14811        }
14812    });
14813}
14814
14815#[gpui::test]
14816async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14817    init_test(cx, |language_settings| {
14818        language_settings.defaults.completions = Some(CompletionSettingsContent {
14819            words: Some(WordsCompletionMode::Enabled),
14820            words_min_length: Some(3),
14821            lsp_insert_mode: Some(LspInsertMode::Insert),
14822            ..Default::default()
14823        });
14824    });
14825
14826    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14827    cx.set_state(indoc! {"ˇ
14828        wow
14829        wowen
14830        wowser
14831    "});
14832    cx.simulate_keystroke("w");
14833    cx.executor().run_until_parked();
14834    cx.update_editor(|editor, _, _| {
14835        if editor.context_menu.borrow_mut().is_some() {
14836            panic!(
14837                "expected completion menu to be hidden, as words completion threshold is not met"
14838            );
14839        }
14840    });
14841
14842    cx.update_editor(|editor, window, cx| {
14843        editor.show_word_completions(&ShowWordCompletions, window, cx);
14844    });
14845    cx.executor().run_until_parked();
14846    cx.update_editor(|editor, window, cx| {
14847        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14848        {
14849            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");
14850        } else {
14851            panic!("expected completion menu to be open after the word completions are called with an action");
14852        }
14853
14854        editor.cancel(&Cancel, window, cx);
14855    });
14856    cx.update_editor(|editor, _, _| {
14857        if editor.context_menu.borrow_mut().is_some() {
14858            panic!("expected completion menu to be hidden after canceling");
14859        }
14860    });
14861
14862    cx.simulate_keystroke("o");
14863    cx.executor().run_until_parked();
14864    cx.update_editor(|editor, _, _| {
14865        if editor.context_menu.borrow_mut().is_some() {
14866            panic!(
14867                "expected completion menu to be hidden, as words completion threshold is not met still"
14868            );
14869        }
14870    });
14871
14872    cx.simulate_keystroke("w");
14873    cx.executor().run_until_parked();
14874    cx.update_editor(|editor, _, _| {
14875        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14876        {
14877            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14878        } else {
14879            panic!("expected completion menu to be open after the word completions threshold is met");
14880        }
14881    });
14882}
14883
14884#[gpui::test]
14885async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14886    init_test(cx, |language_settings| {
14887        language_settings.defaults.completions = Some(CompletionSettingsContent {
14888            words: Some(WordsCompletionMode::Enabled),
14889            words_min_length: Some(0),
14890            lsp_insert_mode: Some(LspInsertMode::Insert),
14891            ..Default::default()
14892        });
14893    });
14894
14895    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14896    cx.update_editor(|editor, _, _| {
14897        editor.disable_word_completions();
14898    });
14899    cx.set_state(indoc! {"ˇ
14900        wow
14901        wowen
14902        wowser
14903    "});
14904    cx.simulate_keystroke("w");
14905    cx.executor().run_until_parked();
14906    cx.update_editor(|editor, _, _| {
14907        if editor.context_menu.borrow_mut().is_some() {
14908            panic!(
14909                "expected completion menu to be hidden, as words completion are disabled for this editor"
14910            );
14911        }
14912    });
14913
14914    cx.update_editor(|editor, window, cx| {
14915        editor.show_word_completions(&ShowWordCompletions, window, cx);
14916    });
14917    cx.executor().run_until_parked();
14918    cx.update_editor(|editor, _, _| {
14919        if editor.context_menu.borrow_mut().is_some() {
14920            panic!(
14921                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14922            );
14923        }
14924    });
14925}
14926
14927fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14928    let position = || lsp::Position {
14929        line: params.text_document_position.position.line,
14930        character: params.text_document_position.position.character,
14931    };
14932    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14933        range: lsp::Range {
14934            start: position(),
14935            end: position(),
14936        },
14937        new_text: text.to_string(),
14938    }))
14939}
14940
14941#[gpui::test]
14942async fn test_multiline_completion(cx: &mut TestAppContext) {
14943    init_test(cx, |_| {});
14944
14945    let fs = FakeFs::new(cx.executor());
14946    fs.insert_tree(
14947        path!("/a"),
14948        json!({
14949            "main.ts": "a",
14950        }),
14951    )
14952    .await;
14953
14954    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14955    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14956    let typescript_language = Arc::new(Language::new(
14957        LanguageConfig {
14958            name: "TypeScript".into(),
14959            matcher: LanguageMatcher {
14960                path_suffixes: vec!["ts".to_string()],
14961                ..LanguageMatcher::default()
14962            },
14963            line_comments: vec!["// ".into()],
14964            ..LanguageConfig::default()
14965        },
14966        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14967    ));
14968    language_registry.add(typescript_language.clone());
14969    let mut fake_servers = language_registry.register_fake_lsp(
14970        "TypeScript",
14971        FakeLspAdapter {
14972            capabilities: lsp::ServerCapabilities {
14973                completion_provider: Some(lsp::CompletionOptions {
14974                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14975                    ..lsp::CompletionOptions::default()
14976                }),
14977                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14978                ..lsp::ServerCapabilities::default()
14979            },
14980            // Emulate vtsls label generation
14981            label_for_completion: Some(Box::new(|item, _| {
14982                let text = if let Some(description) = item
14983                    .label_details
14984                    .as_ref()
14985                    .and_then(|label_details| label_details.description.as_ref())
14986                {
14987                    format!("{} {}", item.label, description)
14988                } else if let Some(detail) = &item.detail {
14989                    format!("{} {}", item.label, detail)
14990                } else {
14991                    item.label.clone()
14992                };
14993                Some(language::CodeLabel::plain(text, None))
14994            })),
14995            ..FakeLspAdapter::default()
14996        },
14997    );
14998    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14999    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15000    let worktree_id = workspace
15001        .update(cx, |workspace, _window, cx| {
15002            workspace.project().update(cx, |project, cx| {
15003                project.worktrees(cx).next().unwrap().read(cx).id()
15004            })
15005        })
15006        .unwrap();
15007    let _buffer = project
15008        .update(cx, |project, cx| {
15009            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15010        })
15011        .await
15012        .unwrap();
15013    let editor = workspace
15014        .update(cx, |workspace, window, cx| {
15015            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15016        })
15017        .unwrap()
15018        .await
15019        .unwrap()
15020        .downcast::<Editor>()
15021        .unwrap();
15022    let fake_server = fake_servers.next().await.unwrap();
15023
15024    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15025    let multiline_label_2 = "a\nb\nc\n";
15026    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15027    let multiline_description = "d\ne\nf\n";
15028    let multiline_detail_2 = "g\nh\ni\n";
15029
15030    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15031        move |params, _| async move {
15032            Ok(Some(lsp::CompletionResponse::Array(vec![
15033                lsp::CompletionItem {
15034                    label: multiline_label.to_string(),
15035                    text_edit: gen_text_edit(&params, "new_text_1"),
15036                    ..lsp::CompletionItem::default()
15037                },
15038                lsp::CompletionItem {
15039                    label: "single line label 1".to_string(),
15040                    detail: Some(multiline_detail.to_string()),
15041                    text_edit: gen_text_edit(&params, "new_text_2"),
15042                    ..lsp::CompletionItem::default()
15043                },
15044                lsp::CompletionItem {
15045                    label: "single line label 2".to_string(),
15046                    label_details: Some(lsp::CompletionItemLabelDetails {
15047                        description: Some(multiline_description.to_string()),
15048                        detail: None,
15049                    }),
15050                    text_edit: gen_text_edit(&params, "new_text_2"),
15051                    ..lsp::CompletionItem::default()
15052                },
15053                lsp::CompletionItem {
15054                    label: multiline_label_2.to_string(),
15055                    detail: Some(multiline_detail_2.to_string()),
15056                    text_edit: gen_text_edit(&params, "new_text_3"),
15057                    ..lsp::CompletionItem::default()
15058                },
15059                lsp::CompletionItem {
15060                    label: "Label with many     spaces and \t but without newlines".to_string(),
15061                    detail: Some(
15062                        "Details with many     spaces and \t but without newlines".to_string(),
15063                    ),
15064                    text_edit: gen_text_edit(&params, "new_text_4"),
15065                    ..lsp::CompletionItem::default()
15066                },
15067            ])))
15068        },
15069    );
15070
15071    editor.update_in(cx, |editor, window, cx| {
15072        cx.focus_self(window);
15073        editor.move_to_end(&MoveToEnd, window, cx);
15074        editor.handle_input(".", window, cx);
15075    });
15076    cx.run_until_parked();
15077    completion_handle.next().await.unwrap();
15078
15079    editor.update(cx, |editor, _| {
15080        assert!(editor.context_menu_visible());
15081        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15082        {
15083            let completion_labels = menu
15084                .completions
15085                .borrow()
15086                .iter()
15087                .map(|c| c.label.text.clone())
15088                .collect::<Vec<_>>();
15089            assert_eq!(
15090                completion_labels,
15091                &[
15092                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15093                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15094                    "single line label 2 d e f ",
15095                    "a b c g h i ",
15096                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15097                ],
15098                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15099            );
15100
15101            for completion in menu
15102                .completions
15103                .borrow()
15104                .iter() {
15105                    assert_eq!(
15106                        completion.label.filter_range,
15107                        0..completion.label.text.len(),
15108                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15109                    );
15110                }
15111        } else {
15112            panic!("expected completion menu to be open");
15113        }
15114    });
15115}
15116
15117#[gpui::test]
15118async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15119    init_test(cx, |_| {});
15120    let mut cx = EditorLspTestContext::new_rust(
15121        lsp::ServerCapabilities {
15122            completion_provider: Some(lsp::CompletionOptions {
15123                trigger_characters: Some(vec![".".to_string()]),
15124                ..Default::default()
15125            }),
15126            ..Default::default()
15127        },
15128        cx,
15129    )
15130    .await;
15131    cx.lsp
15132        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15133            Ok(Some(lsp::CompletionResponse::Array(vec![
15134                lsp::CompletionItem {
15135                    label: "first".into(),
15136                    ..Default::default()
15137                },
15138                lsp::CompletionItem {
15139                    label: "last".into(),
15140                    ..Default::default()
15141                },
15142            ])))
15143        });
15144    cx.set_state("variableˇ");
15145    cx.simulate_keystroke(".");
15146    cx.executor().run_until_parked();
15147
15148    cx.update_editor(|editor, _, _| {
15149        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15150        {
15151            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15152        } else {
15153            panic!("expected completion menu to be open");
15154        }
15155    });
15156
15157    cx.update_editor(|editor, window, cx| {
15158        editor.move_page_down(&MovePageDown::default(), window, cx);
15159        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15160        {
15161            assert!(
15162                menu.selected_item == 1,
15163                "expected PageDown to select the last item from the context menu"
15164            );
15165        } else {
15166            panic!("expected completion menu to stay open after PageDown");
15167        }
15168    });
15169
15170    cx.update_editor(|editor, window, cx| {
15171        editor.move_page_up(&MovePageUp::default(), window, cx);
15172        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15173        {
15174            assert!(
15175                menu.selected_item == 0,
15176                "expected PageUp to select the first item from the context menu"
15177            );
15178        } else {
15179            panic!("expected completion menu to stay open after PageUp");
15180        }
15181    });
15182}
15183
15184#[gpui::test]
15185async fn test_as_is_completions(cx: &mut TestAppContext) {
15186    init_test(cx, |_| {});
15187    let mut cx = EditorLspTestContext::new_rust(
15188        lsp::ServerCapabilities {
15189            completion_provider: Some(lsp::CompletionOptions {
15190                ..Default::default()
15191            }),
15192            ..Default::default()
15193        },
15194        cx,
15195    )
15196    .await;
15197    cx.lsp
15198        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15199            Ok(Some(lsp::CompletionResponse::Array(vec![
15200                lsp::CompletionItem {
15201                    label: "unsafe".into(),
15202                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15203                        range: lsp::Range {
15204                            start: lsp::Position {
15205                                line: 1,
15206                                character: 2,
15207                            },
15208                            end: lsp::Position {
15209                                line: 1,
15210                                character: 3,
15211                            },
15212                        },
15213                        new_text: "unsafe".to_string(),
15214                    })),
15215                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15216                    ..Default::default()
15217                },
15218            ])))
15219        });
15220    cx.set_state("fn a() {}\n");
15221    cx.executor().run_until_parked();
15222    cx.update_editor(|editor, window, cx| {
15223        editor.show_completions(
15224            &ShowCompletions {
15225                trigger: Some("\n".into()),
15226            },
15227            window,
15228            cx,
15229        );
15230    });
15231    cx.executor().run_until_parked();
15232
15233    cx.update_editor(|editor, window, cx| {
15234        editor.confirm_completion(&Default::default(), window, cx)
15235    });
15236    cx.executor().run_until_parked();
15237    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15238}
15239
15240#[gpui::test]
15241async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15242    init_test(cx, |_| {});
15243    let language =
15244        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15245    let mut cx = EditorLspTestContext::new(
15246        language,
15247        lsp::ServerCapabilities {
15248            completion_provider: Some(lsp::CompletionOptions {
15249                ..lsp::CompletionOptions::default()
15250            }),
15251            ..lsp::ServerCapabilities::default()
15252        },
15253        cx,
15254    )
15255    .await;
15256
15257    cx.set_state(
15258        "#ifndef BAR_H
15259#define BAR_H
15260
15261#include <stdbool.h>
15262
15263int fn_branch(bool do_branch1, bool do_branch2);
15264
15265#endif // BAR_H
15266ˇ",
15267    );
15268    cx.executor().run_until_parked();
15269    cx.update_editor(|editor, window, cx| {
15270        editor.handle_input("#", window, cx);
15271    });
15272    cx.executor().run_until_parked();
15273    cx.update_editor(|editor, window, cx| {
15274        editor.handle_input("i", window, cx);
15275    });
15276    cx.executor().run_until_parked();
15277    cx.update_editor(|editor, window, cx| {
15278        editor.handle_input("n", window, cx);
15279    });
15280    cx.executor().run_until_parked();
15281    cx.assert_editor_state(
15282        "#ifndef BAR_H
15283#define BAR_H
15284
15285#include <stdbool.h>
15286
15287int fn_branch(bool do_branch1, bool do_branch2);
15288
15289#endif // BAR_H
15290#inˇ",
15291    );
15292
15293    cx.lsp
15294        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15295            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15296                is_incomplete: false,
15297                item_defaults: None,
15298                items: vec![lsp::CompletionItem {
15299                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15300                    label_details: Some(lsp::CompletionItemLabelDetails {
15301                        detail: Some("header".to_string()),
15302                        description: None,
15303                    }),
15304                    label: " include".to_string(),
15305                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15306                        range: lsp::Range {
15307                            start: lsp::Position {
15308                                line: 8,
15309                                character: 1,
15310                            },
15311                            end: lsp::Position {
15312                                line: 8,
15313                                character: 1,
15314                            },
15315                        },
15316                        new_text: "include \"$0\"".to_string(),
15317                    })),
15318                    sort_text: Some("40b67681include".to_string()),
15319                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15320                    filter_text: Some("include".to_string()),
15321                    insert_text: Some("include \"$0\"".to_string()),
15322                    ..lsp::CompletionItem::default()
15323                }],
15324            })))
15325        });
15326    cx.update_editor(|editor, window, cx| {
15327        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15328    });
15329    cx.executor().run_until_parked();
15330    cx.update_editor(|editor, window, cx| {
15331        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15332    });
15333    cx.executor().run_until_parked();
15334    cx.assert_editor_state(
15335        "#ifndef BAR_H
15336#define BAR_H
15337
15338#include <stdbool.h>
15339
15340int fn_branch(bool do_branch1, bool do_branch2);
15341
15342#endif // BAR_H
15343#include \"ˇ\"",
15344    );
15345
15346    cx.lsp
15347        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15348            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15349                is_incomplete: true,
15350                item_defaults: None,
15351                items: vec![lsp::CompletionItem {
15352                    kind: Some(lsp::CompletionItemKind::FILE),
15353                    label: "AGL/".to_string(),
15354                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15355                        range: lsp::Range {
15356                            start: lsp::Position {
15357                                line: 8,
15358                                character: 10,
15359                            },
15360                            end: lsp::Position {
15361                                line: 8,
15362                                character: 11,
15363                            },
15364                        },
15365                        new_text: "AGL/".to_string(),
15366                    })),
15367                    sort_text: Some("40b67681AGL/".to_string()),
15368                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15369                    filter_text: Some("AGL/".to_string()),
15370                    insert_text: Some("AGL/".to_string()),
15371                    ..lsp::CompletionItem::default()
15372                }],
15373            })))
15374        });
15375    cx.update_editor(|editor, window, cx| {
15376        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15377    });
15378    cx.executor().run_until_parked();
15379    cx.update_editor(|editor, window, cx| {
15380        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15381    });
15382    cx.executor().run_until_parked();
15383    cx.assert_editor_state(
15384        r##"#ifndef BAR_H
15385#define BAR_H
15386
15387#include <stdbool.h>
15388
15389int fn_branch(bool do_branch1, bool do_branch2);
15390
15391#endif // BAR_H
15392#include "AGL/ˇ"##,
15393    );
15394
15395    cx.update_editor(|editor, window, cx| {
15396        editor.handle_input("\"", window, cx);
15397    });
15398    cx.executor().run_until_parked();
15399    cx.assert_editor_state(
15400        r##"#ifndef BAR_H
15401#define BAR_H
15402
15403#include <stdbool.h>
15404
15405int fn_branch(bool do_branch1, bool do_branch2);
15406
15407#endif // BAR_H
15408#include "AGL/"ˇ"##,
15409    );
15410}
15411
15412#[gpui::test]
15413async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15414    init_test(cx, |_| {});
15415
15416    let mut cx = EditorLspTestContext::new_rust(
15417        lsp::ServerCapabilities {
15418            completion_provider: Some(lsp::CompletionOptions {
15419                trigger_characters: Some(vec![".".to_string()]),
15420                resolve_provider: Some(true),
15421                ..Default::default()
15422            }),
15423            ..Default::default()
15424        },
15425        cx,
15426    )
15427    .await;
15428
15429    cx.set_state("fn main() { let a = 2ˇ; }");
15430    cx.simulate_keystroke(".");
15431    let completion_item = lsp::CompletionItem {
15432        label: "Some".into(),
15433        kind: Some(lsp::CompletionItemKind::SNIPPET),
15434        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15435        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15436            kind: lsp::MarkupKind::Markdown,
15437            value: "```rust\nSome(2)\n```".to_string(),
15438        })),
15439        deprecated: Some(false),
15440        sort_text: Some("Some".to_string()),
15441        filter_text: Some("Some".to_string()),
15442        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15443        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15444            range: lsp::Range {
15445                start: lsp::Position {
15446                    line: 0,
15447                    character: 22,
15448                },
15449                end: lsp::Position {
15450                    line: 0,
15451                    character: 22,
15452                },
15453            },
15454            new_text: "Some(2)".to_string(),
15455        })),
15456        additional_text_edits: Some(vec![lsp::TextEdit {
15457            range: lsp::Range {
15458                start: lsp::Position {
15459                    line: 0,
15460                    character: 20,
15461                },
15462                end: lsp::Position {
15463                    line: 0,
15464                    character: 22,
15465                },
15466            },
15467            new_text: "".to_string(),
15468        }]),
15469        ..Default::default()
15470    };
15471
15472    let closure_completion_item = completion_item.clone();
15473    let counter = Arc::new(AtomicUsize::new(0));
15474    let counter_clone = counter.clone();
15475    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15476        let task_completion_item = closure_completion_item.clone();
15477        counter_clone.fetch_add(1, atomic::Ordering::Release);
15478        async move {
15479            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15480                is_incomplete: true,
15481                item_defaults: None,
15482                items: vec![task_completion_item],
15483            })))
15484        }
15485    });
15486
15487    cx.condition(|editor, _| editor.context_menu_visible())
15488        .await;
15489    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15490    assert!(request.next().await.is_some());
15491    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15492
15493    cx.simulate_keystrokes("S o m");
15494    cx.condition(|editor, _| editor.context_menu_visible())
15495        .await;
15496    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15497    assert!(request.next().await.is_some());
15498    assert!(request.next().await.is_some());
15499    assert!(request.next().await.is_some());
15500    request.close();
15501    assert!(request.next().await.is_none());
15502    assert_eq!(
15503        counter.load(atomic::Ordering::Acquire),
15504        4,
15505        "With the completions menu open, only one LSP request should happen per input"
15506    );
15507}
15508
15509#[gpui::test]
15510async fn test_toggle_comment(cx: &mut TestAppContext) {
15511    init_test(cx, |_| {});
15512    let mut cx = EditorTestContext::new(cx).await;
15513    let language = Arc::new(Language::new(
15514        LanguageConfig {
15515            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15516            ..Default::default()
15517        },
15518        Some(tree_sitter_rust::LANGUAGE.into()),
15519    ));
15520    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15521
15522    // If multiple selections intersect a line, the line is only toggled once.
15523    cx.set_state(indoc! {"
15524        fn a() {
15525            «//b();
15526            ˇ»// «c();
15527            //ˇ»  d();
15528        }
15529    "});
15530
15531    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15532
15533    cx.assert_editor_state(indoc! {"
15534        fn a() {
15535            «b();
15536            c();
15537            ˇ» d();
15538        }
15539    "});
15540
15541    // The comment prefix is inserted at the same column for every line in a
15542    // selection.
15543    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15544
15545    cx.assert_editor_state(indoc! {"
15546        fn a() {
15547            // «b();
15548            // c();
15549            ˇ»//  d();
15550        }
15551    "});
15552
15553    // If a selection ends at the beginning of a line, that line is not toggled.
15554    cx.set_selections_state(indoc! {"
15555        fn a() {
15556            // b();
15557            «// c();
15558        ˇ»    //  d();
15559        }
15560    "});
15561
15562    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15563
15564    cx.assert_editor_state(indoc! {"
15565        fn a() {
15566            // b();
15567            «c();
15568        ˇ»    //  d();
15569        }
15570    "});
15571
15572    // If a selection span a single line and is empty, the line is toggled.
15573    cx.set_state(indoc! {"
15574        fn a() {
15575            a();
15576            b();
15577        ˇ
15578        }
15579    "});
15580
15581    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15582
15583    cx.assert_editor_state(indoc! {"
15584        fn a() {
15585            a();
15586            b();
15587        //•ˇ
15588        }
15589    "});
15590
15591    // If a selection span multiple lines, empty lines are not toggled.
15592    cx.set_state(indoc! {"
15593        fn a() {
15594            «a();
15595
15596            c();ˇ»
15597        }
15598    "});
15599
15600    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15601
15602    cx.assert_editor_state(indoc! {"
15603        fn a() {
15604            // «a();
15605
15606            // c();ˇ»
15607        }
15608    "});
15609
15610    // If a selection includes multiple comment prefixes, all lines are uncommented.
15611    cx.set_state(indoc! {"
15612        fn a() {
15613            «// a();
15614            /// b();
15615            //! c();ˇ»
15616        }
15617    "});
15618
15619    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15620
15621    cx.assert_editor_state(indoc! {"
15622        fn a() {
15623            «a();
15624            b();
15625            c();ˇ»
15626        }
15627    "});
15628}
15629
15630#[gpui::test]
15631async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15632    init_test(cx, |_| {});
15633    let mut cx = EditorTestContext::new(cx).await;
15634    let language = Arc::new(Language::new(
15635        LanguageConfig {
15636            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15637            ..Default::default()
15638        },
15639        Some(tree_sitter_rust::LANGUAGE.into()),
15640    ));
15641    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15642
15643    let toggle_comments = &ToggleComments {
15644        advance_downwards: false,
15645        ignore_indent: true,
15646    };
15647
15648    // If multiple selections intersect a line, the line is only toggled once.
15649    cx.set_state(indoc! {"
15650        fn a() {
15651        //    «b();
15652        //    c();
15653        //    ˇ» d();
15654        }
15655    "});
15656
15657    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15658
15659    cx.assert_editor_state(indoc! {"
15660        fn a() {
15661            «b();
15662            c();
15663            ˇ» d();
15664        }
15665    "});
15666
15667    // The comment prefix is inserted at the beginning of each line
15668    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15669
15670    cx.assert_editor_state(indoc! {"
15671        fn a() {
15672        //    «b();
15673        //    c();
15674        //    ˇ» d();
15675        }
15676    "});
15677
15678    // If a selection ends at the beginning of a line, that line is not toggled.
15679    cx.set_selections_state(indoc! {"
15680        fn a() {
15681        //    b();
15682        //    «c();
15683        ˇ»//     d();
15684        }
15685    "});
15686
15687    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15688
15689    cx.assert_editor_state(indoc! {"
15690        fn a() {
15691        //    b();
15692            «c();
15693        ˇ»//     d();
15694        }
15695    "});
15696
15697    // If a selection span a single line and is empty, the line is toggled.
15698    cx.set_state(indoc! {"
15699        fn a() {
15700            a();
15701            b();
15702        ˇ
15703        }
15704    "});
15705
15706    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15707
15708    cx.assert_editor_state(indoc! {"
15709        fn a() {
15710            a();
15711            b();
15712        //ˇ
15713        }
15714    "});
15715
15716    // If a selection span multiple lines, empty lines are not toggled.
15717    cx.set_state(indoc! {"
15718        fn a() {
15719            «a();
15720
15721            c();ˇ»
15722        }
15723    "});
15724
15725    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15726
15727    cx.assert_editor_state(indoc! {"
15728        fn a() {
15729        //    «a();
15730
15731        //    c();ˇ»
15732        }
15733    "});
15734
15735    // If a selection includes multiple comment prefixes, all lines are uncommented.
15736    cx.set_state(indoc! {"
15737        fn a() {
15738        //    «a();
15739        ///    b();
15740        //!    c();ˇ»
15741        }
15742    "});
15743
15744    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15745
15746    cx.assert_editor_state(indoc! {"
15747        fn a() {
15748            «a();
15749            b();
15750            c();ˇ»
15751        }
15752    "});
15753}
15754
15755#[gpui::test]
15756async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15757    init_test(cx, |_| {});
15758
15759    let language = Arc::new(Language::new(
15760        LanguageConfig {
15761            line_comments: vec!["// ".into()],
15762            ..Default::default()
15763        },
15764        Some(tree_sitter_rust::LANGUAGE.into()),
15765    ));
15766
15767    let mut cx = EditorTestContext::new(cx).await;
15768
15769    cx.language_registry().add(language.clone());
15770    cx.update_buffer(|buffer, cx| {
15771        buffer.set_language(Some(language), cx);
15772    });
15773
15774    let toggle_comments = &ToggleComments {
15775        advance_downwards: true,
15776        ignore_indent: false,
15777    };
15778
15779    // Single cursor on one line -> advance
15780    // Cursor moves horizontally 3 characters as well on non-blank line
15781    cx.set_state(indoc!(
15782        "fn a() {
15783             ˇdog();
15784             cat();
15785        }"
15786    ));
15787    cx.update_editor(|editor, window, cx| {
15788        editor.toggle_comments(toggle_comments, window, cx);
15789    });
15790    cx.assert_editor_state(indoc!(
15791        "fn a() {
15792             // dog();
15793             catˇ();
15794        }"
15795    ));
15796
15797    // Single selection on one line -> don't advance
15798    cx.set_state(indoc!(
15799        "fn a() {
15800             «dog()ˇ»;
15801             cat();
15802        }"
15803    ));
15804    cx.update_editor(|editor, window, cx| {
15805        editor.toggle_comments(toggle_comments, window, cx);
15806    });
15807    cx.assert_editor_state(indoc!(
15808        "fn a() {
15809             // «dog()ˇ»;
15810             cat();
15811        }"
15812    ));
15813
15814    // Multiple cursors on one line -> advance
15815    cx.set_state(indoc!(
15816        "fn a() {
15817             ˇdˇog();
15818             cat();
15819        }"
15820    ));
15821    cx.update_editor(|editor, window, cx| {
15822        editor.toggle_comments(toggle_comments, window, cx);
15823    });
15824    cx.assert_editor_state(indoc!(
15825        "fn a() {
15826             // dog();
15827             catˇ(ˇ);
15828        }"
15829    ));
15830
15831    // Multiple cursors on one line, with selection -> don't advance
15832    cx.set_state(indoc!(
15833        "fn a() {
15834             ˇdˇog«()ˇ»;
15835             cat();
15836        }"
15837    ));
15838    cx.update_editor(|editor, window, cx| {
15839        editor.toggle_comments(toggle_comments, window, cx);
15840    });
15841    cx.assert_editor_state(indoc!(
15842        "fn a() {
15843             // ˇdˇog«()ˇ»;
15844             cat();
15845        }"
15846    ));
15847
15848    // Single cursor on one line -> advance
15849    // Cursor moves to column 0 on blank line
15850    cx.set_state(indoc!(
15851        "fn a() {
15852             ˇdog();
15853
15854             cat();
15855        }"
15856    ));
15857    cx.update_editor(|editor, window, cx| {
15858        editor.toggle_comments(toggle_comments, window, cx);
15859    });
15860    cx.assert_editor_state(indoc!(
15861        "fn a() {
15862             // dog();
15863        ˇ
15864             cat();
15865        }"
15866    ));
15867
15868    // Single cursor on one line -> advance
15869    // Cursor starts and ends at column 0
15870    cx.set_state(indoc!(
15871        "fn a() {
15872         ˇ    dog();
15873             cat();
15874        }"
15875    ));
15876    cx.update_editor(|editor, window, cx| {
15877        editor.toggle_comments(toggle_comments, window, cx);
15878    });
15879    cx.assert_editor_state(indoc!(
15880        "fn a() {
15881             // dog();
15882         ˇ    cat();
15883        }"
15884    ));
15885}
15886
15887#[gpui::test]
15888async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15889    init_test(cx, |_| {});
15890
15891    let mut cx = EditorTestContext::new(cx).await;
15892
15893    let html_language = Arc::new(
15894        Language::new(
15895            LanguageConfig {
15896                name: "HTML".into(),
15897                block_comment: Some(BlockCommentConfig {
15898                    start: "<!-- ".into(),
15899                    prefix: "".into(),
15900                    end: " -->".into(),
15901                    tab_size: 0,
15902                }),
15903                ..Default::default()
15904            },
15905            Some(tree_sitter_html::LANGUAGE.into()),
15906        )
15907        .with_injection_query(
15908            r#"
15909            (script_element
15910                (raw_text) @injection.content
15911                (#set! injection.language "javascript"))
15912            "#,
15913        )
15914        .unwrap(),
15915    );
15916
15917    let javascript_language = Arc::new(Language::new(
15918        LanguageConfig {
15919            name: "JavaScript".into(),
15920            line_comments: vec!["// ".into()],
15921            ..Default::default()
15922        },
15923        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15924    ));
15925
15926    cx.language_registry().add(html_language.clone());
15927    cx.language_registry().add(javascript_language);
15928    cx.update_buffer(|buffer, cx| {
15929        buffer.set_language(Some(html_language), cx);
15930    });
15931
15932    // Toggle comments for empty selections
15933    cx.set_state(
15934        &r#"
15935            <p>A</p>ˇ
15936            <p>B</p>ˇ
15937            <p>C</p>ˇ
15938        "#
15939        .unindent(),
15940    );
15941    cx.update_editor(|editor, window, cx| {
15942        editor.toggle_comments(&ToggleComments::default(), window, cx)
15943    });
15944    cx.assert_editor_state(
15945        &r#"
15946            <!-- <p>A</p>ˇ -->
15947            <!-- <p>B</p>ˇ -->
15948            <!-- <p>C</p>ˇ -->
15949        "#
15950        .unindent(),
15951    );
15952    cx.update_editor(|editor, window, cx| {
15953        editor.toggle_comments(&ToggleComments::default(), window, cx)
15954    });
15955    cx.assert_editor_state(
15956        &r#"
15957            <p>A</p>ˇ
15958            <p>B</p>ˇ
15959            <p>C</p>ˇ
15960        "#
15961        .unindent(),
15962    );
15963
15964    // Toggle comments for mixture of empty and non-empty selections, where
15965    // multiple selections occupy a given line.
15966    cx.set_state(
15967        &r#"
15968            <p>A«</p>
15969            <p>ˇ»B</p>ˇ
15970            <p>C«</p>
15971            <p>ˇ»D</p>ˇ
15972        "#
15973        .unindent(),
15974    );
15975
15976    cx.update_editor(|editor, window, cx| {
15977        editor.toggle_comments(&ToggleComments::default(), window, cx)
15978    });
15979    cx.assert_editor_state(
15980        &r#"
15981            <!-- <p>A«</p>
15982            <p>ˇ»B</p>ˇ -->
15983            <!-- <p>C«</p>
15984            <p>ˇ»D</p>ˇ -->
15985        "#
15986        .unindent(),
15987    );
15988    cx.update_editor(|editor, window, cx| {
15989        editor.toggle_comments(&ToggleComments::default(), window, cx)
15990    });
15991    cx.assert_editor_state(
15992        &r#"
15993            <p>A«</p>
15994            <p>ˇ»B</p>ˇ
15995            <p>C«</p>
15996            <p>ˇ»D</p>ˇ
15997        "#
15998        .unindent(),
15999    );
16000
16001    // Toggle comments when different languages are active for different
16002    // selections.
16003    cx.set_state(
16004        &r#"
16005            ˇ<script>
16006                ˇvar x = new Y();
16007            ˇ</script>
16008        "#
16009        .unindent(),
16010    );
16011    cx.executor().run_until_parked();
16012    cx.update_editor(|editor, window, cx| {
16013        editor.toggle_comments(&ToggleComments::default(), window, cx)
16014    });
16015    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16016    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16017    cx.assert_editor_state(
16018        &r#"
16019            <!-- ˇ<script> -->
16020                // ˇvar x = new Y();
16021            <!-- ˇ</script> -->
16022        "#
16023        .unindent(),
16024    );
16025}
16026
16027#[gpui::test]
16028fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16029    init_test(cx, |_| {});
16030
16031    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16032    let multibuffer = cx.new(|cx| {
16033        let mut multibuffer = MultiBuffer::new(ReadWrite);
16034        multibuffer.push_excerpts(
16035            buffer.clone(),
16036            [
16037                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16038                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16039            ],
16040            cx,
16041        );
16042        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16043        multibuffer
16044    });
16045
16046    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16047    editor.update_in(cx, |editor, window, cx| {
16048        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16050            s.select_ranges([
16051                Point::new(0, 0)..Point::new(0, 0),
16052                Point::new(1, 0)..Point::new(1, 0),
16053            ])
16054        });
16055
16056        editor.handle_input("X", window, cx);
16057        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16058        assert_eq!(
16059            editor.selections.ranges(&editor.display_snapshot(cx)),
16060            [
16061                Point::new(0, 1)..Point::new(0, 1),
16062                Point::new(1, 1)..Point::new(1, 1),
16063            ]
16064        );
16065
16066        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16067        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16068            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16069        });
16070        editor.backspace(&Default::default(), window, cx);
16071        assert_eq!(editor.text(cx), "Xa\nbbb");
16072        assert_eq!(
16073            editor.selections.ranges(&editor.display_snapshot(cx)),
16074            [Point::new(1, 0)..Point::new(1, 0)]
16075        );
16076
16077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16078            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16079        });
16080        editor.backspace(&Default::default(), window, cx);
16081        assert_eq!(editor.text(cx), "X\nbb");
16082        assert_eq!(
16083            editor.selections.ranges(&editor.display_snapshot(cx)),
16084            [Point::new(0, 1)..Point::new(0, 1)]
16085        );
16086    });
16087}
16088
16089#[gpui::test]
16090fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16091    init_test(cx, |_| {});
16092
16093    let markers = vec![('[', ']').into(), ('(', ')').into()];
16094    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16095        indoc! {"
16096            [aaaa
16097            (bbbb]
16098            cccc)",
16099        },
16100        markers.clone(),
16101    );
16102    let excerpt_ranges = markers.into_iter().map(|marker| {
16103        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16104        ExcerptRange::new(context)
16105    });
16106    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16107    let multibuffer = cx.new(|cx| {
16108        let mut multibuffer = MultiBuffer::new(ReadWrite);
16109        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16110        multibuffer
16111    });
16112
16113    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16114    editor.update_in(cx, |editor, window, cx| {
16115        let (expected_text, selection_ranges) = marked_text_ranges(
16116            indoc! {"
16117                aaaa
16118                bˇbbb
16119                bˇbbˇb
16120                cccc"
16121            },
16122            true,
16123        );
16124        assert_eq!(editor.text(cx), expected_text);
16125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16126            s.select_ranges(selection_ranges)
16127        });
16128
16129        editor.handle_input("X", window, cx);
16130
16131        let (expected_text, expected_selections) = marked_text_ranges(
16132            indoc! {"
16133                aaaa
16134                bXˇbbXb
16135                bXˇbbXˇb
16136                cccc"
16137            },
16138            false,
16139        );
16140        assert_eq!(editor.text(cx), expected_text);
16141        assert_eq!(
16142            editor.selections.ranges(&editor.display_snapshot(cx)),
16143            expected_selections
16144        );
16145
16146        editor.newline(&Newline, window, cx);
16147        let (expected_text, expected_selections) = marked_text_ranges(
16148            indoc! {"
16149                aaaa
16150                bX
16151                ˇbbX
16152                b
16153                bX
16154                ˇbbX
16155                ˇb
16156                cccc"
16157            },
16158            false,
16159        );
16160        assert_eq!(editor.text(cx), expected_text);
16161        assert_eq!(
16162            editor.selections.ranges(&editor.display_snapshot(cx)),
16163            expected_selections
16164        );
16165    });
16166}
16167
16168#[gpui::test]
16169fn test_refresh_selections(cx: &mut TestAppContext) {
16170    init_test(cx, |_| {});
16171
16172    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16173    let mut excerpt1_id = None;
16174    let multibuffer = cx.new(|cx| {
16175        let mut multibuffer = MultiBuffer::new(ReadWrite);
16176        excerpt1_id = multibuffer
16177            .push_excerpts(
16178                buffer.clone(),
16179                [
16180                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16181                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16182                ],
16183                cx,
16184            )
16185            .into_iter()
16186            .next();
16187        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16188        multibuffer
16189    });
16190
16191    let editor = cx.add_window(|window, cx| {
16192        let mut editor = build_editor(multibuffer.clone(), window, cx);
16193        let snapshot = editor.snapshot(window, cx);
16194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16195            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16196        });
16197        editor.begin_selection(
16198            Point::new(2, 1).to_display_point(&snapshot),
16199            true,
16200            1,
16201            window,
16202            cx,
16203        );
16204        assert_eq!(
16205            editor.selections.ranges(&editor.display_snapshot(cx)),
16206            [
16207                Point::new(1, 3)..Point::new(1, 3),
16208                Point::new(2, 1)..Point::new(2, 1),
16209            ]
16210        );
16211        editor
16212    });
16213
16214    // Refreshing selections is a no-op when excerpts haven't changed.
16215    _ = editor.update(cx, |editor, window, cx| {
16216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16217        assert_eq!(
16218            editor.selections.ranges(&editor.display_snapshot(cx)),
16219            [
16220                Point::new(1, 3)..Point::new(1, 3),
16221                Point::new(2, 1)..Point::new(2, 1),
16222            ]
16223        );
16224    });
16225
16226    multibuffer.update(cx, |multibuffer, cx| {
16227        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16228    });
16229    _ = editor.update(cx, |editor, window, cx| {
16230        // Removing an excerpt causes the first selection to become degenerate.
16231        assert_eq!(
16232            editor.selections.ranges(&editor.display_snapshot(cx)),
16233            [
16234                Point::new(0, 0)..Point::new(0, 0),
16235                Point::new(0, 1)..Point::new(0, 1)
16236            ]
16237        );
16238
16239        // Refreshing selections will relocate the first selection to the original buffer
16240        // location.
16241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16242        assert_eq!(
16243            editor.selections.ranges(&editor.display_snapshot(cx)),
16244            [
16245                Point::new(0, 1)..Point::new(0, 1),
16246                Point::new(0, 3)..Point::new(0, 3)
16247            ]
16248        );
16249        assert!(editor.selections.pending_anchor().is_some());
16250    });
16251}
16252
16253#[gpui::test]
16254fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16255    init_test(cx, |_| {});
16256
16257    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16258    let mut excerpt1_id = None;
16259    let multibuffer = cx.new(|cx| {
16260        let mut multibuffer = MultiBuffer::new(ReadWrite);
16261        excerpt1_id = multibuffer
16262            .push_excerpts(
16263                buffer.clone(),
16264                [
16265                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16266                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16267                ],
16268                cx,
16269            )
16270            .into_iter()
16271            .next();
16272        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16273        multibuffer
16274    });
16275
16276    let editor = cx.add_window(|window, cx| {
16277        let mut editor = build_editor(multibuffer.clone(), window, cx);
16278        let snapshot = editor.snapshot(window, cx);
16279        editor.begin_selection(
16280            Point::new(1, 3).to_display_point(&snapshot),
16281            false,
16282            1,
16283            window,
16284            cx,
16285        );
16286        assert_eq!(
16287            editor.selections.ranges(&editor.display_snapshot(cx)),
16288            [Point::new(1, 3)..Point::new(1, 3)]
16289        );
16290        editor
16291    });
16292
16293    multibuffer.update(cx, |multibuffer, cx| {
16294        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16295    });
16296    _ = editor.update(cx, |editor, window, cx| {
16297        assert_eq!(
16298            editor.selections.ranges(&editor.display_snapshot(cx)),
16299            [Point::new(0, 0)..Point::new(0, 0)]
16300        );
16301
16302        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16304        assert_eq!(
16305            editor.selections.ranges(&editor.display_snapshot(cx)),
16306            [Point::new(0, 3)..Point::new(0, 3)]
16307        );
16308        assert!(editor.selections.pending_anchor().is_some());
16309    });
16310}
16311
16312#[gpui::test]
16313async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16314    init_test(cx, |_| {});
16315
16316    let language = Arc::new(
16317        Language::new(
16318            LanguageConfig {
16319                brackets: BracketPairConfig {
16320                    pairs: vec![
16321                        BracketPair {
16322                            start: "{".to_string(),
16323                            end: "}".to_string(),
16324                            close: true,
16325                            surround: true,
16326                            newline: true,
16327                        },
16328                        BracketPair {
16329                            start: "/* ".to_string(),
16330                            end: " */".to_string(),
16331                            close: true,
16332                            surround: true,
16333                            newline: true,
16334                        },
16335                    ],
16336                    ..Default::default()
16337                },
16338                ..Default::default()
16339            },
16340            Some(tree_sitter_rust::LANGUAGE.into()),
16341        )
16342        .with_indents_query("")
16343        .unwrap(),
16344    );
16345
16346    let text = concat!(
16347        "{   }\n",     //
16348        "  x\n",       //
16349        "  /*   */\n", //
16350        "x\n",         //
16351        "{{} }\n",     //
16352    );
16353
16354    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16355    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16356    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16357    editor
16358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16359        .await;
16360
16361    editor.update_in(cx, |editor, window, cx| {
16362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16363            s.select_display_ranges([
16364                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16365                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16366                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16367            ])
16368        });
16369        editor.newline(&Newline, window, cx);
16370
16371        assert_eq!(
16372            editor.buffer().read(cx).read(cx).text(),
16373            concat!(
16374                "{ \n",    // Suppress rustfmt
16375                "\n",      //
16376                "}\n",     //
16377                "  x\n",   //
16378                "  /* \n", //
16379                "  \n",    //
16380                "  */\n",  //
16381                "x\n",     //
16382                "{{} \n",  //
16383                "}\n",     //
16384            )
16385        );
16386    });
16387}
16388
16389#[gpui::test]
16390fn test_highlighted_ranges(cx: &mut TestAppContext) {
16391    init_test(cx, |_| {});
16392
16393    let editor = cx.add_window(|window, cx| {
16394        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16395        build_editor(buffer, window, cx)
16396    });
16397
16398    _ = editor.update(cx, |editor, window, cx| {
16399        struct Type1;
16400        struct Type2;
16401
16402        let buffer = editor.buffer.read(cx).snapshot(cx);
16403
16404        let anchor_range =
16405            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16406
16407        editor.highlight_background::<Type1>(
16408            &[
16409                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16410                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16411                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16412                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16413            ],
16414            |_| Hsla::red(),
16415            cx,
16416        );
16417        editor.highlight_background::<Type2>(
16418            &[
16419                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16420                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16421                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16422                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16423            ],
16424            |_| Hsla::green(),
16425            cx,
16426        );
16427
16428        let snapshot = editor.snapshot(window, cx);
16429        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16430            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16431            &snapshot,
16432            cx.theme(),
16433        );
16434        assert_eq!(
16435            highlighted_ranges,
16436            &[
16437                (
16438                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16439                    Hsla::green(),
16440                ),
16441                (
16442                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16443                    Hsla::red(),
16444                ),
16445                (
16446                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16447                    Hsla::green(),
16448                ),
16449                (
16450                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16451                    Hsla::red(),
16452                ),
16453            ]
16454        );
16455        assert_eq!(
16456            editor.sorted_background_highlights_in_range(
16457                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16458                &snapshot,
16459                cx.theme(),
16460            ),
16461            &[(
16462                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16463                Hsla::red(),
16464            )]
16465        );
16466    });
16467}
16468
16469#[gpui::test]
16470async fn test_following(cx: &mut TestAppContext) {
16471    init_test(cx, |_| {});
16472
16473    let fs = FakeFs::new(cx.executor());
16474    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16475
16476    let buffer = project.update(cx, |project, cx| {
16477        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16478        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16479    });
16480    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16481    let follower = cx.update(|cx| {
16482        cx.open_window(
16483            WindowOptions {
16484                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16485                    gpui::Point::new(px(0.), px(0.)),
16486                    gpui::Point::new(px(10.), px(80.)),
16487                ))),
16488                ..Default::default()
16489            },
16490            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16491        )
16492        .unwrap()
16493    });
16494
16495    let is_still_following = Rc::new(RefCell::new(true));
16496    let follower_edit_event_count = Rc::new(RefCell::new(0));
16497    let pending_update = Rc::new(RefCell::new(None));
16498    let leader_entity = leader.root(cx).unwrap();
16499    let follower_entity = follower.root(cx).unwrap();
16500    _ = follower.update(cx, {
16501        let update = pending_update.clone();
16502        let is_still_following = is_still_following.clone();
16503        let follower_edit_event_count = follower_edit_event_count.clone();
16504        |_, window, cx| {
16505            cx.subscribe_in(
16506                &leader_entity,
16507                window,
16508                move |_, leader, event, window, cx| {
16509                    leader.read(cx).add_event_to_update_proto(
16510                        event,
16511                        &mut update.borrow_mut(),
16512                        window,
16513                        cx,
16514                    );
16515                },
16516            )
16517            .detach();
16518
16519            cx.subscribe_in(
16520                &follower_entity,
16521                window,
16522                move |_, _, event: &EditorEvent, _window, _cx| {
16523                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16524                        *is_still_following.borrow_mut() = false;
16525                    }
16526
16527                    if let EditorEvent::BufferEdited = event {
16528                        *follower_edit_event_count.borrow_mut() += 1;
16529                    }
16530                },
16531            )
16532            .detach();
16533        }
16534    });
16535
16536    // Update the selections only
16537    _ = leader.update(cx, |leader, window, cx| {
16538        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16539            s.select_ranges([1..1])
16540        });
16541    });
16542    follower
16543        .update(cx, |follower, window, cx| {
16544            follower.apply_update_proto(
16545                &project,
16546                pending_update.borrow_mut().take().unwrap(),
16547                window,
16548                cx,
16549            )
16550        })
16551        .unwrap()
16552        .await
16553        .unwrap();
16554    _ = follower.update(cx, |follower, _, cx| {
16555        assert_eq!(
16556            follower.selections.ranges(&follower.display_snapshot(cx)),
16557            vec![1..1]
16558        );
16559    });
16560    assert!(*is_still_following.borrow());
16561    assert_eq!(*follower_edit_event_count.borrow(), 0);
16562
16563    // Update the scroll position only
16564    _ = leader.update(cx, |leader, window, cx| {
16565        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16566    });
16567    follower
16568        .update(cx, |follower, window, cx| {
16569            follower.apply_update_proto(
16570                &project,
16571                pending_update.borrow_mut().take().unwrap(),
16572                window,
16573                cx,
16574            )
16575        })
16576        .unwrap()
16577        .await
16578        .unwrap();
16579    assert_eq!(
16580        follower
16581            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16582            .unwrap(),
16583        gpui::Point::new(1.5, 3.5)
16584    );
16585    assert!(*is_still_following.borrow());
16586    assert_eq!(*follower_edit_event_count.borrow(), 0);
16587
16588    // Update the selections and scroll position. The follower's scroll position is updated
16589    // via autoscroll, not via the leader's exact scroll position.
16590    _ = leader.update(cx, |leader, window, cx| {
16591        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16592            s.select_ranges([0..0])
16593        });
16594        leader.request_autoscroll(Autoscroll::newest(), cx);
16595        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16596    });
16597    follower
16598        .update(cx, |follower, window, cx| {
16599            follower.apply_update_proto(
16600                &project,
16601                pending_update.borrow_mut().take().unwrap(),
16602                window,
16603                cx,
16604            )
16605        })
16606        .unwrap()
16607        .await
16608        .unwrap();
16609    _ = follower.update(cx, |follower, _, cx| {
16610        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16611        assert_eq!(
16612            follower.selections.ranges(&follower.display_snapshot(cx)),
16613            vec![0..0]
16614        );
16615    });
16616    assert!(*is_still_following.borrow());
16617
16618    // Creating a pending selection that precedes another selection
16619    _ = leader.update(cx, |leader, window, cx| {
16620        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16621            s.select_ranges([1..1])
16622        });
16623        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16624    });
16625    follower
16626        .update(cx, |follower, window, cx| {
16627            follower.apply_update_proto(
16628                &project,
16629                pending_update.borrow_mut().take().unwrap(),
16630                window,
16631                cx,
16632            )
16633        })
16634        .unwrap()
16635        .await
16636        .unwrap();
16637    _ = follower.update(cx, |follower, _, cx| {
16638        assert_eq!(
16639            follower.selections.ranges(&follower.display_snapshot(cx)),
16640            vec![0..0, 1..1]
16641        );
16642    });
16643    assert!(*is_still_following.borrow());
16644
16645    // Extend the pending selection so that it surrounds another selection
16646    _ = leader.update(cx, |leader, window, cx| {
16647        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16648    });
16649    follower
16650        .update(cx, |follower, window, cx| {
16651            follower.apply_update_proto(
16652                &project,
16653                pending_update.borrow_mut().take().unwrap(),
16654                window,
16655                cx,
16656            )
16657        })
16658        .unwrap()
16659        .await
16660        .unwrap();
16661    _ = follower.update(cx, |follower, _, cx| {
16662        assert_eq!(
16663            follower.selections.ranges(&follower.display_snapshot(cx)),
16664            vec![0..2]
16665        );
16666    });
16667
16668    // Scrolling locally breaks the follow
16669    _ = follower.update(cx, |follower, window, cx| {
16670        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16671        follower.set_scroll_anchor(
16672            ScrollAnchor {
16673                anchor: top_anchor,
16674                offset: gpui::Point::new(0.0, 0.5),
16675            },
16676            window,
16677            cx,
16678        );
16679    });
16680    assert!(!(*is_still_following.borrow()));
16681}
16682
16683#[gpui::test]
16684async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16685    init_test(cx, |_| {});
16686
16687    let fs = FakeFs::new(cx.executor());
16688    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16689    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16690    let pane = workspace
16691        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16692        .unwrap();
16693
16694    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16695
16696    let leader = pane.update_in(cx, |_, window, cx| {
16697        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16698        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16699    });
16700
16701    // Start following the editor when it has no excerpts.
16702    let mut state_message =
16703        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16704    let workspace_entity = workspace.root(cx).unwrap();
16705    let follower_1 = cx
16706        .update_window(*workspace.deref(), |_, window, cx| {
16707            Editor::from_state_proto(
16708                workspace_entity,
16709                ViewId {
16710                    creator: CollaboratorId::PeerId(PeerId::default()),
16711                    id: 0,
16712                },
16713                &mut state_message,
16714                window,
16715                cx,
16716            )
16717        })
16718        .unwrap()
16719        .unwrap()
16720        .await
16721        .unwrap();
16722
16723    let update_message = Rc::new(RefCell::new(None));
16724    follower_1.update_in(cx, {
16725        let update = update_message.clone();
16726        |_, window, cx| {
16727            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16728                leader.read(cx).add_event_to_update_proto(
16729                    event,
16730                    &mut update.borrow_mut(),
16731                    window,
16732                    cx,
16733                );
16734            })
16735            .detach();
16736        }
16737    });
16738
16739    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16740        (
16741            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16742            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16743        )
16744    });
16745
16746    // Insert some excerpts.
16747    leader.update(cx, |leader, cx| {
16748        leader.buffer.update(cx, |multibuffer, cx| {
16749            multibuffer.set_excerpts_for_path(
16750                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16751                buffer_1.clone(),
16752                vec![
16753                    Point::row_range(0..3),
16754                    Point::row_range(1..6),
16755                    Point::row_range(12..15),
16756                ],
16757                0,
16758                cx,
16759            );
16760            multibuffer.set_excerpts_for_path(
16761                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16762                buffer_2.clone(),
16763                vec![Point::row_range(0..6), Point::row_range(8..12)],
16764                0,
16765                cx,
16766            );
16767        });
16768    });
16769
16770    // Apply the update of adding the excerpts.
16771    follower_1
16772        .update_in(cx, |follower, window, cx| {
16773            follower.apply_update_proto(
16774                &project,
16775                update_message.borrow().clone().unwrap(),
16776                window,
16777                cx,
16778            )
16779        })
16780        .await
16781        .unwrap();
16782    assert_eq!(
16783        follower_1.update(cx, |editor, cx| editor.text(cx)),
16784        leader.update(cx, |editor, cx| editor.text(cx))
16785    );
16786    update_message.borrow_mut().take();
16787
16788    // Start following separately after it already has excerpts.
16789    let mut state_message =
16790        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16791    let workspace_entity = workspace.root(cx).unwrap();
16792    let follower_2 = cx
16793        .update_window(*workspace.deref(), |_, window, cx| {
16794            Editor::from_state_proto(
16795                workspace_entity,
16796                ViewId {
16797                    creator: CollaboratorId::PeerId(PeerId::default()),
16798                    id: 0,
16799                },
16800                &mut state_message,
16801                window,
16802                cx,
16803            )
16804        })
16805        .unwrap()
16806        .unwrap()
16807        .await
16808        .unwrap();
16809    assert_eq!(
16810        follower_2.update(cx, |editor, cx| editor.text(cx)),
16811        leader.update(cx, |editor, cx| editor.text(cx))
16812    );
16813
16814    // Remove some excerpts.
16815    leader.update(cx, |leader, cx| {
16816        leader.buffer.update(cx, |multibuffer, cx| {
16817            let excerpt_ids = multibuffer.excerpt_ids();
16818            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16819            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16820        });
16821    });
16822
16823    // Apply the update of removing the excerpts.
16824    follower_1
16825        .update_in(cx, |follower, window, cx| {
16826            follower.apply_update_proto(
16827                &project,
16828                update_message.borrow().clone().unwrap(),
16829                window,
16830                cx,
16831            )
16832        })
16833        .await
16834        .unwrap();
16835    follower_2
16836        .update_in(cx, |follower, window, cx| {
16837            follower.apply_update_proto(
16838                &project,
16839                update_message.borrow().clone().unwrap(),
16840                window,
16841                cx,
16842            )
16843        })
16844        .await
16845        .unwrap();
16846    update_message.borrow_mut().take();
16847    assert_eq!(
16848        follower_1.update(cx, |editor, cx| editor.text(cx)),
16849        leader.update(cx, |editor, cx| editor.text(cx))
16850    );
16851}
16852
16853#[gpui::test]
16854async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16855    init_test(cx, |_| {});
16856
16857    let mut cx = EditorTestContext::new(cx).await;
16858    let lsp_store =
16859        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16860
16861    cx.set_state(indoc! {"
16862        ˇfn func(abc def: i32) -> u32 {
16863        }
16864    "});
16865
16866    cx.update(|_, cx| {
16867        lsp_store.update(cx, |lsp_store, cx| {
16868            lsp_store
16869                .update_diagnostics(
16870                    LanguageServerId(0),
16871                    lsp::PublishDiagnosticsParams {
16872                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16873                        version: None,
16874                        diagnostics: vec![
16875                            lsp::Diagnostic {
16876                                range: lsp::Range::new(
16877                                    lsp::Position::new(0, 11),
16878                                    lsp::Position::new(0, 12),
16879                                ),
16880                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16881                                ..Default::default()
16882                            },
16883                            lsp::Diagnostic {
16884                                range: lsp::Range::new(
16885                                    lsp::Position::new(0, 12),
16886                                    lsp::Position::new(0, 15),
16887                                ),
16888                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16889                                ..Default::default()
16890                            },
16891                            lsp::Diagnostic {
16892                                range: lsp::Range::new(
16893                                    lsp::Position::new(0, 25),
16894                                    lsp::Position::new(0, 28),
16895                                ),
16896                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16897                                ..Default::default()
16898                            },
16899                        ],
16900                    },
16901                    None,
16902                    DiagnosticSourceKind::Pushed,
16903                    &[],
16904                    cx,
16905                )
16906                .unwrap()
16907        });
16908    });
16909
16910    executor.run_until_parked();
16911
16912    cx.update_editor(|editor, window, cx| {
16913        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16914    });
16915
16916    cx.assert_editor_state(indoc! {"
16917        fn func(abc def: i32) -> ˇu32 {
16918        }
16919    "});
16920
16921    cx.update_editor(|editor, window, cx| {
16922        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16923    });
16924
16925    cx.assert_editor_state(indoc! {"
16926        fn func(abc ˇdef: i32) -> u32 {
16927        }
16928    "});
16929
16930    cx.update_editor(|editor, window, cx| {
16931        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16932    });
16933
16934    cx.assert_editor_state(indoc! {"
16935        fn func(abcˇ def: i32) -> u32 {
16936        }
16937    "});
16938
16939    cx.update_editor(|editor, window, cx| {
16940        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16941    });
16942
16943    cx.assert_editor_state(indoc! {"
16944        fn func(abc def: i32) -> ˇu32 {
16945        }
16946    "});
16947}
16948
16949#[gpui::test]
16950async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16951    init_test(cx, |_| {});
16952
16953    let mut cx = EditorTestContext::new(cx).await;
16954
16955    let diff_base = r#"
16956        use some::mod;
16957
16958        const A: u32 = 42;
16959
16960        fn main() {
16961            println!("hello");
16962
16963            println!("world");
16964        }
16965        "#
16966    .unindent();
16967
16968    // Edits are modified, removed, modified, added
16969    cx.set_state(
16970        &r#"
16971        use some::modified;
16972
16973        ˇ
16974        fn main() {
16975            println!("hello there");
16976
16977            println!("around the");
16978            println!("world");
16979        }
16980        "#
16981        .unindent(),
16982    );
16983
16984    cx.set_head_text(&diff_base);
16985    executor.run_until_parked();
16986
16987    cx.update_editor(|editor, window, cx| {
16988        //Wrap around the bottom of the buffer
16989        for _ in 0..3 {
16990            editor.go_to_next_hunk(&GoToHunk, window, cx);
16991        }
16992    });
16993
16994    cx.assert_editor_state(
16995        &r#"
16996        ˇuse some::modified;
16997
16998
16999        fn main() {
17000            println!("hello there");
17001
17002            println!("around the");
17003            println!("world");
17004        }
17005        "#
17006        .unindent(),
17007    );
17008
17009    cx.update_editor(|editor, window, cx| {
17010        //Wrap around the top of the buffer
17011        for _ in 0..2 {
17012            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17013        }
17014    });
17015
17016    cx.assert_editor_state(
17017        &r#"
17018        use some::modified;
17019
17020
17021        fn main() {
17022        ˇ    println!("hello there");
17023
17024            println!("around the");
17025            println!("world");
17026        }
17027        "#
17028        .unindent(),
17029    );
17030
17031    cx.update_editor(|editor, window, cx| {
17032        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17033    });
17034
17035    cx.assert_editor_state(
17036        &r#"
17037        use some::modified;
17038
17039        ˇ
17040        fn main() {
17041            println!("hello there");
17042
17043            println!("around the");
17044            println!("world");
17045        }
17046        "#
17047        .unindent(),
17048    );
17049
17050    cx.update_editor(|editor, window, cx| {
17051        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17052    });
17053
17054    cx.assert_editor_state(
17055        &r#"
17056        ˇuse some::modified;
17057
17058
17059        fn main() {
17060            println!("hello there");
17061
17062            println!("around the");
17063            println!("world");
17064        }
17065        "#
17066        .unindent(),
17067    );
17068
17069    cx.update_editor(|editor, window, cx| {
17070        for _ in 0..2 {
17071            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17072        }
17073    });
17074
17075    cx.assert_editor_state(
17076        &r#"
17077        use some::modified;
17078
17079
17080        fn main() {
17081        ˇ    println!("hello there");
17082
17083            println!("around the");
17084            println!("world");
17085        }
17086        "#
17087        .unindent(),
17088    );
17089
17090    cx.update_editor(|editor, window, cx| {
17091        editor.fold(&Fold, window, cx);
17092    });
17093
17094    cx.update_editor(|editor, window, cx| {
17095        editor.go_to_next_hunk(&GoToHunk, window, cx);
17096    });
17097
17098    cx.assert_editor_state(
17099        &r#"
17100        ˇuse some::modified;
17101
17102
17103        fn main() {
17104            println!("hello there");
17105
17106            println!("around the");
17107            println!("world");
17108        }
17109        "#
17110        .unindent(),
17111    );
17112}
17113
17114#[test]
17115fn test_split_words() {
17116    fn split(text: &str) -> Vec<&str> {
17117        split_words(text).collect()
17118    }
17119
17120    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17121    assert_eq!(split("hello_world"), &["hello_", "world"]);
17122    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17123    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17124    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17125    assert_eq!(split("helloworld"), &["helloworld"]);
17126
17127    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17128}
17129
17130#[gpui::test]
17131async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17132    init_test(cx, |_| {});
17133
17134    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17135    let mut assert = |before, after| {
17136        let _state_context = cx.set_state(before);
17137        cx.run_until_parked();
17138        cx.update_editor(|editor, window, cx| {
17139            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17140        });
17141        cx.run_until_parked();
17142        cx.assert_editor_state(after);
17143    };
17144
17145    // Outside bracket jumps to outside of matching bracket
17146    assert("console.logˇ(var);", "console.log(var)ˇ;");
17147    assert("console.log(var)ˇ;", "console.logˇ(var);");
17148
17149    // Inside bracket jumps to inside of matching bracket
17150    assert("console.log(ˇvar);", "console.log(varˇ);");
17151    assert("console.log(varˇ);", "console.log(ˇvar);");
17152
17153    // When outside a bracket and inside, favor jumping to the inside bracket
17154    assert(
17155        "console.log('foo', [1, 2, 3]ˇ);",
17156        "console.log(ˇ'foo', [1, 2, 3]);",
17157    );
17158    assert(
17159        "console.log(ˇ'foo', [1, 2, 3]);",
17160        "console.log('foo', [1, 2, 3]ˇ);",
17161    );
17162
17163    // Bias forward if two options are equally likely
17164    assert(
17165        "let result = curried_fun()ˇ();",
17166        "let result = curried_fun()()ˇ;",
17167    );
17168
17169    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17170    assert(
17171        indoc! {"
17172            function test() {
17173                console.log('test')ˇ
17174            }"},
17175        indoc! {"
17176            function test() {
17177                console.logˇ('test')
17178            }"},
17179    );
17180}
17181
17182#[gpui::test]
17183async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17184    init_test(cx, |_| {});
17185
17186    let fs = FakeFs::new(cx.executor());
17187    fs.insert_tree(
17188        path!("/a"),
17189        json!({
17190            "main.rs": "fn main() { let a = 5; }",
17191            "other.rs": "// Test file",
17192        }),
17193    )
17194    .await;
17195    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17196
17197    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17198    language_registry.add(Arc::new(Language::new(
17199        LanguageConfig {
17200            name: "Rust".into(),
17201            matcher: LanguageMatcher {
17202                path_suffixes: vec!["rs".to_string()],
17203                ..Default::default()
17204            },
17205            brackets: BracketPairConfig {
17206                pairs: vec![BracketPair {
17207                    start: "{".to_string(),
17208                    end: "}".to_string(),
17209                    close: true,
17210                    surround: true,
17211                    newline: true,
17212                }],
17213                disabled_scopes_by_bracket_ix: Vec::new(),
17214            },
17215            ..Default::default()
17216        },
17217        Some(tree_sitter_rust::LANGUAGE.into()),
17218    )));
17219    let mut fake_servers = language_registry.register_fake_lsp(
17220        "Rust",
17221        FakeLspAdapter {
17222            capabilities: lsp::ServerCapabilities {
17223                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17224                    first_trigger_character: "{".to_string(),
17225                    more_trigger_character: None,
17226                }),
17227                ..Default::default()
17228            },
17229            ..Default::default()
17230        },
17231    );
17232
17233    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17234
17235    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17236
17237    let worktree_id = workspace
17238        .update(cx, |workspace, _, cx| {
17239            workspace.project().update(cx, |project, cx| {
17240                project.worktrees(cx).next().unwrap().read(cx).id()
17241            })
17242        })
17243        .unwrap();
17244
17245    let buffer = project
17246        .update(cx, |project, cx| {
17247            project.open_local_buffer(path!("/a/main.rs"), cx)
17248        })
17249        .await
17250        .unwrap();
17251    let editor_handle = workspace
17252        .update(cx, |workspace, window, cx| {
17253            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17254        })
17255        .unwrap()
17256        .await
17257        .unwrap()
17258        .downcast::<Editor>()
17259        .unwrap();
17260
17261    cx.executor().start_waiting();
17262    let fake_server = fake_servers.next().await.unwrap();
17263
17264    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17265        |params, _| async move {
17266            assert_eq!(
17267                params.text_document_position.text_document.uri,
17268                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17269            );
17270            assert_eq!(
17271                params.text_document_position.position,
17272                lsp::Position::new(0, 21),
17273            );
17274
17275            Ok(Some(vec![lsp::TextEdit {
17276                new_text: "]".to_string(),
17277                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17278            }]))
17279        },
17280    );
17281
17282    editor_handle.update_in(cx, |editor, window, cx| {
17283        window.focus(&editor.focus_handle(cx));
17284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17285            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17286        });
17287        editor.handle_input("{", window, cx);
17288    });
17289
17290    cx.executor().run_until_parked();
17291
17292    buffer.update(cx, |buffer, _| {
17293        assert_eq!(
17294            buffer.text(),
17295            "fn main() { let a = {5}; }",
17296            "No extra braces from on type formatting should appear in the buffer"
17297        )
17298    });
17299}
17300
17301#[gpui::test(iterations = 20, seeds(31))]
17302async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17303    init_test(cx, |_| {});
17304
17305    let mut cx = EditorLspTestContext::new_rust(
17306        lsp::ServerCapabilities {
17307            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17308                first_trigger_character: ".".to_string(),
17309                more_trigger_character: None,
17310            }),
17311            ..Default::default()
17312        },
17313        cx,
17314    )
17315    .await;
17316
17317    cx.update_buffer(|buffer, _| {
17318        // This causes autoindent to be async.
17319        buffer.set_sync_parse_timeout(Duration::ZERO)
17320    });
17321
17322    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17323    cx.simulate_keystroke("\n");
17324    cx.run_until_parked();
17325
17326    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17327    let mut request =
17328        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17329            let buffer_cloned = buffer_cloned.clone();
17330            async move {
17331                buffer_cloned.update(&mut cx, |buffer, _| {
17332                    assert_eq!(
17333                        buffer.text(),
17334                        "fn c() {\n    d()\n        .\n}\n",
17335                        "OnTypeFormatting should triggered after autoindent applied"
17336                    )
17337                })?;
17338
17339                Ok(Some(vec![]))
17340            }
17341        });
17342
17343    cx.simulate_keystroke(".");
17344    cx.run_until_parked();
17345
17346    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17347    assert!(request.next().await.is_some());
17348    request.close();
17349    assert!(request.next().await.is_none());
17350}
17351
17352#[gpui::test]
17353async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17354    init_test(cx, |_| {});
17355
17356    let fs = FakeFs::new(cx.executor());
17357    fs.insert_tree(
17358        path!("/a"),
17359        json!({
17360            "main.rs": "fn main() { let a = 5; }",
17361            "other.rs": "// Test file",
17362        }),
17363    )
17364    .await;
17365
17366    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17367
17368    let server_restarts = Arc::new(AtomicUsize::new(0));
17369    let closure_restarts = Arc::clone(&server_restarts);
17370    let language_server_name = "test language server";
17371    let language_name: LanguageName = "Rust".into();
17372
17373    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17374    language_registry.add(Arc::new(Language::new(
17375        LanguageConfig {
17376            name: language_name.clone(),
17377            matcher: LanguageMatcher {
17378                path_suffixes: vec!["rs".to_string()],
17379                ..Default::default()
17380            },
17381            ..Default::default()
17382        },
17383        Some(tree_sitter_rust::LANGUAGE.into()),
17384    )));
17385    let mut fake_servers = language_registry.register_fake_lsp(
17386        "Rust",
17387        FakeLspAdapter {
17388            name: language_server_name,
17389            initialization_options: Some(json!({
17390                "testOptionValue": true
17391            })),
17392            initializer: Some(Box::new(move |fake_server| {
17393                let task_restarts = Arc::clone(&closure_restarts);
17394                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17395                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17396                    futures::future::ready(Ok(()))
17397                });
17398            })),
17399            ..Default::default()
17400        },
17401    );
17402
17403    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17404    let _buffer = project
17405        .update(cx, |project, cx| {
17406            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17407        })
17408        .await
17409        .unwrap();
17410    let _fake_server = fake_servers.next().await.unwrap();
17411    update_test_language_settings(cx, |language_settings| {
17412        language_settings.languages.0.insert(
17413            language_name.clone().0,
17414            LanguageSettingsContent {
17415                tab_size: NonZeroU32::new(8),
17416                ..Default::default()
17417            },
17418        );
17419    });
17420    cx.executor().run_until_parked();
17421    assert_eq!(
17422        server_restarts.load(atomic::Ordering::Acquire),
17423        0,
17424        "Should not restart LSP server on an unrelated change"
17425    );
17426
17427    update_test_project_settings(cx, |project_settings| {
17428        project_settings.lsp.insert(
17429            "Some other server name".into(),
17430            LspSettings {
17431                binary: None,
17432                settings: None,
17433                initialization_options: Some(json!({
17434                    "some other init value": false
17435                })),
17436                enable_lsp_tasks: false,
17437                fetch: None,
17438            },
17439        );
17440    });
17441    cx.executor().run_until_parked();
17442    assert_eq!(
17443        server_restarts.load(atomic::Ordering::Acquire),
17444        0,
17445        "Should not restart LSP server on an unrelated LSP settings change"
17446    );
17447
17448    update_test_project_settings(cx, |project_settings| {
17449        project_settings.lsp.insert(
17450            language_server_name.into(),
17451            LspSettings {
17452                binary: None,
17453                settings: None,
17454                initialization_options: Some(json!({
17455                    "anotherInitValue": false
17456                })),
17457                enable_lsp_tasks: false,
17458                fetch: None,
17459            },
17460        );
17461    });
17462    cx.executor().run_until_parked();
17463    assert_eq!(
17464        server_restarts.load(atomic::Ordering::Acquire),
17465        1,
17466        "Should restart LSP server on a related LSP settings change"
17467    );
17468
17469    update_test_project_settings(cx, |project_settings| {
17470        project_settings.lsp.insert(
17471            language_server_name.into(),
17472            LspSettings {
17473                binary: None,
17474                settings: None,
17475                initialization_options: Some(json!({
17476                    "anotherInitValue": false
17477                })),
17478                enable_lsp_tasks: false,
17479                fetch: None,
17480            },
17481        );
17482    });
17483    cx.executor().run_until_parked();
17484    assert_eq!(
17485        server_restarts.load(atomic::Ordering::Acquire),
17486        1,
17487        "Should not restart LSP server on a related LSP settings change that is the same"
17488    );
17489
17490    update_test_project_settings(cx, |project_settings| {
17491        project_settings.lsp.insert(
17492            language_server_name.into(),
17493            LspSettings {
17494                binary: None,
17495                settings: None,
17496                initialization_options: None,
17497                enable_lsp_tasks: false,
17498                fetch: None,
17499            },
17500        );
17501    });
17502    cx.executor().run_until_parked();
17503    assert_eq!(
17504        server_restarts.load(atomic::Ordering::Acquire),
17505        2,
17506        "Should restart LSP server on another related LSP settings change"
17507    );
17508}
17509
17510#[gpui::test]
17511async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17512    init_test(cx, |_| {});
17513
17514    let mut cx = EditorLspTestContext::new_rust(
17515        lsp::ServerCapabilities {
17516            completion_provider: Some(lsp::CompletionOptions {
17517                trigger_characters: Some(vec![".".to_string()]),
17518                resolve_provider: Some(true),
17519                ..Default::default()
17520            }),
17521            ..Default::default()
17522        },
17523        cx,
17524    )
17525    .await;
17526
17527    cx.set_state("fn main() { let a = 2ˇ; }");
17528    cx.simulate_keystroke(".");
17529    let completion_item = lsp::CompletionItem {
17530        label: "some".into(),
17531        kind: Some(lsp::CompletionItemKind::SNIPPET),
17532        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17533        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17534            kind: lsp::MarkupKind::Markdown,
17535            value: "```rust\nSome(2)\n```".to_string(),
17536        })),
17537        deprecated: Some(false),
17538        sort_text: Some("fffffff2".to_string()),
17539        filter_text: Some("some".to_string()),
17540        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17541        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17542            range: lsp::Range {
17543                start: lsp::Position {
17544                    line: 0,
17545                    character: 22,
17546                },
17547                end: lsp::Position {
17548                    line: 0,
17549                    character: 22,
17550                },
17551            },
17552            new_text: "Some(2)".to_string(),
17553        })),
17554        additional_text_edits: Some(vec![lsp::TextEdit {
17555            range: lsp::Range {
17556                start: lsp::Position {
17557                    line: 0,
17558                    character: 20,
17559                },
17560                end: lsp::Position {
17561                    line: 0,
17562                    character: 22,
17563                },
17564            },
17565            new_text: "".to_string(),
17566        }]),
17567        ..Default::default()
17568    };
17569
17570    let closure_completion_item = completion_item.clone();
17571    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17572        let task_completion_item = closure_completion_item.clone();
17573        async move {
17574            Ok(Some(lsp::CompletionResponse::Array(vec![
17575                task_completion_item,
17576            ])))
17577        }
17578    });
17579
17580    request.next().await;
17581
17582    cx.condition(|editor, _| editor.context_menu_visible())
17583        .await;
17584    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17585        editor
17586            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17587            .unwrap()
17588    });
17589    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17590
17591    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17592        let task_completion_item = completion_item.clone();
17593        async move { Ok(task_completion_item) }
17594    })
17595    .next()
17596    .await
17597    .unwrap();
17598    apply_additional_edits.await.unwrap();
17599    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17600}
17601
17602#[gpui::test]
17603async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17604    init_test(cx, |_| {});
17605
17606    let mut cx = EditorLspTestContext::new_rust(
17607        lsp::ServerCapabilities {
17608            completion_provider: Some(lsp::CompletionOptions {
17609                trigger_characters: Some(vec![".".to_string()]),
17610                resolve_provider: Some(true),
17611                ..Default::default()
17612            }),
17613            ..Default::default()
17614        },
17615        cx,
17616    )
17617    .await;
17618
17619    cx.set_state("fn main() { let a = 2ˇ; }");
17620    cx.simulate_keystroke(".");
17621
17622    let item1 = lsp::CompletionItem {
17623        label: "method id()".to_string(),
17624        filter_text: Some("id".to_string()),
17625        detail: None,
17626        documentation: None,
17627        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17628            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17629            new_text: ".id".to_string(),
17630        })),
17631        ..lsp::CompletionItem::default()
17632    };
17633
17634    let item2 = lsp::CompletionItem {
17635        label: "other".to_string(),
17636        filter_text: Some("other".to_string()),
17637        detail: None,
17638        documentation: None,
17639        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17640            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17641            new_text: ".other".to_string(),
17642        })),
17643        ..lsp::CompletionItem::default()
17644    };
17645
17646    let item1 = item1.clone();
17647    cx.set_request_handler::<lsp::request::Completion, _, _>({
17648        let item1 = item1.clone();
17649        move |_, _, _| {
17650            let item1 = item1.clone();
17651            let item2 = item2.clone();
17652            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17653        }
17654    })
17655    .next()
17656    .await;
17657
17658    cx.condition(|editor, _| editor.context_menu_visible())
17659        .await;
17660    cx.update_editor(|editor, _, _| {
17661        let context_menu = editor.context_menu.borrow_mut();
17662        let context_menu = context_menu
17663            .as_ref()
17664            .expect("Should have the context menu deployed");
17665        match context_menu {
17666            CodeContextMenu::Completions(completions_menu) => {
17667                let completions = completions_menu.completions.borrow_mut();
17668                assert_eq!(
17669                    completions
17670                        .iter()
17671                        .map(|completion| &completion.label.text)
17672                        .collect::<Vec<_>>(),
17673                    vec!["method id()", "other"]
17674                )
17675            }
17676            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17677        }
17678    });
17679
17680    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17681        let item1 = item1.clone();
17682        move |_, item_to_resolve, _| {
17683            let item1 = item1.clone();
17684            async move {
17685                if item1 == item_to_resolve {
17686                    Ok(lsp::CompletionItem {
17687                        label: "method id()".to_string(),
17688                        filter_text: Some("id".to_string()),
17689                        detail: Some("Now resolved!".to_string()),
17690                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17691                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17692                            range: lsp::Range::new(
17693                                lsp::Position::new(0, 22),
17694                                lsp::Position::new(0, 22),
17695                            ),
17696                            new_text: ".id".to_string(),
17697                        })),
17698                        ..lsp::CompletionItem::default()
17699                    })
17700                } else {
17701                    Ok(item_to_resolve)
17702                }
17703            }
17704        }
17705    })
17706    .next()
17707    .await
17708    .unwrap();
17709    cx.run_until_parked();
17710
17711    cx.update_editor(|editor, window, cx| {
17712        editor.context_menu_next(&Default::default(), window, cx);
17713    });
17714
17715    cx.update_editor(|editor, _, _| {
17716        let context_menu = editor.context_menu.borrow_mut();
17717        let context_menu = context_menu
17718            .as_ref()
17719            .expect("Should have the context menu deployed");
17720        match context_menu {
17721            CodeContextMenu::Completions(completions_menu) => {
17722                let completions = completions_menu.completions.borrow_mut();
17723                assert_eq!(
17724                    completions
17725                        .iter()
17726                        .map(|completion| &completion.label.text)
17727                        .collect::<Vec<_>>(),
17728                    vec!["method id() Now resolved!", "other"],
17729                    "Should update first completion label, but not second as the filter text did not match."
17730                );
17731            }
17732            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17733        }
17734    });
17735}
17736
17737#[gpui::test]
17738async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17739    init_test(cx, |_| {});
17740    let mut cx = EditorLspTestContext::new_rust(
17741        lsp::ServerCapabilities {
17742            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17743            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17744            completion_provider: Some(lsp::CompletionOptions {
17745                resolve_provider: Some(true),
17746                ..Default::default()
17747            }),
17748            ..Default::default()
17749        },
17750        cx,
17751    )
17752    .await;
17753    cx.set_state(indoc! {"
17754        struct TestStruct {
17755            field: i32
17756        }
17757
17758        fn mainˇ() {
17759            let unused_var = 42;
17760            let test_struct = TestStruct { field: 42 };
17761        }
17762    "});
17763    let symbol_range = cx.lsp_range(indoc! {"
17764        struct TestStruct {
17765            field: i32
17766        }
17767
17768        «fn main»() {
17769            let unused_var = 42;
17770            let test_struct = TestStruct { field: 42 };
17771        }
17772    "});
17773    let mut hover_requests =
17774        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17775            Ok(Some(lsp::Hover {
17776                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17777                    kind: lsp::MarkupKind::Markdown,
17778                    value: "Function documentation".to_string(),
17779                }),
17780                range: Some(symbol_range),
17781            }))
17782        });
17783
17784    // Case 1: Test that code action menu hide hover popover
17785    cx.dispatch_action(Hover);
17786    hover_requests.next().await;
17787    cx.condition(|editor, _| editor.hover_state.visible()).await;
17788    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17789        move |_, _, _| async move {
17790            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17791                lsp::CodeAction {
17792                    title: "Remove unused variable".to_string(),
17793                    kind: Some(CodeActionKind::QUICKFIX),
17794                    edit: Some(lsp::WorkspaceEdit {
17795                        changes: Some(
17796                            [(
17797                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17798                                vec![lsp::TextEdit {
17799                                    range: lsp::Range::new(
17800                                        lsp::Position::new(5, 4),
17801                                        lsp::Position::new(5, 27),
17802                                    ),
17803                                    new_text: "".to_string(),
17804                                }],
17805                            )]
17806                            .into_iter()
17807                            .collect(),
17808                        ),
17809                        ..Default::default()
17810                    }),
17811                    ..Default::default()
17812                },
17813            )]))
17814        },
17815    );
17816    cx.update_editor(|editor, window, cx| {
17817        editor.toggle_code_actions(
17818            &ToggleCodeActions {
17819                deployed_from: None,
17820                quick_launch: false,
17821            },
17822            window,
17823            cx,
17824        );
17825    });
17826    code_action_requests.next().await;
17827    cx.run_until_parked();
17828    cx.condition(|editor, _| editor.context_menu_visible())
17829        .await;
17830    cx.update_editor(|editor, _, _| {
17831        assert!(
17832            !editor.hover_state.visible(),
17833            "Hover popover should be hidden when code action menu is shown"
17834        );
17835        // Hide code actions
17836        editor.context_menu.take();
17837    });
17838
17839    // Case 2: Test that code completions hide hover popover
17840    cx.dispatch_action(Hover);
17841    hover_requests.next().await;
17842    cx.condition(|editor, _| editor.hover_state.visible()).await;
17843    let counter = Arc::new(AtomicUsize::new(0));
17844    let mut completion_requests =
17845        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17846            let counter = counter.clone();
17847            async move {
17848                counter.fetch_add(1, atomic::Ordering::Release);
17849                Ok(Some(lsp::CompletionResponse::Array(vec![
17850                    lsp::CompletionItem {
17851                        label: "main".into(),
17852                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17853                        detail: Some("() -> ()".to_string()),
17854                        ..Default::default()
17855                    },
17856                    lsp::CompletionItem {
17857                        label: "TestStruct".into(),
17858                        kind: Some(lsp::CompletionItemKind::STRUCT),
17859                        detail: Some("struct TestStruct".to_string()),
17860                        ..Default::default()
17861                    },
17862                ])))
17863            }
17864        });
17865    cx.update_editor(|editor, window, cx| {
17866        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17867    });
17868    completion_requests.next().await;
17869    cx.condition(|editor, _| editor.context_menu_visible())
17870        .await;
17871    cx.update_editor(|editor, _, _| {
17872        assert!(
17873            !editor.hover_state.visible(),
17874            "Hover popover should be hidden when completion menu is shown"
17875        );
17876    });
17877}
17878
17879#[gpui::test]
17880async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17881    init_test(cx, |_| {});
17882
17883    let mut cx = EditorLspTestContext::new_rust(
17884        lsp::ServerCapabilities {
17885            completion_provider: Some(lsp::CompletionOptions {
17886                trigger_characters: Some(vec![".".to_string()]),
17887                resolve_provider: Some(true),
17888                ..Default::default()
17889            }),
17890            ..Default::default()
17891        },
17892        cx,
17893    )
17894    .await;
17895
17896    cx.set_state("fn main() { let a = 2ˇ; }");
17897    cx.simulate_keystroke(".");
17898
17899    let unresolved_item_1 = lsp::CompletionItem {
17900        label: "id".to_string(),
17901        filter_text: Some("id".to_string()),
17902        detail: None,
17903        documentation: None,
17904        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17905            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17906            new_text: ".id".to_string(),
17907        })),
17908        ..lsp::CompletionItem::default()
17909    };
17910    let resolved_item_1 = lsp::CompletionItem {
17911        additional_text_edits: Some(vec![lsp::TextEdit {
17912            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17913            new_text: "!!".to_string(),
17914        }]),
17915        ..unresolved_item_1.clone()
17916    };
17917    let unresolved_item_2 = lsp::CompletionItem {
17918        label: "other".to_string(),
17919        filter_text: Some("other".to_string()),
17920        detail: None,
17921        documentation: None,
17922        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17923            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17924            new_text: ".other".to_string(),
17925        })),
17926        ..lsp::CompletionItem::default()
17927    };
17928    let resolved_item_2 = lsp::CompletionItem {
17929        additional_text_edits: Some(vec![lsp::TextEdit {
17930            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17931            new_text: "??".to_string(),
17932        }]),
17933        ..unresolved_item_2.clone()
17934    };
17935
17936    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17937    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17938    cx.lsp
17939        .server
17940        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17941            let unresolved_item_1 = unresolved_item_1.clone();
17942            let resolved_item_1 = resolved_item_1.clone();
17943            let unresolved_item_2 = unresolved_item_2.clone();
17944            let resolved_item_2 = resolved_item_2.clone();
17945            let resolve_requests_1 = resolve_requests_1.clone();
17946            let resolve_requests_2 = resolve_requests_2.clone();
17947            move |unresolved_request, _| {
17948                let unresolved_item_1 = unresolved_item_1.clone();
17949                let resolved_item_1 = resolved_item_1.clone();
17950                let unresolved_item_2 = unresolved_item_2.clone();
17951                let resolved_item_2 = resolved_item_2.clone();
17952                let resolve_requests_1 = resolve_requests_1.clone();
17953                let resolve_requests_2 = resolve_requests_2.clone();
17954                async move {
17955                    if unresolved_request == unresolved_item_1 {
17956                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17957                        Ok(resolved_item_1.clone())
17958                    } else if unresolved_request == unresolved_item_2 {
17959                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17960                        Ok(resolved_item_2.clone())
17961                    } else {
17962                        panic!("Unexpected completion item {unresolved_request:?}")
17963                    }
17964                }
17965            }
17966        })
17967        .detach();
17968
17969    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17970        let unresolved_item_1 = unresolved_item_1.clone();
17971        let unresolved_item_2 = unresolved_item_2.clone();
17972        async move {
17973            Ok(Some(lsp::CompletionResponse::Array(vec![
17974                unresolved_item_1,
17975                unresolved_item_2,
17976            ])))
17977        }
17978    })
17979    .next()
17980    .await;
17981
17982    cx.condition(|editor, _| editor.context_menu_visible())
17983        .await;
17984    cx.update_editor(|editor, _, _| {
17985        let context_menu = editor.context_menu.borrow_mut();
17986        let context_menu = context_menu
17987            .as_ref()
17988            .expect("Should have the context menu deployed");
17989        match context_menu {
17990            CodeContextMenu::Completions(completions_menu) => {
17991                let completions = completions_menu.completions.borrow_mut();
17992                assert_eq!(
17993                    completions
17994                        .iter()
17995                        .map(|completion| &completion.label.text)
17996                        .collect::<Vec<_>>(),
17997                    vec!["id", "other"]
17998                )
17999            }
18000            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18001        }
18002    });
18003    cx.run_until_parked();
18004
18005    cx.update_editor(|editor, window, cx| {
18006        editor.context_menu_next(&ContextMenuNext, window, cx);
18007    });
18008    cx.run_until_parked();
18009    cx.update_editor(|editor, window, cx| {
18010        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18011    });
18012    cx.run_until_parked();
18013    cx.update_editor(|editor, window, cx| {
18014        editor.context_menu_next(&ContextMenuNext, window, cx);
18015    });
18016    cx.run_until_parked();
18017    cx.update_editor(|editor, window, cx| {
18018        editor
18019            .compose_completion(&ComposeCompletion::default(), window, cx)
18020            .expect("No task returned")
18021    })
18022    .await
18023    .expect("Completion failed");
18024    cx.run_until_parked();
18025
18026    cx.update_editor(|editor, _, cx| {
18027        assert_eq!(
18028            resolve_requests_1.load(atomic::Ordering::Acquire),
18029            1,
18030            "Should always resolve once despite multiple selections"
18031        );
18032        assert_eq!(
18033            resolve_requests_2.load(atomic::Ordering::Acquire),
18034            1,
18035            "Should always resolve once after multiple selections and applying the completion"
18036        );
18037        assert_eq!(
18038            editor.text(cx),
18039            "fn main() { let a = ??.other; }",
18040            "Should use resolved data when applying the completion"
18041        );
18042    });
18043}
18044
18045#[gpui::test]
18046async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18047    init_test(cx, |_| {});
18048
18049    let item_0 = lsp::CompletionItem {
18050        label: "abs".into(),
18051        insert_text: Some("abs".into()),
18052        data: Some(json!({ "very": "special"})),
18053        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18054        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18055            lsp::InsertReplaceEdit {
18056                new_text: "abs".to_string(),
18057                insert: lsp::Range::default(),
18058                replace: lsp::Range::default(),
18059            },
18060        )),
18061        ..lsp::CompletionItem::default()
18062    };
18063    let items = iter::once(item_0.clone())
18064        .chain((11..51).map(|i| lsp::CompletionItem {
18065            label: format!("item_{}", i),
18066            insert_text: Some(format!("item_{}", i)),
18067            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18068            ..lsp::CompletionItem::default()
18069        }))
18070        .collect::<Vec<_>>();
18071
18072    let default_commit_characters = vec!["?".to_string()];
18073    let default_data = json!({ "default": "data"});
18074    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18075    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18076    let default_edit_range = lsp::Range {
18077        start: lsp::Position {
18078            line: 0,
18079            character: 5,
18080        },
18081        end: lsp::Position {
18082            line: 0,
18083            character: 5,
18084        },
18085    };
18086
18087    let mut cx = EditorLspTestContext::new_rust(
18088        lsp::ServerCapabilities {
18089            completion_provider: Some(lsp::CompletionOptions {
18090                trigger_characters: Some(vec![".".to_string()]),
18091                resolve_provider: Some(true),
18092                ..Default::default()
18093            }),
18094            ..Default::default()
18095        },
18096        cx,
18097    )
18098    .await;
18099
18100    cx.set_state("fn main() { let a = 2ˇ; }");
18101    cx.simulate_keystroke(".");
18102
18103    let completion_data = default_data.clone();
18104    let completion_characters = default_commit_characters.clone();
18105    let completion_items = items.clone();
18106    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18107        let default_data = completion_data.clone();
18108        let default_commit_characters = completion_characters.clone();
18109        let items = completion_items.clone();
18110        async move {
18111            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18112                items,
18113                item_defaults: Some(lsp::CompletionListItemDefaults {
18114                    data: Some(default_data.clone()),
18115                    commit_characters: Some(default_commit_characters.clone()),
18116                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18117                        default_edit_range,
18118                    )),
18119                    insert_text_format: Some(default_insert_text_format),
18120                    insert_text_mode: Some(default_insert_text_mode),
18121                }),
18122                ..lsp::CompletionList::default()
18123            })))
18124        }
18125    })
18126    .next()
18127    .await;
18128
18129    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18130    cx.lsp
18131        .server
18132        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18133            let closure_resolved_items = resolved_items.clone();
18134            move |item_to_resolve, _| {
18135                let closure_resolved_items = closure_resolved_items.clone();
18136                async move {
18137                    closure_resolved_items.lock().push(item_to_resolve.clone());
18138                    Ok(item_to_resolve)
18139                }
18140            }
18141        })
18142        .detach();
18143
18144    cx.condition(|editor, _| editor.context_menu_visible())
18145        .await;
18146    cx.run_until_parked();
18147    cx.update_editor(|editor, _, _| {
18148        let menu = editor.context_menu.borrow_mut();
18149        match menu.as_ref().expect("should have the completions menu") {
18150            CodeContextMenu::Completions(completions_menu) => {
18151                assert_eq!(
18152                    completions_menu
18153                        .entries
18154                        .borrow()
18155                        .iter()
18156                        .map(|mat| mat.string.clone())
18157                        .collect::<Vec<String>>(),
18158                    items
18159                        .iter()
18160                        .map(|completion| completion.label.clone())
18161                        .collect::<Vec<String>>()
18162                );
18163            }
18164            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18165        }
18166    });
18167    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18168    // with 4 from the end.
18169    assert_eq!(
18170        *resolved_items.lock(),
18171        [&items[0..16], &items[items.len() - 4..items.len()]]
18172            .concat()
18173            .iter()
18174            .cloned()
18175            .map(|mut item| {
18176                if item.data.is_none() {
18177                    item.data = Some(default_data.clone());
18178                }
18179                item
18180            })
18181            .collect::<Vec<lsp::CompletionItem>>(),
18182        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18183    );
18184    resolved_items.lock().clear();
18185
18186    cx.update_editor(|editor, window, cx| {
18187        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18188    });
18189    cx.run_until_parked();
18190    // Completions that have already been resolved are skipped.
18191    assert_eq!(
18192        *resolved_items.lock(),
18193        items[items.len() - 17..items.len() - 4]
18194            .iter()
18195            .cloned()
18196            .map(|mut item| {
18197                if item.data.is_none() {
18198                    item.data = Some(default_data.clone());
18199                }
18200                item
18201            })
18202            .collect::<Vec<lsp::CompletionItem>>()
18203    );
18204    resolved_items.lock().clear();
18205}
18206
18207#[gpui::test]
18208async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18209    init_test(cx, |_| {});
18210
18211    let mut cx = EditorLspTestContext::new(
18212        Language::new(
18213            LanguageConfig {
18214                matcher: LanguageMatcher {
18215                    path_suffixes: vec!["jsx".into()],
18216                    ..Default::default()
18217                },
18218                overrides: [(
18219                    "element".into(),
18220                    LanguageConfigOverride {
18221                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18222                        ..Default::default()
18223                    },
18224                )]
18225                .into_iter()
18226                .collect(),
18227                ..Default::default()
18228            },
18229            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18230        )
18231        .with_override_query("(jsx_self_closing_element) @element")
18232        .unwrap(),
18233        lsp::ServerCapabilities {
18234            completion_provider: Some(lsp::CompletionOptions {
18235                trigger_characters: Some(vec![":".to_string()]),
18236                ..Default::default()
18237            }),
18238            ..Default::default()
18239        },
18240        cx,
18241    )
18242    .await;
18243
18244    cx.lsp
18245        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18246            Ok(Some(lsp::CompletionResponse::Array(vec![
18247                lsp::CompletionItem {
18248                    label: "bg-blue".into(),
18249                    ..Default::default()
18250                },
18251                lsp::CompletionItem {
18252                    label: "bg-red".into(),
18253                    ..Default::default()
18254                },
18255                lsp::CompletionItem {
18256                    label: "bg-yellow".into(),
18257                    ..Default::default()
18258                },
18259            ])))
18260        });
18261
18262    cx.set_state(r#"<p class="bgˇ" />"#);
18263
18264    // Trigger completion when typing a dash, because the dash is an extra
18265    // word character in the 'element' scope, which contains the cursor.
18266    cx.simulate_keystroke("-");
18267    cx.executor().run_until_parked();
18268    cx.update_editor(|editor, _, _| {
18269        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18270        {
18271            assert_eq!(
18272                completion_menu_entries(menu),
18273                &["bg-blue", "bg-red", "bg-yellow"]
18274            );
18275        } else {
18276            panic!("expected completion menu to be open");
18277        }
18278    });
18279
18280    cx.simulate_keystroke("l");
18281    cx.executor().run_until_parked();
18282    cx.update_editor(|editor, _, _| {
18283        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18284        {
18285            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18286        } else {
18287            panic!("expected completion menu to be open");
18288        }
18289    });
18290
18291    // When filtering completions, consider the character after the '-' to
18292    // be the start of a subword.
18293    cx.set_state(r#"<p class="yelˇ" />"#);
18294    cx.simulate_keystroke("l");
18295    cx.executor().run_until_parked();
18296    cx.update_editor(|editor, _, _| {
18297        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18298        {
18299            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18300        } else {
18301            panic!("expected completion menu to be open");
18302        }
18303    });
18304}
18305
18306fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18307    let entries = menu.entries.borrow();
18308    entries.iter().map(|mat| mat.string.clone()).collect()
18309}
18310
18311#[gpui::test]
18312async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18313    init_test(cx, |settings| {
18314        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18315    });
18316
18317    let fs = FakeFs::new(cx.executor());
18318    fs.insert_file(path!("/file.ts"), Default::default()).await;
18319
18320    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18321    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18322
18323    language_registry.add(Arc::new(Language::new(
18324        LanguageConfig {
18325            name: "TypeScript".into(),
18326            matcher: LanguageMatcher {
18327                path_suffixes: vec!["ts".to_string()],
18328                ..Default::default()
18329            },
18330            ..Default::default()
18331        },
18332        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18333    )));
18334    update_test_language_settings(cx, |settings| {
18335        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18336    });
18337
18338    let test_plugin = "test_plugin";
18339    let _ = language_registry.register_fake_lsp(
18340        "TypeScript",
18341        FakeLspAdapter {
18342            prettier_plugins: vec![test_plugin],
18343            ..Default::default()
18344        },
18345    );
18346
18347    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18348    let buffer = project
18349        .update(cx, |project, cx| {
18350            project.open_local_buffer(path!("/file.ts"), cx)
18351        })
18352        .await
18353        .unwrap();
18354
18355    let buffer_text = "one\ntwo\nthree\n";
18356    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18357    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18358    editor.update_in(cx, |editor, window, cx| {
18359        editor.set_text(buffer_text, window, cx)
18360    });
18361
18362    editor
18363        .update_in(cx, |editor, window, cx| {
18364            editor.perform_format(
18365                project.clone(),
18366                FormatTrigger::Manual,
18367                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18368                window,
18369                cx,
18370            )
18371        })
18372        .unwrap()
18373        .await;
18374    assert_eq!(
18375        editor.update(cx, |editor, cx| editor.text(cx)),
18376        buffer_text.to_string() + prettier_format_suffix,
18377        "Test prettier formatting was not applied to the original buffer text",
18378    );
18379
18380    update_test_language_settings(cx, |settings| {
18381        settings.defaults.formatter = Some(FormatterList::default())
18382    });
18383    let format = editor.update_in(cx, |editor, window, cx| {
18384        editor.perform_format(
18385            project.clone(),
18386            FormatTrigger::Manual,
18387            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18388            window,
18389            cx,
18390        )
18391    });
18392    format.await.unwrap();
18393    assert_eq!(
18394        editor.update(cx, |editor, cx| editor.text(cx)),
18395        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18396        "Autoformatting (via test prettier) was not applied to the original buffer text",
18397    );
18398}
18399
18400#[gpui::test]
18401async fn test_addition_reverts(cx: &mut TestAppContext) {
18402    init_test(cx, |_| {});
18403    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18404    let base_text = indoc! {r#"
18405        struct Row;
18406        struct Row1;
18407        struct Row2;
18408
18409        struct Row4;
18410        struct Row5;
18411        struct Row6;
18412
18413        struct Row8;
18414        struct Row9;
18415        struct Row10;"#};
18416
18417    // When addition hunks are not adjacent to carets, no hunk revert is performed
18418    assert_hunk_revert(
18419        indoc! {r#"struct Row;
18420                   struct Row1;
18421                   struct Row1.1;
18422                   struct Row1.2;
18423                   struct Row2;ˇ
18424
18425                   struct Row4;
18426                   struct Row5;
18427                   struct Row6;
18428
18429                   struct Row8;
18430                   ˇstruct Row9;
18431                   struct Row9.1;
18432                   struct Row9.2;
18433                   struct Row9.3;
18434                   struct Row10;"#},
18435        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18436        indoc! {r#"struct Row;
18437                   struct Row1;
18438                   struct Row1.1;
18439                   struct Row1.2;
18440                   struct Row2;ˇ
18441
18442                   struct Row4;
18443                   struct Row5;
18444                   struct Row6;
18445
18446                   struct Row8;
18447                   ˇstruct Row9;
18448                   struct Row9.1;
18449                   struct Row9.2;
18450                   struct Row9.3;
18451                   struct Row10;"#},
18452        base_text,
18453        &mut cx,
18454    );
18455    // Same for selections
18456    assert_hunk_revert(
18457        indoc! {r#"struct Row;
18458                   struct Row1;
18459                   struct Row2;
18460                   struct Row2.1;
18461                   struct Row2.2;
18462                   «ˇ
18463                   struct Row4;
18464                   struct» Row5;
18465                   «struct Row6;
18466                   ˇ»
18467                   struct Row9.1;
18468                   struct Row9.2;
18469                   struct Row9.3;
18470                   struct Row8;
18471                   struct Row9;
18472                   struct Row10;"#},
18473        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18474        indoc! {r#"struct Row;
18475                   struct Row1;
18476                   struct Row2;
18477                   struct Row2.1;
18478                   struct Row2.2;
18479                   «ˇ
18480                   struct Row4;
18481                   struct» Row5;
18482                   «struct Row6;
18483                   ˇ»
18484                   struct Row9.1;
18485                   struct Row9.2;
18486                   struct Row9.3;
18487                   struct Row8;
18488                   struct Row9;
18489                   struct Row10;"#},
18490        base_text,
18491        &mut cx,
18492    );
18493
18494    // When carets and selections intersect the addition hunks, those are reverted.
18495    // Adjacent carets got merged.
18496    assert_hunk_revert(
18497        indoc! {r#"struct Row;
18498                   ˇ// something on the top
18499                   struct Row1;
18500                   struct Row2;
18501                   struct Roˇw3.1;
18502                   struct Row2.2;
18503                   struct Row2.3;ˇ
18504
18505                   struct Row4;
18506                   struct ˇRow5.1;
18507                   struct Row5.2;
18508                   struct «Rowˇ»5.3;
18509                   struct Row5;
18510                   struct Row6;
18511                   ˇ
18512                   struct Row9.1;
18513                   struct «Rowˇ»9.2;
18514                   struct «ˇRow»9.3;
18515                   struct Row8;
18516                   struct Row9;
18517                   «ˇ// something on bottom»
18518                   struct Row10;"#},
18519        vec![
18520            DiffHunkStatusKind::Added,
18521            DiffHunkStatusKind::Added,
18522            DiffHunkStatusKind::Added,
18523            DiffHunkStatusKind::Added,
18524            DiffHunkStatusKind::Added,
18525        ],
18526        indoc! {r#"struct Row;
18527                   ˇstruct Row1;
18528                   struct Row2;
18529                   ˇ
18530                   struct Row4;
18531                   ˇstruct Row5;
18532                   struct Row6;
18533                   ˇ
18534                   ˇstruct Row8;
18535                   struct Row9;
18536                   ˇstruct Row10;"#},
18537        base_text,
18538        &mut cx,
18539    );
18540}
18541
18542#[gpui::test]
18543async fn test_modification_reverts(cx: &mut TestAppContext) {
18544    init_test(cx, |_| {});
18545    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18546    let base_text = indoc! {r#"
18547        struct Row;
18548        struct Row1;
18549        struct Row2;
18550
18551        struct Row4;
18552        struct Row5;
18553        struct Row6;
18554
18555        struct Row8;
18556        struct Row9;
18557        struct Row10;"#};
18558
18559    // Modification hunks behave the same as the addition ones.
18560    assert_hunk_revert(
18561        indoc! {r#"struct Row;
18562                   struct Row1;
18563                   struct Row33;
18564                   ˇ
18565                   struct Row4;
18566                   struct Row5;
18567                   struct Row6;
18568                   ˇ
18569                   struct Row99;
18570                   struct Row9;
18571                   struct Row10;"#},
18572        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18573        indoc! {r#"struct Row;
18574                   struct Row1;
18575                   struct Row33;
18576                   ˇ
18577                   struct Row4;
18578                   struct Row5;
18579                   struct Row6;
18580                   ˇ
18581                   struct Row99;
18582                   struct Row9;
18583                   struct Row10;"#},
18584        base_text,
18585        &mut cx,
18586    );
18587    assert_hunk_revert(
18588        indoc! {r#"struct Row;
18589                   struct Row1;
18590                   struct Row33;
18591                   «ˇ
18592                   struct Row4;
18593                   struct» Row5;
18594                   «struct Row6;
18595                   ˇ»
18596                   struct Row99;
18597                   struct Row9;
18598                   struct Row10;"#},
18599        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18600        indoc! {r#"struct Row;
18601                   struct Row1;
18602                   struct Row33;
18603                   «ˇ
18604                   struct Row4;
18605                   struct» Row5;
18606                   «struct Row6;
18607                   ˇ»
18608                   struct Row99;
18609                   struct Row9;
18610                   struct Row10;"#},
18611        base_text,
18612        &mut cx,
18613    );
18614
18615    assert_hunk_revert(
18616        indoc! {r#"ˇstruct Row1.1;
18617                   struct Row1;
18618                   «ˇstr»uct Row22;
18619
18620                   struct ˇRow44;
18621                   struct Row5;
18622                   struct «Rˇ»ow66;ˇ
18623
18624                   «struˇ»ct Row88;
18625                   struct Row9;
18626                   struct Row1011;ˇ"#},
18627        vec![
18628            DiffHunkStatusKind::Modified,
18629            DiffHunkStatusKind::Modified,
18630            DiffHunkStatusKind::Modified,
18631            DiffHunkStatusKind::Modified,
18632            DiffHunkStatusKind::Modified,
18633            DiffHunkStatusKind::Modified,
18634        ],
18635        indoc! {r#"struct Row;
18636                   ˇstruct Row1;
18637                   struct Row2;
18638                   ˇ
18639                   struct Row4;
18640                   ˇstruct Row5;
18641                   struct Row6;
18642                   ˇ
18643                   struct Row8;
18644                   ˇstruct Row9;
18645                   struct Row10;ˇ"#},
18646        base_text,
18647        &mut cx,
18648    );
18649}
18650
18651#[gpui::test]
18652async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18653    init_test(cx, |_| {});
18654    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18655    let base_text = indoc! {r#"
18656        one
18657
18658        two
18659        three
18660        "#};
18661
18662    cx.set_head_text(base_text);
18663    cx.set_state("\nˇ\n");
18664    cx.executor().run_until_parked();
18665    cx.update_editor(|editor, _window, cx| {
18666        editor.expand_selected_diff_hunks(cx);
18667    });
18668    cx.executor().run_until_parked();
18669    cx.update_editor(|editor, window, cx| {
18670        editor.backspace(&Default::default(), window, cx);
18671    });
18672    cx.run_until_parked();
18673    cx.assert_state_with_diff(
18674        indoc! {r#"
18675
18676        - two
18677        - threeˇ
18678        +
18679        "#}
18680        .to_string(),
18681    );
18682}
18683
18684#[gpui::test]
18685async fn test_deletion_reverts(cx: &mut TestAppContext) {
18686    init_test(cx, |_| {});
18687    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18688    let base_text = indoc! {r#"struct Row;
18689struct Row1;
18690struct Row2;
18691
18692struct Row4;
18693struct Row5;
18694struct Row6;
18695
18696struct Row8;
18697struct Row9;
18698struct Row10;"#};
18699
18700    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18701    assert_hunk_revert(
18702        indoc! {r#"struct Row;
18703                   struct Row2;
18704
18705                   ˇstruct Row4;
18706                   struct Row5;
18707                   struct Row6;
18708                   ˇ
18709                   struct Row8;
18710                   struct Row10;"#},
18711        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18712        indoc! {r#"struct Row;
18713                   struct Row2;
18714
18715                   ˇstruct Row4;
18716                   struct Row5;
18717                   struct Row6;
18718                   ˇ
18719                   struct Row8;
18720                   struct Row10;"#},
18721        base_text,
18722        &mut cx,
18723    );
18724    assert_hunk_revert(
18725        indoc! {r#"struct Row;
18726                   struct Row2;
18727
18728                   «ˇstruct Row4;
18729                   struct» Row5;
18730                   «struct Row6;
18731                   ˇ»
18732                   struct Row8;
18733                   struct Row10;"#},
18734        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18735        indoc! {r#"struct Row;
18736                   struct Row2;
18737
18738                   «ˇstruct Row4;
18739                   struct» Row5;
18740                   «struct Row6;
18741                   ˇ»
18742                   struct Row8;
18743                   struct Row10;"#},
18744        base_text,
18745        &mut cx,
18746    );
18747
18748    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18749    assert_hunk_revert(
18750        indoc! {r#"struct Row;
18751                   ˇstruct Row2;
18752
18753                   struct Row4;
18754                   struct Row5;
18755                   struct Row6;
18756
18757                   struct Row8;ˇ
18758                   struct Row10;"#},
18759        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18760        indoc! {r#"struct Row;
18761                   struct Row1;
18762                   ˇstruct Row2;
18763
18764                   struct Row4;
18765                   struct Row5;
18766                   struct Row6;
18767
18768                   struct Row8;ˇ
18769                   struct Row9;
18770                   struct Row10;"#},
18771        base_text,
18772        &mut cx,
18773    );
18774    assert_hunk_revert(
18775        indoc! {r#"struct Row;
18776                   struct Row2«ˇ;
18777                   struct Row4;
18778                   struct» Row5;
18779                   «struct Row6;
18780
18781                   struct Row8;ˇ»
18782                   struct Row10;"#},
18783        vec![
18784            DiffHunkStatusKind::Deleted,
18785            DiffHunkStatusKind::Deleted,
18786            DiffHunkStatusKind::Deleted,
18787        ],
18788        indoc! {r#"struct Row;
18789                   struct Row1;
18790                   struct Row2«ˇ;
18791
18792                   struct Row4;
18793                   struct» Row5;
18794                   «struct Row6;
18795
18796                   struct Row8;ˇ»
18797                   struct Row9;
18798                   struct Row10;"#},
18799        base_text,
18800        &mut cx,
18801    );
18802}
18803
18804#[gpui::test]
18805async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18806    init_test(cx, |_| {});
18807
18808    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18809    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18810    let base_text_3 =
18811        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18812
18813    let text_1 = edit_first_char_of_every_line(base_text_1);
18814    let text_2 = edit_first_char_of_every_line(base_text_2);
18815    let text_3 = edit_first_char_of_every_line(base_text_3);
18816
18817    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18818    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18819    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18820
18821    let multibuffer = cx.new(|cx| {
18822        let mut multibuffer = MultiBuffer::new(ReadWrite);
18823        multibuffer.push_excerpts(
18824            buffer_1.clone(),
18825            [
18826                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18827                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18828                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18829            ],
18830            cx,
18831        );
18832        multibuffer.push_excerpts(
18833            buffer_2.clone(),
18834            [
18835                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18836                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18837                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18838            ],
18839            cx,
18840        );
18841        multibuffer.push_excerpts(
18842            buffer_3.clone(),
18843            [
18844                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18845                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18846                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18847            ],
18848            cx,
18849        );
18850        multibuffer
18851    });
18852
18853    let fs = FakeFs::new(cx.executor());
18854    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18855    let (editor, cx) = cx
18856        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18857    editor.update_in(cx, |editor, _window, cx| {
18858        for (buffer, diff_base) in [
18859            (buffer_1.clone(), base_text_1),
18860            (buffer_2.clone(), base_text_2),
18861            (buffer_3.clone(), base_text_3),
18862        ] {
18863            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18864            editor
18865                .buffer
18866                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18867        }
18868    });
18869    cx.executor().run_until_parked();
18870
18871    editor.update_in(cx, |editor, window, cx| {
18872        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}");
18873        editor.select_all(&SelectAll, window, cx);
18874        editor.git_restore(&Default::default(), window, cx);
18875    });
18876    cx.executor().run_until_parked();
18877
18878    // When all ranges are selected, all buffer hunks are reverted.
18879    editor.update(cx, |editor, cx| {
18880        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");
18881    });
18882    buffer_1.update(cx, |buffer, _| {
18883        assert_eq!(buffer.text(), base_text_1);
18884    });
18885    buffer_2.update(cx, |buffer, _| {
18886        assert_eq!(buffer.text(), base_text_2);
18887    });
18888    buffer_3.update(cx, |buffer, _| {
18889        assert_eq!(buffer.text(), base_text_3);
18890    });
18891
18892    editor.update_in(cx, |editor, window, cx| {
18893        editor.undo(&Default::default(), window, cx);
18894    });
18895
18896    editor.update_in(cx, |editor, window, cx| {
18897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18898            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18899        });
18900        editor.git_restore(&Default::default(), window, cx);
18901    });
18902
18903    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18904    // but not affect buffer_2 and its related excerpts.
18905    editor.update(cx, |editor, cx| {
18906        assert_eq!(
18907            editor.text(cx),
18908            "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}"
18909        );
18910    });
18911    buffer_1.update(cx, |buffer, _| {
18912        assert_eq!(buffer.text(), base_text_1);
18913    });
18914    buffer_2.update(cx, |buffer, _| {
18915        assert_eq!(
18916            buffer.text(),
18917            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18918        );
18919    });
18920    buffer_3.update(cx, |buffer, _| {
18921        assert_eq!(
18922            buffer.text(),
18923            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18924        );
18925    });
18926
18927    fn edit_first_char_of_every_line(text: &str) -> String {
18928        text.split('\n')
18929            .map(|line| format!("X{}", &line[1..]))
18930            .collect::<Vec<_>>()
18931            .join("\n")
18932    }
18933}
18934
18935#[gpui::test]
18936async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18937    init_test(cx, |_| {});
18938
18939    let cols = 4;
18940    let rows = 10;
18941    let sample_text_1 = sample_text(rows, cols, 'a');
18942    assert_eq!(
18943        sample_text_1,
18944        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18945    );
18946    let sample_text_2 = sample_text(rows, cols, 'l');
18947    assert_eq!(
18948        sample_text_2,
18949        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18950    );
18951    let sample_text_3 = sample_text(rows, cols, 'v');
18952    assert_eq!(
18953        sample_text_3,
18954        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18955    );
18956
18957    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18958    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18959    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18960
18961    let multi_buffer = cx.new(|cx| {
18962        let mut multibuffer = MultiBuffer::new(ReadWrite);
18963        multibuffer.push_excerpts(
18964            buffer_1.clone(),
18965            [
18966                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18967                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18968                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18969            ],
18970            cx,
18971        );
18972        multibuffer.push_excerpts(
18973            buffer_2.clone(),
18974            [
18975                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18976                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18977                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18978            ],
18979            cx,
18980        );
18981        multibuffer.push_excerpts(
18982            buffer_3.clone(),
18983            [
18984                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18985                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18986                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18987            ],
18988            cx,
18989        );
18990        multibuffer
18991    });
18992
18993    let fs = FakeFs::new(cx.executor());
18994    fs.insert_tree(
18995        "/a",
18996        json!({
18997            "main.rs": sample_text_1,
18998            "other.rs": sample_text_2,
18999            "lib.rs": sample_text_3,
19000        }),
19001    )
19002    .await;
19003    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19004    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19005    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19006    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19007        Editor::new(
19008            EditorMode::full(),
19009            multi_buffer,
19010            Some(project.clone()),
19011            window,
19012            cx,
19013        )
19014    });
19015    let multibuffer_item_id = workspace
19016        .update(cx, |workspace, window, cx| {
19017            assert!(
19018                workspace.active_item(cx).is_none(),
19019                "active item should be None before the first item is added"
19020            );
19021            workspace.add_item_to_active_pane(
19022                Box::new(multi_buffer_editor.clone()),
19023                None,
19024                true,
19025                window,
19026                cx,
19027            );
19028            let active_item = workspace
19029                .active_item(cx)
19030                .expect("should have an active item after adding the multi buffer");
19031            assert_eq!(
19032                active_item.buffer_kind(cx),
19033                ItemBufferKind::Multibuffer,
19034                "A multi buffer was expected to active after adding"
19035            );
19036            active_item.item_id()
19037        })
19038        .unwrap();
19039    cx.executor().run_until_parked();
19040
19041    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19042        editor.change_selections(
19043            SelectionEffects::scroll(Autoscroll::Next),
19044            window,
19045            cx,
19046            |s| s.select_ranges(Some(1..2)),
19047        );
19048        editor.open_excerpts(&OpenExcerpts, window, cx);
19049    });
19050    cx.executor().run_until_parked();
19051    let first_item_id = workspace
19052        .update(cx, |workspace, window, cx| {
19053            let active_item = workspace
19054                .active_item(cx)
19055                .expect("should have an active item after navigating into the 1st buffer");
19056            let first_item_id = active_item.item_id();
19057            assert_ne!(
19058                first_item_id, multibuffer_item_id,
19059                "Should navigate into the 1st buffer and activate it"
19060            );
19061            assert_eq!(
19062                active_item.buffer_kind(cx),
19063                ItemBufferKind::Singleton,
19064                "New active item should be a singleton buffer"
19065            );
19066            assert_eq!(
19067                active_item
19068                    .act_as::<Editor>(cx)
19069                    .expect("should have navigated into an editor for the 1st buffer")
19070                    .read(cx)
19071                    .text(cx),
19072                sample_text_1
19073            );
19074
19075            workspace
19076                .go_back(workspace.active_pane().downgrade(), window, cx)
19077                .detach_and_log_err(cx);
19078
19079            first_item_id
19080        })
19081        .unwrap();
19082    cx.executor().run_until_parked();
19083    workspace
19084        .update(cx, |workspace, _, cx| {
19085            let active_item = workspace
19086                .active_item(cx)
19087                .expect("should have an active item after navigating back");
19088            assert_eq!(
19089                active_item.item_id(),
19090                multibuffer_item_id,
19091                "Should navigate back to the multi buffer"
19092            );
19093            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19094        })
19095        .unwrap();
19096
19097    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19098        editor.change_selections(
19099            SelectionEffects::scroll(Autoscroll::Next),
19100            window,
19101            cx,
19102            |s| s.select_ranges(Some(39..40)),
19103        );
19104        editor.open_excerpts(&OpenExcerpts, window, cx);
19105    });
19106    cx.executor().run_until_parked();
19107    let second_item_id = workspace
19108        .update(cx, |workspace, window, cx| {
19109            let active_item = workspace
19110                .active_item(cx)
19111                .expect("should have an active item after navigating into the 2nd buffer");
19112            let second_item_id = active_item.item_id();
19113            assert_ne!(
19114                second_item_id, multibuffer_item_id,
19115                "Should navigate away from the multibuffer"
19116            );
19117            assert_ne!(
19118                second_item_id, first_item_id,
19119                "Should navigate into the 2nd buffer and activate it"
19120            );
19121            assert_eq!(
19122                active_item.buffer_kind(cx),
19123                ItemBufferKind::Singleton,
19124                "New active item should be a singleton buffer"
19125            );
19126            assert_eq!(
19127                active_item
19128                    .act_as::<Editor>(cx)
19129                    .expect("should have navigated into an editor")
19130                    .read(cx)
19131                    .text(cx),
19132                sample_text_2
19133            );
19134
19135            workspace
19136                .go_back(workspace.active_pane().downgrade(), window, cx)
19137                .detach_and_log_err(cx);
19138
19139            second_item_id
19140        })
19141        .unwrap();
19142    cx.executor().run_until_parked();
19143    workspace
19144        .update(cx, |workspace, _, cx| {
19145            let active_item = workspace
19146                .active_item(cx)
19147                .expect("should have an active item after navigating back from the 2nd buffer");
19148            assert_eq!(
19149                active_item.item_id(),
19150                multibuffer_item_id,
19151                "Should navigate back from the 2nd buffer to the multi buffer"
19152            );
19153            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19154        })
19155        .unwrap();
19156
19157    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19158        editor.change_selections(
19159            SelectionEffects::scroll(Autoscroll::Next),
19160            window,
19161            cx,
19162            |s| s.select_ranges(Some(70..70)),
19163        );
19164        editor.open_excerpts(&OpenExcerpts, window, cx);
19165    });
19166    cx.executor().run_until_parked();
19167    workspace
19168        .update(cx, |workspace, window, cx| {
19169            let active_item = workspace
19170                .active_item(cx)
19171                .expect("should have an active item after navigating into the 3rd buffer");
19172            let third_item_id = active_item.item_id();
19173            assert_ne!(
19174                third_item_id, multibuffer_item_id,
19175                "Should navigate into the 3rd buffer and activate it"
19176            );
19177            assert_ne!(third_item_id, first_item_id);
19178            assert_ne!(third_item_id, second_item_id);
19179            assert_eq!(
19180                active_item.buffer_kind(cx),
19181                ItemBufferKind::Singleton,
19182                "New active item should be a singleton buffer"
19183            );
19184            assert_eq!(
19185                active_item
19186                    .act_as::<Editor>(cx)
19187                    .expect("should have navigated into an editor")
19188                    .read(cx)
19189                    .text(cx),
19190                sample_text_3
19191            );
19192
19193            workspace
19194                .go_back(workspace.active_pane().downgrade(), window, cx)
19195                .detach_and_log_err(cx);
19196        })
19197        .unwrap();
19198    cx.executor().run_until_parked();
19199    workspace
19200        .update(cx, |workspace, _, cx| {
19201            let active_item = workspace
19202                .active_item(cx)
19203                .expect("should have an active item after navigating back from the 3rd buffer");
19204            assert_eq!(
19205                active_item.item_id(),
19206                multibuffer_item_id,
19207                "Should navigate back from the 3rd buffer to the multi buffer"
19208            );
19209            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19210        })
19211        .unwrap();
19212}
19213
19214#[gpui::test]
19215async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19216    init_test(cx, |_| {});
19217
19218    let mut cx = EditorTestContext::new(cx).await;
19219
19220    let diff_base = r#"
19221        use some::mod;
19222
19223        const A: u32 = 42;
19224
19225        fn main() {
19226            println!("hello");
19227
19228            println!("world");
19229        }
19230        "#
19231    .unindent();
19232
19233    cx.set_state(
19234        &r#"
19235        use some::modified;
19236
19237        ˇ
19238        fn main() {
19239            println!("hello there");
19240
19241            println!("around the");
19242            println!("world");
19243        }
19244        "#
19245        .unindent(),
19246    );
19247
19248    cx.set_head_text(&diff_base);
19249    executor.run_until_parked();
19250
19251    cx.update_editor(|editor, window, cx| {
19252        editor.go_to_next_hunk(&GoToHunk, window, cx);
19253        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19254    });
19255    executor.run_until_parked();
19256    cx.assert_state_with_diff(
19257        r#"
19258          use some::modified;
19259
19260
19261          fn main() {
19262        -     println!("hello");
19263        + ˇ    println!("hello there");
19264
19265              println!("around the");
19266              println!("world");
19267          }
19268        "#
19269        .unindent(),
19270    );
19271
19272    cx.update_editor(|editor, window, cx| {
19273        for _ in 0..2 {
19274            editor.go_to_next_hunk(&GoToHunk, window, cx);
19275            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19276        }
19277    });
19278    executor.run_until_parked();
19279    cx.assert_state_with_diff(
19280        r#"
19281        - use some::mod;
19282        + ˇuse some::modified;
19283
19284
19285          fn main() {
19286        -     println!("hello");
19287        +     println!("hello there");
19288
19289        +     println!("around the");
19290              println!("world");
19291          }
19292        "#
19293        .unindent(),
19294    );
19295
19296    cx.update_editor(|editor, window, cx| {
19297        editor.go_to_next_hunk(&GoToHunk, window, cx);
19298        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19299    });
19300    executor.run_until_parked();
19301    cx.assert_state_with_diff(
19302        r#"
19303        - use some::mod;
19304        + use some::modified;
19305
19306        - const A: u32 = 42;
19307          ˇ
19308          fn main() {
19309        -     println!("hello");
19310        +     println!("hello there");
19311
19312        +     println!("around the");
19313              println!("world");
19314          }
19315        "#
19316        .unindent(),
19317    );
19318
19319    cx.update_editor(|editor, window, cx| {
19320        editor.cancel(&Cancel, window, cx);
19321    });
19322
19323    cx.assert_state_with_diff(
19324        r#"
19325          use some::modified;
19326
19327          ˇ
19328          fn main() {
19329              println!("hello there");
19330
19331              println!("around the");
19332              println!("world");
19333          }
19334        "#
19335        .unindent(),
19336    );
19337}
19338
19339#[gpui::test]
19340async fn test_diff_base_change_with_expanded_diff_hunks(
19341    executor: BackgroundExecutor,
19342    cx: &mut TestAppContext,
19343) {
19344    init_test(cx, |_| {});
19345
19346    let mut cx = EditorTestContext::new(cx).await;
19347
19348    let diff_base = r#"
19349        use some::mod1;
19350        use some::mod2;
19351
19352        const A: u32 = 42;
19353        const B: u32 = 42;
19354        const C: u32 = 42;
19355
19356        fn main() {
19357            println!("hello");
19358
19359            println!("world");
19360        }
19361        "#
19362    .unindent();
19363
19364    cx.set_state(
19365        &r#"
19366        use some::mod2;
19367
19368        const A: u32 = 42;
19369        const C: u32 = 42;
19370
19371        fn main(ˇ) {
19372            //println!("hello");
19373
19374            println!("world");
19375            //
19376            //
19377        }
19378        "#
19379        .unindent(),
19380    );
19381
19382    cx.set_head_text(&diff_base);
19383    executor.run_until_parked();
19384
19385    cx.update_editor(|editor, window, cx| {
19386        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19387    });
19388    executor.run_until_parked();
19389    cx.assert_state_with_diff(
19390        r#"
19391        - use some::mod1;
19392          use some::mod2;
19393
19394          const A: u32 = 42;
19395        - const B: u32 = 42;
19396          const C: u32 = 42;
19397
19398          fn main(ˇ) {
19399        -     println!("hello");
19400        +     //println!("hello");
19401
19402              println!("world");
19403        +     //
19404        +     //
19405          }
19406        "#
19407        .unindent(),
19408    );
19409
19410    cx.set_head_text("new diff base!");
19411    executor.run_until_parked();
19412    cx.assert_state_with_diff(
19413        r#"
19414        - new diff base!
19415        + use some::mod2;
19416        +
19417        + const A: u32 = 42;
19418        + const C: u32 = 42;
19419        +
19420        + fn main(ˇ) {
19421        +     //println!("hello");
19422        +
19423        +     println!("world");
19424        +     //
19425        +     //
19426        + }
19427        "#
19428        .unindent(),
19429    );
19430}
19431
19432#[gpui::test]
19433async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19434    init_test(cx, |_| {});
19435
19436    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19437    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19438    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19439    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19440    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19441    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19442
19443    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19444    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19445    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19446
19447    let multi_buffer = cx.new(|cx| {
19448        let mut multibuffer = MultiBuffer::new(ReadWrite);
19449        multibuffer.push_excerpts(
19450            buffer_1.clone(),
19451            [
19452                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19453                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19454                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19455            ],
19456            cx,
19457        );
19458        multibuffer.push_excerpts(
19459            buffer_2.clone(),
19460            [
19461                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19462                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19463                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19464            ],
19465            cx,
19466        );
19467        multibuffer.push_excerpts(
19468            buffer_3.clone(),
19469            [
19470                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19471                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19472                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19473            ],
19474            cx,
19475        );
19476        multibuffer
19477    });
19478
19479    let editor =
19480        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19481    editor
19482        .update(cx, |editor, _window, cx| {
19483            for (buffer, diff_base) in [
19484                (buffer_1.clone(), file_1_old),
19485                (buffer_2.clone(), file_2_old),
19486                (buffer_3.clone(), file_3_old),
19487            ] {
19488                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19489                editor
19490                    .buffer
19491                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19492            }
19493        })
19494        .unwrap();
19495
19496    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19497    cx.run_until_parked();
19498
19499    cx.assert_editor_state(
19500        &"
19501            ˇaaa
19502            ccc
19503            ddd
19504
19505            ggg
19506            hhh
19507
19508
19509            lll
19510            mmm
19511            NNN
19512
19513            qqq
19514            rrr
19515
19516            uuu
19517            111
19518            222
19519            333
19520
19521            666
19522            777
19523
19524            000
19525            !!!"
19526        .unindent(),
19527    );
19528
19529    cx.update_editor(|editor, window, cx| {
19530        editor.select_all(&SelectAll, window, cx);
19531        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19532    });
19533    cx.executor().run_until_parked();
19534
19535    cx.assert_state_with_diff(
19536        "
19537            «aaa
19538          - bbb
19539            ccc
19540            ddd
19541
19542            ggg
19543            hhh
19544
19545
19546            lll
19547            mmm
19548          - nnn
19549          + NNN
19550
19551            qqq
19552            rrr
19553
19554            uuu
19555            111
19556            222
19557            333
19558
19559          + 666
19560            777
19561
19562            000
19563            !!!ˇ»"
19564            .unindent(),
19565    );
19566}
19567
19568#[gpui::test]
19569async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19570    init_test(cx, |_| {});
19571
19572    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19573    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19574
19575    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19576    let multi_buffer = cx.new(|cx| {
19577        let mut multibuffer = MultiBuffer::new(ReadWrite);
19578        multibuffer.push_excerpts(
19579            buffer.clone(),
19580            [
19581                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19582                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19583                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19584            ],
19585            cx,
19586        );
19587        multibuffer
19588    });
19589
19590    let editor =
19591        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19592    editor
19593        .update(cx, |editor, _window, cx| {
19594            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19595            editor
19596                .buffer
19597                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19598        })
19599        .unwrap();
19600
19601    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19602    cx.run_until_parked();
19603
19604    cx.update_editor(|editor, window, cx| {
19605        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19606    });
19607    cx.executor().run_until_parked();
19608
19609    // When the start of a hunk coincides with the start of its excerpt,
19610    // the hunk is expanded. When the start of a hunk is earlier than
19611    // the start of its excerpt, the hunk is not expanded.
19612    cx.assert_state_with_diff(
19613        "
19614            ˇaaa
19615          - bbb
19616          + BBB
19617
19618          - ddd
19619          - eee
19620          + DDD
19621          + EEE
19622            fff
19623
19624            iii
19625        "
19626        .unindent(),
19627    );
19628}
19629
19630#[gpui::test]
19631async fn test_edits_around_expanded_insertion_hunks(
19632    executor: BackgroundExecutor,
19633    cx: &mut TestAppContext,
19634) {
19635    init_test(cx, |_| {});
19636
19637    let mut cx = EditorTestContext::new(cx).await;
19638
19639    let diff_base = r#"
19640        use some::mod1;
19641        use some::mod2;
19642
19643        const A: u32 = 42;
19644
19645        fn main() {
19646            println!("hello");
19647
19648            println!("world");
19649        }
19650        "#
19651    .unindent();
19652    executor.run_until_parked();
19653    cx.set_state(
19654        &r#"
19655        use some::mod1;
19656        use some::mod2;
19657
19658        const A: u32 = 42;
19659        const B: u32 = 42;
19660        const C: u32 = 42;
19661        ˇ
19662
19663        fn main() {
19664            println!("hello");
19665
19666            println!("world");
19667        }
19668        "#
19669        .unindent(),
19670    );
19671
19672    cx.set_head_text(&diff_base);
19673    executor.run_until_parked();
19674
19675    cx.update_editor(|editor, window, cx| {
19676        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19677    });
19678    executor.run_until_parked();
19679
19680    cx.assert_state_with_diff(
19681        r#"
19682        use some::mod1;
19683        use some::mod2;
19684
19685        const A: u32 = 42;
19686      + const B: u32 = 42;
19687      + const C: u32 = 42;
19688      + ˇ
19689
19690        fn main() {
19691            println!("hello");
19692
19693            println!("world");
19694        }
19695      "#
19696        .unindent(),
19697    );
19698
19699    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19700    executor.run_until_parked();
19701
19702    cx.assert_state_with_diff(
19703        r#"
19704        use some::mod1;
19705        use some::mod2;
19706
19707        const A: u32 = 42;
19708      + const B: u32 = 42;
19709      + const C: u32 = 42;
19710      + const D: u32 = 42;
19711      + ˇ
19712
19713        fn main() {
19714            println!("hello");
19715
19716            println!("world");
19717        }
19718      "#
19719        .unindent(),
19720    );
19721
19722    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19723    executor.run_until_parked();
19724
19725    cx.assert_state_with_diff(
19726        r#"
19727        use some::mod1;
19728        use some::mod2;
19729
19730        const A: u32 = 42;
19731      + const B: u32 = 42;
19732      + const C: u32 = 42;
19733      + const D: u32 = 42;
19734      + const E: u32 = 42;
19735      + ˇ
19736
19737        fn main() {
19738            println!("hello");
19739
19740            println!("world");
19741        }
19742      "#
19743        .unindent(),
19744    );
19745
19746    cx.update_editor(|editor, window, cx| {
19747        editor.delete_line(&DeleteLine, window, cx);
19748    });
19749    executor.run_until_parked();
19750
19751    cx.assert_state_with_diff(
19752        r#"
19753        use some::mod1;
19754        use some::mod2;
19755
19756        const A: u32 = 42;
19757      + const B: u32 = 42;
19758      + const C: u32 = 42;
19759      + const D: u32 = 42;
19760      + const E: u32 = 42;
19761        ˇ
19762        fn main() {
19763            println!("hello");
19764
19765            println!("world");
19766        }
19767      "#
19768        .unindent(),
19769    );
19770
19771    cx.update_editor(|editor, window, cx| {
19772        editor.move_up(&MoveUp, window, cx);
19773        editor.delete_line(&DeleteLine, window, cx);
19774        editor.move_up(&MoveUp, window, cx);
19775        editor.delete_line(&DeleteLine, window, cx);
19776        editor.move_up(&MoveUp, window, cx);
19777        editor.delete_line(&DeleteLine, window, cx);
19778    });
19779    executor.run_until_parked();
19780    cx.assert_state_with_diff(
19781        r#"
19782        use some::mod1;
19783        use some::mod2;
19784
19785        const A: u32 = 42;
19786      + const B: u32 = 42;
19787        ˇ
19788        fn main() {
19789            println!("hello");
19790
19791            println!("world");
19792        }
19793      "#
19794        .unindent(),
19795    );
19796
19797    cx.update_editor(|editor, window, cx| {
19798        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19799        editor.delete_line(&DeleteLine, window, cx);
19800    });
19801    executor.run_until_parked();
19802    cx.assert_state_with_diff(
19803        r#"
19804        ˇ
19805        fn main() {
19806            println!("hello");
19807
19808            println!("world");
19809        }
19810      "#
19811        .unindent(),
19812    );
19813}
19814
19815#[gpui::test]
19816async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19817    init_test(cx, |_| {});
19818
19819    let mut cx = EditorTestContext::new(cx).await;
19820    cx.set_head_text(indoc! { "
19821        one
19822        two
19823        three
19824        four
19825        five
19826        "
19827    });
19828    cx.set_state(indoc! { "
19829        one
19830        ˇthree
19831        five
19832    "});
19833    cx.run_until_parked();
19834    cx.update_editor(|editor, window, cx| {
19835        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19836    });
19837    cx.assert_state_with_diff(
19838        indoc! { "
19839        one
19840      - two
19841        ˇthree
19842      - four
19843        five
19844    "}
19845        .to_string(),
19846    );
19847    cx.update_editor(|editor, window, cx| {
19848        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19849    });
19850
19851    cx.assert_state_with_diff(
19852        indoc! { "
19853        one
19854        ˇthree
19855        five
19856    "}
19857        .to_string(),
19858    );
19859
19860    cx.set_state(indoc! { "
19861        one
19862        ˇTWO
19863        three
19864        four
19865        five
19866    "});
19867    cx.run_until_parked();
19868    cx.update_editor(|editor, window, cx| {
19869        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19870    });
19871
19872    cx.assert_state_with_diff(
19873        indoc! { "
19874            one
19875          - two
19876          + ˇTWO
19877            three
19878            four
19879            five
19880        "}
19881        .to_string(),
19882    );
19883    cx.update_editor(|editor, window, cx| {
19884        editor.move_up(&Default::default(), window, cx);
19885        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19886    });
19887    cx.assert_state_with_diff(
19888        indoc! { "
19889            one
19890            ˇTWO
19891            three
19892            four
19893            five
19894        "}
19895        .to_string(),
19896    );
19897}
19898
19899#[gpui::test]
19900async fn test_edits_around_expanded_deletion_hunks(
19901    executor: BackgroundExecutor,
19902    cx: &mut TestAppContext,
19903) {
19904    init_test(cx, |_| {});
19905
19906    let mut cx = EditorTestContext::new(cx).await;
19907
19908    let diff_base = r#"
19909        use some::mod1;
19910        use some::mod2;
19911
19912        const A: u32 = 42;
19913        const B: u32 = 42;
19914        const C: u32 = 42;
19915
19916
19917        fn main() {
19918            println!("hello");
19919
19920            println!("world");
19921        }
19922    "#
19923    .unindent();
19924    executor.run_until_parked();
19925    cx.set_state(
19926        &r#"
19927        use some::mod1;
19928        use some::mod2;
19929
19930        ˇconst B: u32 = 42;
19931        const C: u32 = 42;
19932
19933
19934        fn main() {
19935            println!("hello");
19936
19937            println!("world");
19938        }
19939        "#
19940        .unindent(),
19941    );
19942
19943    cx.set_head_text(&diff_base);
19944    executor.run_until_parked();
19945
19946    cx.update_editor(|editor, window, cx| {
19947        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19948    });
19949    executor.run_until_parked();
19950
19951    cx.assert_state_with_diff(
19952        r#"
19953        use some::mod1;
19954        use some::mod2;
19955
19956      - const A: u32 = 42;
19957        ˇconst B: u32 = 42;
19958        const C: u32 = 42;
19959
19960
19961        fn main() {
19962            println!("hello");
19963
19964            println!("world");
19965        }
19966      "#
19967        .unindent(),
19968    );
19969
19970    cx.update_editor(|editor, window, cx| {
19971        editor.delete_line(&DeleteLine, window, cx);
19972    });
19973    executor.run_until_parked();
19974    cx.assert_state_with_diff(
19975        r#"
19976        use some::mod1;
19977        use some::mod2;
19978
19979      - const A: u32 = 42;
19980      - const B: u32 = 42;
19981        ˇconst C: u32 = 42;
19982
19983
19984        fn main() {
19985            println!("hello");
19986
19987            println!("world");
19988        }
19989      "#
19990        .unindent(),
19991    );
19992
19993    cx.update_editor(|editor, window, cx| {
19994        editor.delete_line(&DeleteLine, window, cx);
19995    });
19996    executor.run_until_parked();
19997    cx.assert_state_with_diff(
19998        r#"
19999        use some::mod1;
20000        use some::mod2;
20001
20002      - const A: u32 = 42;
20003      - const B: u32 = 42;
20004      - const C: u32 = 42;
20005        ˇ
20006
20007        fn main() {
20008            println!("hello");
20009
20010            println!("world");
20011        }
20012      "#
20013        .unindent(),
20014    );
20015
20016    cx.update_editor(|editor, window, cx| {
20017        editor.handle_input("replacement", window, cx);
20018    });
20019    executor.run_until_parked();
20020    cx.assert_state_with_diff(
20021        r#"
20022        use some::mod1;
20023        use some::mod2;
20024
20025      - const A: u32 = 42;
20026      - const B: u32 = 42;
20027      - const C: u32 = 42;
20028      -
20029      + replacementˇ
20030
20031        fn main() {
20032            println!("hello");
20033
20034            println!("world");
20035        }
20036      "#
20037        .unindent(),
20038    );
20039}
20040
20041#[gpui::test]
20042async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20043    init_test(cx, |_| {});
20044
20045    let mut cx = EditorTestContext::new(cx).await;
20046
20047    let base_text = r#"
20048        one
20049        two
20050        three
20051        four
20052        five
20053    "#
20054    .unindent();
20055    executor.run_until_parked();
20056    cx.set_state(
20057        &r#"
20058        one
20059        two
20060        fˇour
20061        five
20062        "#
20063        .unindent(),
20064    );
20065
20066    cx.set_head_text(&base_text);
20067    executor.run_until_parked();
20068
20069    cx.update_editor(|editor, window, cx| {
20070        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20071    });
20072    executor.run_until_parked();
20073
20074    cx.assert_state_with_diff(
20075        r#"
20076          one
20077          two
20078        - three
20079          fˇour
20080          five
20081        "#
20082        .unindent(),
20083    );
20084
20085    cx.update_editor(|editor, window, cx| {
20086        editor.backspace(&Backspace, window, cx);
20087        editor.backspace(&Backspace, window, cx);
20088    });
20089    executor.run_until_parked();
20090    cx.assert_state_with_diff(
20091        r#"
20092          one
20093          two
20094        - threeˇ
20095        - four
20096        + our
20097          five
20098        "#
20099        .unindent(),
20100    );
20101}
20102
20103#[gpui::test]
20104async fn test_edit_after_expanded_modification_hunk(
20105    executor: BackgroundExecutor,
20106    cx: &mut TestAppContext,
20107) {
20108    init_test(cx, |_| {});
20109
20110    let mut cx = EditorTestContext::new(cx).await;
20111
20112    let diff_base = r#"
20113        use some::mod1;
20114        use some::mod2;
20115
20116        const A: u32 = 42;
20117        const B: u32 = 42;
20118        const C: u32 = 42;
20119        const D: u32 = 42;
20120
20121
20122        fn main() {
20123            println!("hello");
20124
20125            println!("world");
20126        }"#
20127    .unindent();
20128
20129    cx.set_state(
20130        &r#"
20131        use some::mod1;
20132        use some::mod2;
20133
20134        const A: u32 = 42;
20135        const B: u32 = 42;
20136        const C: u32 = 43ˇ
20137        const D: u32 = 42;
20138
20139
20140        fn main() {
20141            println!("hello");
20142
20143            println!("world");
20144        }"#
20145        .unindent(),
20146    );
20147
20148    cx.set_head_text(&diff_base);
20149    executor.run_until_parked();
20150    cx.update_editor(|editor, window, cx| {
20151        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20152    });
20153    executor.run_until_parked();
20154
20155    cx.assert_state_with_diff(
20156        r#"
20157        use some::mod1;
20158        use some::mod2;
20159
20160        const A: u32 = 42;
20161        const B: u32 = 42;
20162      - const C: u32 = 42;
20163      + const C: u32 = 43ˇ
20164        const D: u32 = 42;
20165
20166
20167        fn main() {
20168            println!("hello");
20169
20170            println!("world");
20171        }"#
20172        .unindent(),
20173    );
20174
20175    cx.update_editor(|editor, window, cx| {
20176        editor.handle_input("\nnew_line\n", window, cx);
20177    });
20178    executor.run_until_parked();
20179
20180    cx.assert_state_with_diff(
20181        r#"
20182        use some::mod1;
20183        use some::mod2;
20184
20185        const A: u32 = 42;
20186        const B: u32 = 42;
20187      - const C: u32 = 42;
20188      + const C: u32 = 43
20189      + new_line
20190      + ˇ
20191        const D: u32 = 42;
20192
20193
20194        fn main() {
20195            println!("hello");
20196
20197            println!("world");
20198        }"#
20199        .unindent(),
20200    );
20201}
20202
20203#[gpui::test]
20204async fn test_stage_and_unstage_added_file_hunk(
20205    executor: BackgroundExecutor,
20206    cx: &mut TestAppContext,
20207) {
20208    init_test(cx, |_| {});
20209
20210    let mut cx = EditorTestContext::new(cx).await;
20211    cx.update_editor(|editor, _, cx| {
20212        editor.set_expand_all_diff_hunks(cx);
20213    });
20214
20215    let working_copy = r#"
20216            ˇfn main() {
20217                println!("hello, world!");
20218            }
20219        "#
20220    .unindent();
20221
20222    cx.set_state(&working_copy);
20223    executor.run_until_parked();
20224
20225    cx.assert_state_with_diff(
20226        r#"
20227            + ˇfn main() {
20228            +     println!("hello, world!");
20229            + }
20230        "#
20231        .unindent(),
20232    );
20233    cx.assert_index_text(None);
20234
20235    cx.update_editor(|editor, window, cx| {
20236        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20237    });
20238    executor.run_until_parked();
20239    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20240    cx.assert_state_with_diff(
20241        r#"
20242            + ˇfn main() {
20243            +     println!("hello, world!");
20244            + }
20245        "#
20246        .unindent(),
20247    );
20248
20249    cx.update_editor(|editor, window, cx| {
20250        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20251    });
20252    executor.run_until_parked();
20253    cx.assert_index_text(None);
20254}
20255
20256async fn setup_indent_guides_editor(
20257    text: &str,
20258    cx: &mut TestAppContext,
20259) -> (BufferId, EditorTestContext) {
20260    init_test(cx, |_| {});
20261
20262    let mut cx = EditorTestContext::new(cx).await;
20263
20264    let buffer_id = cx.update_editor(|editor, window, cx| {
20265        editor.set_text(text, window, cx);
20266        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20267
20268        buffer_ids[0]
20269    });
20270
20271    (buffer_id, cx)
20272}
20273
20274fn assert_indent_guides(
20275    range: Range<u32>,
20276    expected: Vec<IndentGuide>,
20277    active_indices: Option<Vec<usize>>,
20278    cx: &mut EditorTestContext,
20279) {
20280    let indent_guides = cx.update_editor(|editor, window, cx| {
20281        let snapshot = editor.snapshot(window, cx).display_snapshot;
20282        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20283            editor,
20284            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20285            true,
20286            &snapshot,
20287            cx,
20288        );
20289
20290        indent_guides.sort_by(|a, b| {
20291            a.depth.cmp(&b.depth).then(
20292                a.start_row
20293                    .cmp(&b.start_row)
20294                    .then(a.end_row.cmp(&b.end_row)),
20295            )
20296        });
20297        indent_guides
20298    });
20299
20300    if let Some(expected) = active_indices {
20301        let active_indices = cx.update_editor(|editor, window, cx| {
20302            let snapshot = editor.snapshot(window, cx).display_snapshot;
20303            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20304        });
20305
20306        assert_eq!(
20307            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20308            expected,
20309            "Active indent guide indices do not match"
20310        );
20311    }
20312
20313    assert_eq!(indent_guides, expected, "Indent guides do not match");
20314}
20315
20316fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20317    IndentGuide {
20318        buffer_id,
20319        start_row: MultiBufferRow(start_row),
20320        end_row: MultiBufferRow(end_row),
20321        depth,
20322        tab_size: 4,
20323        settings: IndentGuideSettings {
20324            enabled: true,
20325            line_width: 1,
20326            active_line_width: 1,
20327            coloring: IndentGuideColoring::default(),
20328            background_coloring: IndentGuideBackgroundColoring::default(),
20329        },
20330    }
20331}
20332
20333#[gpui::test]
20334async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20335    let (buffer_id, mut cx) = setup_indent_guides_editor(
20336        &"
20337        fn main() {
20338            let a = 1;
20339        }"
20340        .unindent(),
20341        cx,
20342    )
20343    .await;
20344
20345    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20346}
20347
20348#[gpui::test]
20349async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20350    let (buffer_id, mut cx) = setup_indent_guides_editor(
20351        &"
20352        fn main() {
20353            let a = 1;
20354            let b = 2;
20355        }"
20356        .unindent(),
20357        cx,
20358    )
20359    .await;
20360
20361    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20362}
20363
20364#[gpui::test]
20365async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20366    let (buffer_id, mut cx) = setup_indent_guides_editor(
20367        &"
20368        fn main() {
20369            let a = 1;
20370            if a == 3 {
20371                let b = 2;
20372            } else {
20373                let c = 3;
20374            }
20375        }"
20376        .unindent(),
20377        cx,
20378    )
20379    .await;
20380
20381    assert_indent_guides(
20382        0..8,
20383        vec![
20384            indent_guide(buffer_id, 1, 6, 0),
20385            indent_guide(buffer_id, 3, 3, 1),
20386            indent_guide(buffer_id, 5, 5, 1),
20387        ],
20388        None,
20389        &mut cx,
20390    );
20391}
20392
20393#[gpui::test]
20394async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20395    let (buffer_id, mut cx) = setup_indent_guides_editor(
20396        &"
20397        fn main() {
20398            let a = 1;
20399                let b = 2;
20400            let c = 3;
20401        }"
20402        .unindent(),
20403        cx,
20404    )
20405    .await;
20406
20407    assert_indent_guides(
20408        0..5,
20409        vec![
20410            indent_guide(buffer_id, 1, 3, 0),
20411            indent_guide(buffer_id, 2, 2, 1),
20412        ],
20413        None,
20414        &mut cx,
20415    );
20416}
20417
20418#[gpui::test]
20419async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20420    let (buffer_id, mut cx) = setup_indent_guides_editor(
20421        &"
20422        fn main() {
20423            let a = 1;
20424
20425            let c = 3;
20426        }"
20427        .unindent(),
20428        cx,
20429    )
20430    .await;
20431
20432    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20433}
20434
20435#[gpui::test]
20436async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20437    let (buffer_id, mut cx) = setup_indent_guides_editor(
20438        &"
20439        fn main() {
20440            let a = 1;
20441
20442            let c = 3;
20443
20444            if a == 3 {
20445                let b = 2;
20446            } else {
20447                let c = 3;
20448            }
20449        }"
20450        .unindent(),
20451        cx,
20452    )
20453    .await;
20454
20455    assert_indent_guides(
20456        0..11,
20457        vec![
20458            indent_guide(buffer_id, 1, 9, 0),
20459            indent_guide(buffer_id, 6, 6, 1),
20460            indent_guide(buffer_id, 8, 8, 1),
20461        ],
20462        None,
20463        &mut cx,
20464    );
20465}
20466
20467#[gpui::test]
20468async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20469    let (buffer_id, mut cx) = setup_indent_guides_editor(
20470        &"
20471        fn main() {
20472            let a = 1;
20473
20474            let c = 3;
20475
20476            if a == 3 {
20477                let b = 2;
20478            } else {
20479                let c = 3;
20480            }
20481        }"
20482        .unindent(),
20483        cx,
20484    )
20485    .await;
20486
20487    assert_indent_guides(
20488        1..11,
20489        vec![
20490            indent_guide(buffer_id, 1, 9, 0),
20491            indent_guide(buffer_id, 6, 6, 1),
20492            indent_guide(buffer_id, 8, 8, 1),
20493        ],
20494        None,
20495        &mut cx,
20496    );
20497}
20498
20499#[gpui::test]
20500async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20501    let (buffer_id, mut cx) = setup_indent_guides_editor(
20502        &"
20503        fn main() {
20504            let a = 1;
20505
20506            let c = 3;
20507
20508            if a == 3 {
20509                let b = 2;
20510            } else {
20511                let c = 3;
20512            }
20513        }"
20514        .unindent(),
20515        cx,
20516    )
20517    .await;
20518
20519    assert_indent_guides(
20520        1..10,
20521        vec![
20522            indent_guide(buffer_id, 1, 9, 0),
20523            indent_guide(buffer_id, 6, 6, 1),
20524            indent_guide(buffer_id, 8, 8, 1),
20525        ],
20526        None,
20527        &mut cx,
20528    );
20529}
20530
20531#[gpui::test]
20532async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20533    let (buffer_id, mut cx) = setup_indent_guides_editor(
20534        &"
20535        fn main() {
20536            if a {
20537                b(
20538                    c,
20539                    d,
20540                )
20541            } else {
20542                e(
20543                    f
20544                )
20545            }
20546        }"
20547        .unindent(),
20548        cx,
20549    )
20550    .await;
20551
20552    assert_indent_guides(
20553        0..11,
20554        vec![
20555            indent_guide(buffer_id, 1, 10, 0),
20556            indent_guide(buffer_id, 2, 5, 1),
20557            indent_guide(buffer_id, 7, 9, 1),
20558            indent_guide(buffer_id, 3, 4, 2),
20559            indent_guide(buffer_id, 8, 8, 2),
20560        ],
20561        None,
20562        &mut cx,
20563    );
20564
20565    cx.update_editor(|editor, window, cx| {
20566        editor.fold_at(MultiBufferRow(2), window, cx);
20567        assert_eq!(
20568            editor.display_text(cx),
20569            "
20570            fn main() {
20571                if a {
20572                    b(⋯
20573                    )
20574                } else {
20575                    e(
20576                        f
20577                    )
20578                }
20579            }"
20580            .unindent()
20581        );
20582    });
20583
20584    assert_indent_guides(
20585        0..11,
20586        vec![
20587            indent_guide(buffer_id, 1, 10, 0),
20588            indent_guide(buffer_id, 2, 5, 1),
20589            indent_guide(buffer_id, 7, 9, 1),
20590            indent_guide(buffer_id, 8, 8, 2),
20591        ],
20592        None,
20593        &mut cx,
20594    );
20595}
20596
20597#[gpui::test]
20598async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20599    let (buffer_id, mut cx) = setup_indent_guides_editor(
20600        &"
20601        block1
20602            block2
20603                block3
20604                    block4
20605            block2
20606        block1
20607        block1"
20608            .unindent(),
20609        cx,
20610    )
20611    .await;
20612
20613    assert_indent_guides(
20614        1..10,
20615        vec![
20616            indent_guide(buffer_id, 1, 4, 0),
20617            indent_guide(buffer_id, 2, 3, 1),
20618            indent_guide(buffer_id, 3, 3, 2),
20619        ],
20620        None,
20621        &mut cx,
20622    );
20623}
20624
20625#[gpui::test]
20626async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20627    let (buffer_id, mut cx) = setup_indent_guides_editor(
20628        &"
20629        block1
20630            block2
20631                block3
20632
20633        block1
20634        block1"
20635            .unindent(),
20636        cx,
20637    )
20638    .await;
20639
20640    assert_indent_guides(
20641        0..6,
20642        vec![
20643            indent_guide(buffer_id, 1, 2, 0),
20644            indent_guide(buffer_id, 2, 2, 1),
20645        ],
20646        None,
20647        &mut cx,
20648    );
20649}
20650
20651#[gpui::test]
20652async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20653    let (buffer_id, mut cx) = setup_indent_guides_editor(
20654        &"
20655        function component() {
20656        \treturn (
20657        \t\t\t
20658        \t\t<div>
20659        \t\t\t<abc></abc>
20660        \t\t</div>
20661        \t)
20662        }"
20663        .unindent(),
20664        cx,
20665    )
20666    .await;
20667
20668    assert_indent_guides(
20669        0..8,
20670        vec![
20671            indent_guide(buffer_id, 1, 6, 0),
20672            indent_guide(buffer_id, 2, 5, 1),
20673            indent_guide(buffer_id, 4, 4, 2),
20674        ],
20675        None,
20676        &mut cx,
20677    );
20678}
20679
20680#[gpui::test]
20681async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20682    let (buffer_id, mut cx) = setup_indent_guides_editor(
20683        &"
20684        function component() {
20685        \treturn (
20686        \t
20687        \t\t<div>
20688        \t\t\t<abc></abc>
20689        \t\t</div>
20690        \t)
20691        }"
20692        .unindent(),
20693        cx,
20694    )
20695    .await;
20696
20697    assert_indent_guides(
20698        0..8,
20699        vec![
20700            indent_guide(buffer_id, 1, 6, 0),
20701            indent_guide(buffer_id, 2, 5, 1),
20702            indent_guide(buffer_id, 4, 4, 2),
20703        ],
20704        None,
20705        &mut cx,
20706    );
20707}
20708
20709#[gpui::test]
20710async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20711    let (buffer_id, mut cx) = setup_indent_guides_editor(
20712        &"
20713        block1
20714
20715
20716
20717            block2
20718        "
20719        .unindent(),
20720        cx,
20721    )
20722    .await;
20723
20724    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20725}
20726
20727#[gpui::test]
20728async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20729    let (buffer_id, mut cx) = setup_indent_guides_editor(
20730        &"
20731        def a:
20732        \tb = 3
20733        \tif True:
20734        \t\tc = 4
20735        \t\td = 5
20736        \tprint(b)
20737        "
20738        .unindent(),
20739        cx,
20740    )
20741    .await;
20742
20743    assert_indent_guides(
20744        0..6,
20745        vec![
20746            indent_guide(buffer_id, 1, 5, 0),
20747            indent_guide(buffer_id, 3, 4, 1),
20748        ],
20749        None,
20750        &mut cx,
20751    );
20752}
20753
20754#[gpui::test]
20755async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20756    let (buffer_id, mut cx) = setup_indent_guides_editor(
20757        &"
20758    fn main() {
20759        let a = 1;
20760    }"
20761        .unindent(),
20762        cx,
20763    )
20764    .await;
20765
20766    cx.update_editor(|editor, window, cx| {
20767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20768            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20769        });
20770    });
20771
20772    assert_indent_guides(
20773        0..3,
20774        vec![indent_guide(buffer_id, 1, 1, 0)],
20775        Some(vec![0]),
20776        &mut cx,
20777    );
20778}
20779
20780#[gpui::test]
20781async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20782    let (buffer_id, mut cx) = setup_indent_guides_editor(
20783        &"
20784    fn main() {
20785        if 1 == 2 {
20786            let a = 1;
20787        }
20788    }"
20789        .unindent(),
20790        cx,
20791    )
20792    .await;
20793
20794    cx.update_editor(|editor, window, cx| {
20795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20796            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20797        });
20798    });
20799
20800    assert_indent_guides(
20801        0..4,
20802        vec![
20803            indent_guide(buffer_id, 1, 3, 0),
20804            indent_guide(buffer_id, 2, 2, 1),
20805        ],
20806        Some(vec![1]),
20807        &mut cx,
20808    );
20809
20810    cx.update_editor(|editor, window, cx| {
20811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20812            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20813        });
20814    });
20815
20816    assert_indent_guides(
20817        0..4,
20818        vec![
20819            indent_guide(buffer_id, 1, 3, 0),
20820            indent_guide(buffer_id, 2, 2, 1),
20821        ],
20822        Some(vec![1]),
20823        &mut cx,
20824    );
20825
20826    cx.update_editor(|editor, window, cx| {
20827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20828            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20829        });
20830    });
20831
20832    assert_indent_guides(
20833        0..4,
20834        vec![
20835            indent_guide(buffer_id, 1, 3, 0),
20836            indent_guide(buffer_id, 2, 2, 1),
20837        ],
20838        Some(vec![0]),
20839        &mut cx,
20840    );
20841}
20842
20843#[gpui::test]
20844async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20845    let (buffer_id, mut cx) = setup_indent_guides_editor(
20846        &"
20847    fn main() {
20848        let a = 1;
20849
20850        let b = 2;
20851    }"
20852        .unindent(),
20853        cx,
20854    )
20855    .await;
20856
20857    cx.update_editor(|editor, window, cx| {
20858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20859            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20860        });
20861    });
20862
20863    assert_indent_guides(
20864        0..5,
20865        vec![indent_guide(buffer_id, 1, 3, 0)],
20866        Some(vec![0]),
20867        &mut cx,
20868    );
20869}
20870
20871#[gpui::test]
20872async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20873    let (buffer_id, mut cx) = setup_indent_guides_editor(
20874        &"
20875    def m:
20876        a = 1
20877        pass"
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, 2, 0)],
20892        Some(vec![0]),
20893        &mut cx,
20894    );
20895}
20896
20897#[gpui::test]
20898async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20899    init_test(cx, |_| {});
20900    let mut cx = EditorTestContext::new(cx).await;
20901    let text = indoc! {
20902        "
20903        impl A {
20904            fn b() {
20905                0;
20906                3;
20907                5;
20908                6;
20909                7;
20910            }
20911        }
20912        "
20913    };
20914    let base_text = indoc! {
20915        "
20916        impl A {
20917            fn b() {
20918                0;
20919                1;
20920                2;
20921                3;
20922                4;
20923            }
20924            fn c() {
20925                5;
20926                6;
20927                7;
20928            }
20929        }
20930        "
20931    };
20932
20933    cx.update_editor(|editor, window, cx| {
20934        editor.set_text(text, window, cx);
20935
20936        editor.buffer().update(cx, |multibuffer, cx| {
20937            let buffer = multibuffer.as_singleton().unwrap();
20938            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20939
20940            multibuffer.set_all_diff_hunks_expanded(cx);
20941            multibuffer.add_diff(diff, cx);
20942
20943            buffer.read(cx).remote_id()
20944        })
20945    });
20946    cx.run_until_parked();
20947
20948    cx.assert_state_with_diff(
20949        indoc! { "
20950          impl A {
20951              fn b() {
20952                  0;
20953        -         1;
20954        -         2;
20955                  3;
20956        -         4;
20957        -     }
20958        -     fn c() {
20959                  5;
20960                  6;
20961                  7;
20962              }
20963          }
20964          ˇ"
20965        }
20966        .to_string(),
20967    );
20968
20969    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20970        editor
20971            .snapshot(window, cx)
20972            .buffer_snapshot()
20973            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20974            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20975            .collect::<Vec<_>>()
20976    });
20977    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20978    assert_eq!(
20979        actual_guides,
20980        vec![
20981            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20982            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20983            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20984        ]
20985    );
20986}
20987
20988#[gpui::test]
20989async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20990    init_test(cx, |_| {});
20991    let mut cx = EditorTestContext::new(cx).await;
20992
20993    let diff_base = r#"
20994        a
20995        b
20996        c
20997        "#
20998    .unindent();
20999
21000    cx.set_state(
21001        &r#"
21002        ˇA
21003        b
21004        C
21005        "#
21006        .unindent(),
21007    );
21008    cx.set_head_text(&diff_base);
21009    cx.update_editor(|editor, window, cx| {
21010        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21011    });
21012    executor.run_until_parked();
21013
21014    let both_hunks_expanded = r#"
21015        - a
21016        + ˇA
21017          b
21018        - c
21019        + C
21020        "#
21021    .unindent();
21022
21023    cx.assert_state_with_diff(both_hunks_expanded.clone());
21024
21025    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21026        let snapshot = editor.snapshot(window, cx);
21027        let hunks = editor
21028            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21029            .collect::<Vec<_>>();
21030        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21031        let buffer_id = hunks[0].buffer_id;
21032        hunks
21033            .into_iter()
21034            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21035            .collect::<Vec<_>>()
21036    });
21037    assert_eq!(hunk_ranges.len(), 2);
21038
21039    cx.update_editor(|editor, _, cx| {
21040        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21041    });
21042    executor.run_until_parked();
21043
21044    let second_hunk_expanded = r#"
21045          ˇA
21046          b
21047        - c
21048        + C
21049        "#
21050    .unindent();
21051
21052    cx.assert_state_with_diff(second_hunk_expanded);
21053
21054    cx.update_editor(|editor, _, cx| {
21055        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21056    });
21057    executor.run_until_parked();
21058
21059    cx.assert_state_with_diff(both_hunks_expanded.clone());
21060
21061    cx.update_editor(|editor, _, cx| {
21062        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21063    });
21064    executor.run_until_parked();
21065
21066    let first_hunk_expanded = r#"
21067        - a
21068        + ˇA
21069          b
21070          C
21071        "#
21072    .unindent();
21073
21074    cx.assert_state_with_diff(first_hunk_expanded);
21075
21076    cx.update_editor(|editor, _, cx| {
21077        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21078    });
21079    executor.run_until_parked();
21080
21081    cx.assert_state_with_diff(both_hunks_expanded);
21082
21083    cx.set_state(
21084        &r#"
21085        ˇA
21086        b
21087        "#
21088        .unindent(),
21089    );
21090    cx.run_until_parked();
21091
21092    // TODO this cursor position seems bad
21093    cx.assert_state_with_diff(
21094        r#"
21095        - ˇa
21096        + A
21097          b
21098        "#
21099        .unindent(),
21100    );
21101
21102    cx.update_editor(|editor, window, cx| {
21103        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21104    });
21105
21106    cx.assert_state_with_diff(
21107        r#"
21108            - ˇa
21109            + A
21110              b
21111            - c
21112            "#
21113        .unindent(),
21114    );
21115
21116    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21117        let snapshot = editor.snapshot(window, cx);
21118        let hunks = editor
21119            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21120            .collect::<Vec<_>>();
21121        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21122        let buffer_id = hunks[0].buffer_id;
21123        hunks
21124            .into_iter()
21125            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21126            .collect::<Vec<_>>()
21127    });
21128    assert_eq!(hunk_ranges.len(), 2);
21129
21130    cx.update_editor(|editor, _, cx| {
21131        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21132    });
21133    executor.run_until_parked();
21134
21135    cx.assert_state_with_diff(
21136        r#"
21137        - ˇa
21138        + A
21139          b
21140        "#
21141        .unindent(),
21142    );
21143}
21144
21145#[gpui::test]
21146async fn test_toggle_deletion_hunk_at_start_of_file(
21147    executor: BackgroundExecutor,
21148    cx: &mut TestAppContext,
21149) {
21150    init_test(cx, |_| {});
21151    let mut cx = EditorTestContext::new(cx).await;
21152
21153    let diff_base = r#"
21154        a
21155        b
21156        c
21157        "#
21158    .unindent();
21159
21160    cx.set_state(
21161        &r#"
21162        ˇb
21163        c
21164        "#
21165        .unindent(),
21166    );
21167    cx.set_head_text(&diff_base);
21168    cx.update_editor(|editor, window, cx| {
21169        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21170    });
21171    executor.run_until_parked();
21172
21173    let hunk_expanded = r#"
21174        - a
21175          ˇb
21176          c
21177        "#
21178    .unindent();
21179
21180    cx.assert_state_with_diff(hunk_expanded.clone());
21181
21182    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21183        let snapshot = editor.snapshot(window, cx);
21184        let hunks = editor
21185            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21186            .collect::<Vec<_>>();
21187        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21188        let buffer_id = hunks[0].buffer_id;
21189        hunks
21190            .into_iter()
21191            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21192            .collect::<Vec<_>>()
21193    });
21194    assert_eq!(hunk_ranges.len(), 1);
21195
21196    cx.update_editor(|editor, _, cx| {
21197        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21198    });
21199    executor.run_until_parked();
21200
21201    let hunk_collapsed = r#"
21202          ˇb
21203          c
21204        "#
21205    .unindent();
21206
21207    cx.assert_state_with_diff(hunk_collapsed);
21208
21209    cx.update_editor(|editor, _, cx| {
21210        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21211    });
21212    executor.run_until_parked();
21213
21214    cx.assert_state_with_diff(hunk_expanded);
21215}
21216
21217#[gpui::test]
21218async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21219    init_test(cx, |_| {});
21220
21221    let fs = FakeFs::new(cx.executor());
21222    fs.insert_tree(
21223        path!("/test"),
21224        json!({
21225            ".git": {},
21226            "file-1": "ONE\n",
21227            "file-2": "TWO\n",
21228            "file-3": "THREE\n",
21229        }),
21230    )
21231    .await;
21232
21233    fs.set_head_for_repo(
21234        path!("/test/.git").as_ref(),
21235        &[
21236            ("file-1", "one\n".into()),
21237            ("file-2", "two\n".into()),
21238            ("file-3", "three\n".into()),
21239        ],
21240        "deadbeef",
21241    );
21242
21243    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21244    let mut buffers = vec![];
21245    for i in 1..=3 {
21246        let buffer = project
21247            .update(cx, |project, cx| {
21248                let path = format!(path!("/test/file-{}"), i);
21249                project.open_local_buffer(path, cx)
21250            })
21251            .await
21252            .unwrap();
21253        buffers.push(buffer);
21254    }
21255
21256    let multibuffer = cx.new(|cx| {
21257        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21258        multibuffer.set_all_diff_hunks_expanded(cx);
21259        for buffer in &buffers {
21260            let snapshot = buffer.read(cx).snapshot();
21261            multibuffer.set_excerpts_for_path(
21262                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21263                buffer.clone(),
21264                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21265                2,
21266                cx,
21267            );
21268        }
21269        multibuffer
21270    });
21271
21272    let editor = cx.add_window(|window, cx| {
21273        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21274    });
21275    cx.run_until_parked();
21276
21277    let snapshot = editor
21278        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21279        .unwrap();
21280    let hunks = snapshot
21281        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21282        .map(|hunk| match hunk {
21283            DisplayDiffHunk::Unfolded {
21284                display_row_range, ..
21285            } => display_row_range,
21286            DisplayDiffHunk::Folded { .. } => unreachable!(),
21287        })
21288        .collect::<Vec<_>>();
21289    assert_eq!(
21290        hunks,
21291        [
21292            DisplayRow(2)..DisplayRow(4),
21293            DisplayRow(7)..DisplayRow(9),
21294            DisplayRow(12)..DisplayRow(14),
21295        ]
21296    );
21297}
21298
21299#[gpui::test]
21300async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21301    init_test(cx, |_| {});
21302
21303    let mut cx = EditorTestContext::new(cx).await;
21304    cx.set_head_text(indoc! { "
21305        one
21306        two
21307        three
21308        four
21309        five
21310        "
21311    });
21312    cx.set_index_text(indoc! { "
21313        one
21314        two
21315        three
21316        four
21317        five
21318        "
21319    });
21320    cx.set_state(indoc! {"
21321        one
21322        TWO
21323        ˇTHREE
21324        FOUR
21325        five
21326    "});
21327    cx.run_until_parked();
21328    cx.update_editor(|editor, window, cx| {
21329        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21330    });
21331    cx.run_until_parked();
21332    cx.assert_index_text(Some(indoc! {"
21333        one
21334        TWO
21335        THREE
21336        FOUR
21337        five
21338    "}));
21339    cx.set_state(indoc! { "
21340        one
21341        TWO
21342        ˇTHREE-HUNDRED
21343        FOUR
21344        five
21345    "});
21346    cx.run_until_parked();
21347    cx.update_editor(|editor, window, cx| {
21348        let snapshot = editor.snapshot(window, cx);
21349        let hunks = editor
21350            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21351            .collect::<Vec<_>>();
21352        assert_eq!(hunks.len(), 1);
21353        assert_eq!(
21354            hunks[0].status(),
21355            DiffHunkStatus {
21356                kind: DiffHunkStatusKind::Modified,
21357                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21358            }
21359        );
21360
21361        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21362    });
21363    cx.run_until_parked();
21364    cx.assert_index_text(Some(indoc! {"
21365        one
21366        TWO
21367        THREE-HUNDRED
21368        FOUR
21369        five
21370    "}));
21371}
21372
21373#[gpui::test]
21374fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21375    init_test(cx, |_| {});
21376
21377    let editor = cx.add_window(|window, cx| {
21378        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21379        build_editor(buffer, window, cx)
21380    });
21381
21382    let render_args = Arc::new(Mutex::new(None));
21383    let snapshot = editor
21384        .update(cx, |editor, window, cx| {
21385            let snapshot = editor.buffer().read(cx).snapshot(cx);
21386            let range =
21387                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21388
21389            struct RenderArgs {
21390                row: MultiBufferRow,
21391                folded: bool,
21392                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21393            }
21394
21395            let crease = Crease::inline(
21396                range,
21397                FoldPlaceholder::test(),
21398                {
21399                    let toggle_callback = render_args.clone();
21400                    move |row, folded, callback, _window, _cx| {
21401                        *toggle_callback.lock() = Some(RenderArgs {
21402                            row,
21403                            folded,
21404                            callback,
21405                        });
21406                        div()
21407                    }
21408                },
21409                |_row, _folded, _window, _cx| div(),
21410            );
21411
21412            editor.insert_creases(Some(crease), cx);
21413            let snapshot = editor.snapshot(window, cx);
21414            let _div =
21415                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21416            snapshot
21417        })
21418        .unwrap();
21419
21420    let render_args = render_args.lock().take().unwrap();
21421    assert_eq!(render_args.row, MultiBufferRow(1));
21422    assert!(!render_args.folded);
21423    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21424
21425    cx.update_window(*editor, |_, window, cx| {
21426        (render_args.callback)(true, window, cx)
21427    })
21428    .unwrap();
21429    let snapshot = editor
21430        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21431        .unwrap();
21432    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21433
21434    cx.update_window(*editor, |_, window, cx| {
21435        (render_args.callback)(false, window, cx)
21436    })
21437    .unwrap();
21438    let snapshot = editor
21439        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21440        .unwrap();
21441    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21442}
21443
21444#[gpui::test]
21445async fn test_input_text(cx: &mut TestAppContext) {
21446    init_test(cx, |_| {});
21447    let mut cx = EditorTestContext::new(cx).await;
21448
21449    cx.set_state(
21450        &r#"ˇone
21451        two
21452
21453        three
21454        fourˇ
21455        five
21456
21457        siˇx"#
21458            .unindent(),
21459    );
21460
21461    cx.dispatch_action(HandleInput(String::new()));
21462    cx.assert_editor_state(
21463        &r#"ˇone
21464        two
21465
21466        three
21467        fourˇ
21468        five
21469
21470        siˇx"#
21471            .unindent(),
21472    );
21473
21474    cx.dispatch_action(HandleInput("AAAA".to_string()));
21475    cx.assert_editor_state(
21476        &r#"AAAAˇone
21477        two
21478
21479        three
21480        fourAAAAˇ
21481        five
21482
21483        siAAAAˇx"#
21484            .unindent(),
21485    );
21486}
21487
21488#[gpui::test]
21489async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21490    init_test(cx, |_| {});
21491
21492    let mut cx = EditorTestContext::new(cx).await;
21493    cx.set_state(
21494        r#"let foo = 1;
21495let foo = 2;
21496let foo = 3;
21497let fooˇ = 4;
21498let foo = 5;
21499let foo = 6;
21500let foo = 7;
21501let foo = 8;
21502let foo = 9;
21503let foo = 10;
21504let foo = 11;
21505let foo = 12;
21506let foo = 13;
21507let foo = 14;
21508let foo = 15;"#,
21509    );
21510
21511    cx.update_editor(|e, window, cx| {
21512        assert_eq!(
21513            e.next_scroll_position,
21514            NextScrollCursorCenterTopBottom::Center,
21515            "Default next scroll direction is center",
21516        );
21517
21518        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21519        assert_eq!(
21520            e.next_scroll_position,
21521            NextScrollCursorCenterTopBottom::Top,
21522            "After center, next scroll direction should be top",
21523        );
21524
21525        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21526        assert_eq!(
21527            e.next_scroll_position,
21528            NextScrollCursorCenterTopBottom::Bottom,
21529            "After top, next scroll direction should be bottom",
21530        );
21531
21532        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21533        assert_eq!(
21534            e.next_scroll_position,
21535            NextScrollCursorCenterTopBottom::Center,
21536            "After bottom, scrolling should start over",
21537        );
21538
21539        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21540        assert_eq!(
21541            e.next_scroll_position,
21542            NextScrollCursorCenterTopBottom::Top,
21543            "Scrolling continues if retriggered fast enough"
21544        );
21545    });
21546
21547    cx.executor()
21548        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21549    cx.executor().run_until_parked();
21550    cx.update_editor(|e, _, _| {
21551        assert_eq!(
21552            e.next_scroll_position,
21553            NextScrollCursorCenterTopBottom::Center,
21554            "If scrolling is not triggered fast enough, it should reset"
21555        );
21556    });
21557}
21558
21559#[gpui::test]
21560async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21561    init_test(cx, |_| {});
21562    let mut cx = EditorLspTestContext::new_rust(
21563        lsp::ServerCapabilities {
21564            definition_provider: Some(lsp::OneOf::Left(true)),
21565            references_provider: Some(lsp::OneOf::Left(true)),
21566            ..lsp::ServerCapabilities::default()
21567        },
21568        cx,
21569    )
21570    .await;
21571
21572    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21573        let go_to_definition = cx
21574            .lsp
21575            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21576                move |params, _| async move {
21577                    if empty_go_to_definition {
21578                        Ok(None)
21579                    } else {
21580                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21581                            uri: params.text_document_position_params.text_document.uri,
21582                            range: lsp::Range::new(
21583                                lsp::Position::new(4, 3),
21584                                lsp::Position::new(4, 6),
21585                            ),
21586                        })))
21587                    }
21588                },
21589            );
21590        let references = cx
21591            .lsp
21592            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21593                Ok(Some(vec![lsp::Location {
21594                    uri: params.text_document_position.text_document.uri,
21595                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21596                }]))
21597            });
21598        (go_to_definition, references)
21599    };
21600
21601    cx.set_state(
21602        &r#"fn one() {
21603            let mut a = ˇtwo();
21604        }
21605
21606        fn two() {}"#
21607            .unindent(),
21608    );
21609    set_up_lsp_handlers(false, &mut cx);
21610    let navigated = cx
21611        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21612        .await
21613        .expect("Failed to navigate to definition");
21614    assert_eq!(
21615        navigated,
21616        Navigated::Yes,
21617        "Should have navigated to definition from the GetDefinition response"
21618    );
21619    cx.assert_editor_state(
21620        &r#"fn one() {
21621            let mut a = two();
21622        }
21623
21624        fn «twoˇ»() {}"#
21625            .unindent(),
21626    );
21627
21628    let editors = cx.update_workspace(|workspace, _, cx| {
21629        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21630    });
21631    cx.update_editor(|_, _, test_editor_cx| {
21632        assert_eq!(
21633            editors.len(),
21634            1,
21635            "Initially, only one, test, editor should be open in the workspace"
21636        );
21637        assert_eq!(
21638            test_editor_cx.entity(),
21639            editors.last().expect("Asserted len is 1").clone()
21640        );
21641    });
21642
21643    set_up_lsp_handlers(true, &mut cx);
21644    let navigated = cx
21645        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21646        .await
21647        .expect("Failed to navigate to lookup references");
21648    assert_eq!(
21649        navigated,
21650        Navigated::Yes,
21651        "Should have navigated to references as a fallback after empty GoToDefinition response"
21652    );
21653    // We should not change the selections in the existing file,
21654    // if opening another milti buffer with the references
21655    cx.assert_editor_state(
21656        &r#"fn one() {
21657            let mut a = two();
21658        }
21659
21660        fn «twoˇ»() {}"#
21661            .unindent(),
21662    );
21663    let editors = cx.update_workspace(|workspace, _, cx| {
21664        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21665    });
21666    cx.update_editor(|_, _, test_editor_cx| {
21667        assert_eq!(
21668            editors.len(),
21669            2,
21670            "After falling back to references search, we open a new editor with the results"
21671        );
21672        let references_fallback_text = editors
21673            .into_iter()
21674            .find(|new_editor| *new_editor != test_editor_cx.entity())
21675            .expect("Should have one non-test editor now")
21676            .read(test_editor_cx)
21677            .text(test_editor_cx);
21678        assert_eq!(
21679            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21680            "Should use the range from the references response and not the GoToDefinition one"
21681        );
21682    });
21683}
21684
21685#[gpui::test]
21686async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21687    init_test(cx, |_| {});
21688    cx.update(|cx| {
21689        let mut editor_settings = EditorSettings::get_global(cx).clone();
21690        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21691        EditorSettings::override_global(editor_settings, cx);
21692    });
21693    let mut cx = EditorLspTestContext::new_rust(
21694        lsp::ServerCapabilities {
21695            definition_provider: Some(lsp::OneOf::Left(true)),
21696            references_provider: Some(lsp::OneOf::Left(true)),
21697            ..lsp::ServerCapabilities::default()
21698        },
21699        cx,
21700    )
21701    .await;
21702    let original_state = r#"fn one() {
21703        let mut a = ˇtwo();
21704    }
21705
21706    fn two() {}"#
21707        .unindent();
21708    cx.set_state(&original_state);
21709
21710    let mut go_to_definition = cx
21711        .lsp
21712        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21713            move |_, _| async move { Ok(None) },
21714        );
21715    let _references = cx
21716        .lsp
21717        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21718            panic!("Should not call for references with no go to definition fallback")
21719        });
21720
21721    let navigated = cx
21722        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21723        .await
21724        .expect("Failed to navigate to lookup references");
21725    go_to_definition
21726        .next()
21727        .await
21728        .expect("Should have called the go_to_definition handler");
21729
21730    assert_eq!(
21731        navigated,
21732        Navigated::No,
21733        "Should have navigated to references as a fallback after empty GoToDefinition response"
21734    );
21735    cx.assert_editor_state(&original_state);
21736    let editors = cx.update_workspace(|workspace, _, cx| {
21737        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21738    });
21739    cx.update_editor(|_, _, _| {
21740        assert_eq!(
21741            editors.len(),
21742            1,
21743            "After unsuccessful fallback, no other editor should have been opened"
21744        );
21745    });
21746}
21747
21748#[gpui::test]
21749async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21750    init_test(cx, |_| {});
21751    let mut cx = EditorLspTestContext::new_rust(
21752        lsp::ServerCapabilities {
21753            references_provider: Some(lsp::OneOf::Left(true)),
21754            ..lsp::ServerCapabilities::default()
21755        },
21756        cx,
21757    )
21758    .await;
21759
21760    cx.set_state(
21761        &r#"
21762        fn one() {
21763            let mut a = two();
21764        }
21765
21766        fn ˇtwo() {}"#
21767            .unindent(),
21768    );
21769    cx.lsp
21770        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21771            Ok(Some(vec![
21772                lsp::Location {
21773                    uri: params.text_document_position.text_document.uri.clone(),
21774                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21775                },
21776                lsp::Location {
21777                    uri: params.text_document_position.text_document.uri,
21778                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21779                },
21780            ]))
21781        });
21782    let navigated = cx
21783        .update_editor(|editor, window, cx| {
21784            editor.find_all_references(&FindAllReferences, window, cx)
21785        })
21786        .unwrap()
21787        .await
21788        .expect("Failed to navigate to references");
21789    assert_eq!(
21790        navigated,
21791        Navigated::Yes,
21792        "Should have navigated to references from the FindAllReferences response"
21793    );
21794    cx.assert_editor_state(
21795        &r#"fn one() {
21796            let mut a = two();
21797        }
21798
21799        fn ˇtwo() {}"#
21800            .unindent(),
21801    );
21802
21803    let editors = cx.update_workspace(|workspace, _, cx| {
21804        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21805    });
21806    cx.update_editor(|_, _, _| {
21807        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21808    });
21809
21810    cx.set_state(
21811        &r#"fn one() {
21812            let mut a = ˇtwo();
21813        }
21814
21815        fn two() {}"#
21816            .unindent(),
21817    );
21818    let navigated = cx
21819        .update_editor(|editor, window, cx| {
21820            editor.find_all_references(&FindAllReferences, window, cx)
21821        })
21822        .unwrap()
21823        .await
21824        .expect("Failed to navigate to references");
21825    assert_eq!(
21826        navigated,
21827        Navigated::Yes,
21828        "Should have navigated to references from the FindAllReferences response"
21829    );
21830    cx.assert_editor_state(
21831        &r#"fn one() {
21832            let mut a = ˇtwo();
21833        }
21834
21835        fn two() {}"#
21836            .unindent(),
21837    );
21838    let editors = cx.update_workspace(|workspace, _, cx| {
21839        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21840    });
21841    cx.update_editor(|_, _, _| {
21842        assert_eq!(
21843            editors.len(),
21844            2,
21845            "should have re-used the previous multibuffer"
21846        );
21847    });
21848
21849    cx.set_state(
21850        &r#"fn one() {
21851            let mut a = ˇtwo();
21852        }
21853        fn three() {}
21854        fn two() {}"#
21855            .unindent(),
21856    );
21857    cx.lsp
21858        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21859            Ok(Some(vec![
21860                lsp::Location {
21861                    uri: params.text_document_position.text_document.uri.clone(),
21862                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21863                },
21864                lsp::Location {
21865                    uri: params.text_document_position.text_document.uri,
21866                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21867                },
21868            ]))
21869        });
21870    let navigated = cx
21871        .update_editor(|editor, window, cx| {
21872            editor.find_all_references(&FindAllReferences, window, cx)
21873        })
21874        .unwrap()
21875        .await
21876        .expect("Failed to navigate to references");
21877    assert_eq!(
21878        navigated,
21879        Navigated::Yes,
21880        "Should have navigated to references from the FindAllReferences response"
21881    );
21882    cx.assert_editor_state(
21883        &r#"fn one() {
21884                let mut a = ˇtwo();
21885            }
21886            fn three() {}
21887            fn two() {}"#
21888            .unindent(),
21889    );
21890    let editors = cx.update_workspace(|workspace, _, cx| {
21891        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21892    });
21893    cx.update_editor(|_, _, _| {
21894        assert_eq!(
21895            editors.len(),
21896            3,
21897            "should have used a new multibuffer as offsets changed"
21898        );
21899    });
21900}
21901#[gpui::test]
21902async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21903    init_test(cx, |_| {});
21904
21905    let language = Arc::new(Language::new(
21906        LanguageConfig::default(),
21907        Some(tree_sitter_rust::LANGUAGE.into()),
21908    ));
21909
21910    let text = r#"
21911        #[cfg(test)]
21912        mod tests() {
21913            #[test]
21914            fn runnable_1() {
21915                let a = 1;
21916            }
21917
21918            #[test]
21919            fn runnable_2() {
21920                let a = 1;
21921                let b = 2;
21922            }
21923        }
21924    "#
21925    .unindent();
21926
21927    let fs = FakeFs::new(cx.executor());
21928    fs.insert_file("/file.rs", Default::default()).await;
21929
21930    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21931    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21932    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21933    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21934    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21935
21936    let editor = cx.new_window_entity(|window, cx| {
21937        Editor::new(
21938            EditorMode::full(),
21939            multi_buffer,
21940            Some(project.clone()),
21941            window,
21942            cx,
21943        )
21944    });
21945
21946    editor.update_in(cx, |editor, window, cx| {
21947        let snapshot = editor.buffer().read(cx).snapshot(cx);
21948        editor.tasks.insert(
21949            (buffer.read(cx).remote_id(), 3),
21950            RunnableTasks {
21951                templates: vec![],
21952                offset: snapshot.anchor_before(43),
21953                column: 0,
21954                extra_variables: HashMap::default(),
21955                context_range: BufferOffset(43)..BufferOffset(85),
21956            },
21957        );
21958        editor.tasks.insert(
21959            (buffer.read(cx).remote_id(), 8),
21960            RunnableTasks {
21961                templates: vec![],
21962                offset: snapshot.anchor_before(86),
21963                column: 0,
21964                extra_variables: HashMap::default(),
21965                context_range: BufferOffset(86)..BufferOffset(191),
21966            },
21967        );
21968
21969        // Test finding task when cursor is inside function body
21970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21971            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21972        });
21973        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21974        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21975
21976        // Test finding task when cursor is on function name
21977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21978            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21979        });
21980        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21981        assert_eq!(row, 8, "Should find task when cursor is on function name");
21982    });
21983}
21984
21985#[gpui::test]
21986async fn test_folding_buffers(cx: &mut TestAppContext) {
21987    init_test(cx, |_| {});
21988
21989    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21990    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21991    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21992
21993    let fs = FakeFs::new(cx.executor());
21994    fs.insert_tree(
21995        path!("/a"),
21996        json!({
21997            "first.rs": sample_text_1,
21998            "second.rs": sample_text_2,
21999            "third.rs": sample_text_3,
22000        }),
22001    )
22002    .await;
22003    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22004    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22005    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22006    let worktree = project.update(cx, |project, cx| {
22007        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22008        assert_eq!(worktrees.len(), 1);
22009        worktrees.pop().unwrap()
22010    });
22011    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22012
22013    let buffer_1 = project
22014        .update(cx, |project, cx| {
22015            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22016        })
22017        .await
22018        .unwrap();
22019    let buffer_2 = project
22020        .update(cx, |project, cx| {
22021            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22022        })
22023        .await
22024        .unwrap();
22025    let buffer_3 = project
22026        .update(cx, |project, cx| {
22027            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22028        })
22029        .await
22030        .unwrap();
22031
22032    let multi_buffer = cx.new(|cx| {
22033        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22034        multi_buffer.push_excerpts(
22035            buffer_1.clone(),
22036            [
22037                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22038                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22039                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22040            ],
22041            cx,
22042        );
22043        multi_buffer.push_excerpts(
22044            buffer_2.clone(),
22045            [
22046                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22047                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22048                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22049            ],
22050            cx,
22051        );
22052        multi_buffer.push_excerpts(
22053            buffer_3.clone(),
22054            [
22055                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22056                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22057                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22058            ],
22059            cx,
22060        );
22061        multi_buffer
22062    });
22063    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22064        Editor::new(
22065            EditorMode::full(),
22066            multi_buffer.clone(),
22067            Some(project.clone()),
22068            window,
22069            cx,
22070        )
22071    });
22072
22073    assert_eq!(
22074        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22075        "\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",
22076    );
22077
22078    multi_buffer_editor.update(cx, |editor, cx| {
22079        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22080    });
22081    assert_eq!(
22082        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22083        "\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",
22084        "After folding the first buffer, its text should not be displayed"
22085    );
22086
22087    multi_buffer_editor.update(cx, |editor, cx| {
22088        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22089    });
22090    assert_eq!(
22091        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22092        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22093        "After folding the second buffer, its text should not be displayed"
22094    );
22095
22096    multi_buffer_editor.update(cx, |editor, cx| {
22097        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22098    });
22099    assert_eq!(
22100        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22101        "\n\n\n\n\n",
22102        "After folding the third buffer, its text should not be displayed"
22103    );
22104
22105    // Emulate selection inside the fold logic, that should work
22106    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22107        editor
22108            .snapshot(window, cx)
22109            .next_line_boundary(Point::new(0, 4));
22110    });
22111
22112    multi_buffer_editor.update(cx, |editor, cx| {
22113        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22114    });
22115    assert_eq!(
22116        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22117        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22118        "After unfolding the second buffer, its text should be displayed"
22119    );
22120
22121    // Typing inside of buffer 1 causes that buffer to be unfolded.
22122    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22123        assert_eq!(
22124            multi_buffer
22125                .read(cx)
22126                .snapshot(cx)
22127                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22128                .collect::<String>(),
22129            "bbbb"
22130        );
22131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22132            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22133        });
22134        editor.handle_input("B", window, cx);
22135    });
22136
22137    assert_eq!(
22138        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22139        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22140        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22141    );
22142
22143    multi_buffer_editor.update(cx, |editor, cx| {
22144        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22145    });
22146    assert_eq!(
22147        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22148        "\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",
22149        "After unfolding the all buffers, all original text should be displayed"
22150    );
22151}
22152
22153#[gpui::test]
22154async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22155    init_test(cx, |_| {});
22156
22157    let sample_text_1 = "1111\n2222\n3333".to_string();
22158    let sample_text_2 = "4444\n5555\n6666".to_string();
22159    let sample_text_3 = "7777\n8888\n9999".to_string();
22160
22161    let fs = FakeFs::new(cx.executor());
22162    fs.insert_tree(
22163        path!("/a"),
22164        json!({
22165            "first.rs": sample_text_1,
22166            "second.rs": sample_text_2,
22167            "third.rs": sample_text_3,
22168        }),
22169    )
22170    .await;
22171    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22172    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22173    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22174    let worktree = project.update(cx, |project, cx| {
22175        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22176        assert_eq!(worktrees.len(), 1);
22177        worktrees.pop().unwrap()
22178    });
22179    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22180
22181    let buffer_1 = project
22182        .update(cx, |project, cx| {
22183            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22184        })
22185        .await
22186        .unwrap();
22187    let buffer_2 = project
22188        .update(cx, |project, cx| {
22189            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22190        })
22191        .await
22192        .unwrap();
22193    let buffer_3 = project
22194        .update(cx, |project, cx| {
22195            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22196        })
22197        .await
22198        .unwrap();
22199
22200    let multi_buffer = cx.new(|cx| {
22201        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22202        multi_buffer.push_excerpts(
22203            buffer_1.clone(),
22204            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22205            cx,
22206        );
22207        multi_buffer.push_excerpts(
22208            buffer_2.clone(),
22209            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22210            cx,
22211        );
22212        multi_buffer.push_excerpts(
22213            buffer_3.clone(),
22214            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22215            cx,
22216        );
22217        multi_buffer
22218    });
22219
22220    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22221        Editor::new(
22222            EditorMode::full(),
22223            multi_buffer,
22224            Some(project.clone()),
22225            window,
22226            cx,
22227        )
22228    });
22229
22230    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22231    assert_eq!(
22232        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22233        full_text,
22234    );
22235
22236    multi_buffer_editor.update(cx, |editor, cx| {
22237        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22238    });
22239    assert_eq!(
22240        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22241        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22242        "After folding the first buffer, its text should not be displayed"
22243    );
22244
22245    multi_buffer_editor.update(cx, |editor, cx| {
22246        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22247    });
22248
22249    assert_eq!(
22250        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22251        "\n\n\n\n\n\n7777\n8888\n9999",
22252        "After folding the second buffer, its text should not be displayed"
22253    );
22254
22255    multi_buffer_editor.update(cx, |editor, cx| {
22256        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22257    });
22258    assert_eq!(
22259        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22260        "\n\n\n\n\n",
22261        "After folding the third buffer, its text should not be displayed"
22262    );
22263
22264    multi_buffer_editor.update(cx, |editor, cx| {
22265        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22266    });
22267    assert_eq!(
22268        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22269        "\n\n\n\n4444\n5555\n6666\n\n",
22270        "After unfolding the second buffer, its text should be displayed"
22271    );
22272
22273    multi_buffer_editor.update(cx, |editor, cx| {
22274        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22275    });
22276    assert_eq!(
22277        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22278        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22279        "After unfolding the first buffer, its text should be displayed"
22280    );
22281
22282    multi_buffer_editor.update(cx, |editor, cx| {
22283        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22284    });
22285    assert_eq!(
22286        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22287        full_text,
22288        "After unfolding all buffers, all original text should be displayed"
22289    );
22290}
22291
22292#[gpui::test]
22293async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22294    init_test(cx, |_| {});
22295
22296    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22297
22298    let fs = FakeFs::new(cx.executor());
22299    fs.insert_tree(
22300        path!("/a"),
22301        json!({
22302            "main.rs": sample_text,
22303        }),
22304    )
22305    .await;
22306    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22307    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22308    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22309    let worktree = project.update(cx, |project, cx| {
22310        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22311        assert_eq!(worktrees.len(), 1);
22312        worktrees.pop().unwrap()
22313    });
22314    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22315
22316    let buffer_1 = project
22317        .update(cx, |project, cx| {
22318            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22319        })
22320        .await
22321        .unwrap();
22322
22323    let multi_buffer = cx.new(|cx| {
22324        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22325        multi_buffer.push_excerpts(
22326            buffer_1.clone(),
22327            [ExcerptRange::new(
22328                Point::new(0, 0)
22329                    ..Point::new(
22330                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22331                        0,
22332                    ),
22333            )],
22334            cx,
22335        );
22336        multi_buffer
22337    });
22338    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22339        Editor::new(
22340            EditorMode::full(),
22341            multi_buffer,
22342            Some(project.clone()),
22343            window,
22344            cx,
22345        )
22346    });
22347
22348    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22349    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22350        enum TestHighlight {}
22351        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22352        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22353        editor.highlight_text::<TestHighlight>(
22354            vec![highlight_range.clone()],
22355            HighlightStyle::color(Hsla::green()),
22356            cx,
22357        );
22358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22359            s.select_ranges(Some(highlight_range))
22360        });
22361    });
22362
22363    let full_text = format!("\n\n{sample_text}");
22364    assert_eq!(
22365        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22366        full_text,
22367    );
22368}
22369
22370#[gpui::test]
22371async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22372    init_test(cx, |_| {});
22373    cx.update(|cx| {
22374        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22375            "keymaps/default-linux.json",
22376            cx,
22377        )
22378        .unwrap();
22379        cx.bind_keys(default_key_bindings);
22380    });
22381
22382    let (editor, cx) = cx.add_window_view(|window, cx| {
22383        let multi_buffer = MultiBuffer::build_multi(
22384            [
22385                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22386                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22387                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22388                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22389            ],
22390            cx,
22391        );
22392        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22393
22394        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22395        // fold all but the second buffer, so that we test navigating between two
22396        // adjacent folded buffers, as well as folded buffers at the start and
22397        // end the multibuffer
22398        editor.fold_buffer(buffer_ids[0], cx);
22399        editor.fold_buffer(buffer_ids[2], cx);
22400        editor.fold_buffer(buffer_ids[3], cx);
22401
22402        editor
22403    });
22404    cx.simulate_resize(size(px(1000.), px(1000.)));
22405
22406    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22407    cx.assert_excerpts_with_selections(indoc! {"
22408        [EXCERPT]
22409        ˇ[FOLDED]
22410        [EXCERPT]
22411        a1
22412        b1
22413        [EXCERPT]
22414        [FOLDED]
22415        [EXCERPT]
22416        [FOLDED]
22417        "
22418    });
22419    cx.simulate_keystroke("down");
22420    cx.assert_excerpts_with_selections(indoc! {"
22421        [EXCERPT]
22422        [FOLDED]
22423        [EXCERPT]
22424        ˇa1
22425        b1
22426        [EXCERPT]
22427        [FOLDED]
22428        [EXCERPT]
22429        [FOLDED]
22430        "
22431    });
22432    cx.simulate_keystroke("down");
22433    cx.assert_excerpts_with_selections(indoc! {"
22434        [EXCERPT]
22435        [FOLDED]
22436        [EXCERPT]
22437        a1
22438        ˇb1
22439        [EXCERPT]
22440        [FOLDED]
22441        [EXCERPT]
22442        [FOLDED]
22443        "
22444    });
22445    cx.simulate_keystroke("down");
22446    cx.assert_excerpts_with_selections(indoc! {"
22447        [EXCERPT]
22448        [FOLDED]
22449        [EXCERPT]
22450        a1
22451        b1
22452        ˇ[EXCERPT]
22453        [FOLDED]
22454        [EXCERPT]
22455        [FOLDED]
22456        "
22457    });
22458    cx.simulate_keystroke("down");
22459    cx.assert_excerpts_with_selections(indoc! {"
22460        [EXCERPT]
22461        [FOLDED]
22462        [EXCERPT]
22463        a1
22464        b1
22465        [EXCERPT]
22466        ˇ[FOLDED]
22467        [EXCERPT]
22468        [FOLDED]
22469        "
22470    });
22471    for _ in 0..5 {
22472        cx.simulate_keystroke("down");
22473        cx.assert_excerpts_with_selections(indoc! {"
22474            [EXCERPT]
22475            [FOLDED]
22476            [EXCERPT]
22477            a1
22478            b1
22479            [EXCERPT]
22480            [FOLDED]
22481            [EXCERPT]
22482            ˇ[FOLDED]
22483            "
22484        });
22485    }
22486
22487    cx.simulate_keystroke("up");
22488    cx.assert_excerpts_with_selections(indoc! {"
22489        [EXCERPT]
22490        [FOLDED]
22491        [EXCERPT]
22492        a1
22493        b1
22494        [EXCERPT]
22495        ˇ[FOLDED]
22496        [EXCERPT]
22497        [FOLDED]
22498        "
22499    });
22500    cx.simulate_keystroke("up");
22501    cx.assert_excerpts_with_selections(indoc! {"
22502        [EXCERPT]
22503        [FOLDED]
22504        [EXCERPT]
22505        a1
22506        b1
22507        ˇ[EXCERPT]
22508        [FOLDED]
22509        [EXCERPT]
22510        [FOLDED]
22511        "
22512    });
22513    cx.simulate_keystroke("up");
22514    cx.assert_excerpts_with_selections(indoc! {"
22515        [EXCERPT]
22516        [FOLDED]
22517        [EXCERPT]
22518        a1
22519        ˇb1
22520        [EXCERPT]
22521        [FOLDED]
22522        [EXCERPT]
22523        [FOLDED]
22524        "
22525    });
22526    cx.simulate_keystroke("up");
22527    cx.assert_excerpts_with_selections(indoc! {"
22528        [EXCERPT]
22529        [FOLDED]
22530        [EXCERPT]
22531        ˇa1
22532        b1
22533        [EXCERPT]
22534        [FOLDED]
22535        [EXCERPT]
22536        [FOLDED]
22537        "
22538    });
22539    for _ in 0..5 {
22540        cx.simulate_keystroke("up");
22541        cx.assert_excerpts_with_selections(indoc! {"
22542            [EXCERPT]
22543            ˇ[FOLDED]
22544            [EXCERPT]
22545            a1
22546            b1
22547            [EXCERPT]
22548            [FOLDED]
22549            [EXCERPT]
22550            [FOLDED]
22551            "
22552        });
22553    }
22554}
22555
22556#[gpui::test]
22557async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22558    init_test(cx, |_| {});
22559
22560    // Simple insertion
22561    assert_highlighted_edits(
22562        "Hello, world!",
22563        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22564        true,
22565        cx,
22566        |highlighted_edits, cx| {
22567            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22568            assert_eq!(highlighted_edits.highlights.len(), 1);
22569            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22570            assert_eq!(
22571                highlighted_edits.highlights[0].1.background_color,
22572                Some(cx.theme().status().created_background)
22573            );
22574        },
22575    )
22576    .await;
22577
22578    // Replacement
22579    assert_highlighted_edits(
22580        "This is a test.",
22581        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22582        false,
22583        cx,
22584        |highlighted_edits, cx| {
22585            assert_eq!(highlighted_edits.text, "That is a test.");
22586            assert_eq!(highlighted_edits.highlights.len(), 1);
22587            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22588            assert_eq!(
22589                highlighted_edits.highlights[0].1.background_color,
22590                Some(cx.theme().status().created_background)
22591            );
22592        },
22593    )
22594    .await;
22595
22596    // Multiple edits
22597    assert_highlighted_edits(
22598        "Hello, world!",
22599        vec![
22600            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22601            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22602        ],
22603        false,
22604        cx,
22605        |highlighted_edits, cx| {
22606            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22607            assert_eq!(highlighted_edits.highlights.len(), 2);
22608            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22609            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22610            assert_eq!(
22611                highlighted_edits.highlights[0].1.background_color,
22612                Some(cx.theme().status().created_background)
22613            );
22614            assert_eq!(
22615                highlighted_edits.highlights[1].1.background_color,
22616                Some(cx.theme().status().created_background)
22617            );
22618        },
22619    )
22620    .await;
22621
22622    // Multiple lines with edits
22623    assert_highlighted_edits(
22624        "First line\nSecond line\nThird line\nFourth line",
22625        vec![
22626            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22627            (
22628                Point::new(2, 0)..Point::new(2, 10),
22629                "New third line".to_string(),
22630            ),
22631            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22632        ],
22633        false,
22634        cx,
22635        |highlighted_edits, cx| {
22636            assert_eq!(
22637                highlighted_edits.text,
22638                "Second modified\nNew third line\nFourth updated line"
22639            );
22640            assert_eq!(highlighted_edits.highlights.len(), 3);
22641            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22642            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22643            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22644            for highlight in &highlighted_edits.highlights {
22645                assert_eq!(
22646                    highlight.1.background_color,
22647                    Some(cx.theme().status().created_background)
22648                );
22649            }
22650        },
22651    )
22652    .await;
22653}
22654
22655#[gpui::test]
22656async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22657    init_test(cx, |_| {});
22658
22659    // Deletion
22660    assert_highlighted_edits(
22661        "Hello, world!",
22662        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22663        true,
22664        cx,
22665        |highlighted_edits, cx| {
22666            assert_eq!(highlighted_edits.text, "Hello, world!");
22667            assert_eq!(highlighted_edits.highlights.len(), 1);
22668            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22669            assert_eq!(
22670                highlighted_edits.highlights[0].1.background_color,
22671                Some(cx.theme().status().deleted_background)
22672            );
22673        },
22674    )
22675    .await;
22676
22677    // Insertion
22678    assert_highlighted_edits(
22679        "Hello, world!",
22680        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22681        true,
22682        cx,
22683        |highlighted_edits, cx| {
22684            assert_eq!(highlighted_edits.highlights.len(), 1);
22685            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22686            assert_eq!(
22687                highlighted_edits.highlights[0].1.background_color,
22688                Some(cx.theme().status().created_background)
22689            );
22690        },
22691    )
22692    .await;
22693}
22694
22695async fn assert_highlighted_edits(
22696    text: &str,
22697    edits: Vec<(Range<Point>, String)>,
22698    include_deletions: bool,
22699    cx: &mut TestAppContext,
22700    assertion_fn: impl Fn(HighlightedText, &App),
22701) {
22702    let window = cx.add_window(|window, cx| {
22703        let buffer = MultiBuffer::build_simple(text, cx);
22704        Editor::new(EditorMode::full(), buffer, None, window, cx)
22705    });
22706    let cx = &mut VisualTestContext::from_window(*window, cx);
22707
22708    let (buffer, snapshot) = window
22709        .update(cx, |editor, _window, cx| {
22710            (
22711                editor.buffer().clone(),
22712                editor.buffer().read(cx).snapshot(cx),
22713            )
22714        })
22715        .unwrap();
22716
22717    let edits = edits
22718        .into_iter()
22719        .map(|(range, edit)| {
22720            (
22721                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22722                edit,
22723            )
22724        })
22725        .collect::<Vec<_>>();
22726
22727    let text_anchor_edits = edits
22728        .clone()
22729        .into_iter()
22730        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22731        .collect::<Vec<_>>();
22732
22733    let edit_preview = window
22734        .update(cx, |_, _window, cx| {
22735            buffer
22736                .read(cx)
22737                .as_singleton()
22738                .unwrap()
22739                .read(cx)
22740                .preview_edits(text_anchor_edits.into(), cx)
22741        })
22742        .unwrap()
22743        .await;
22744
22745    cx.update(|_window, cx| {
22746        let highlighted_edits = edit_prediction_edit_text(
22747            snapshot.as_singleton().unwrap().2,
22748            &edits,
22749            &edit_preview,
22750            include_deletions,
22751            cx,
22752        );
22753        assertion_fn(highlighted_edits, cx)
22754    });
22755}
22756
22757#[track_caller]
22758fn assert_breakpoint(
22759    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22760    path: &Arc<Path>,
22761    expected: Vec<(u32, Breakpoint)>,
22762) {
22763    if expected.is_empty() {
22764        assert!(!breakpoints.contains_key(path), "{}", path.display());
22765    } else {
22766        let mut breakpoint = breakpoints
22767            .get(path)
22768            .unwrap()
22769            .iter()
22770            .map(|breakpoint| {
22771                (
22772                    breakpoint.row,
22773                    Breakpoint {
22774                        message: breakpoint.message.clone(),
22775                        state: breakpoint.state,
22776                        condition: breakpoint.condition.clone(),
22777                        hit_condition: breakpoint.hit_condition.clone(),
22778                    },
22779                )
22780            })
22781            .collect::<Vec<_>>();
22782
22783        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22784
22785        assert_eq!(expected, breakpoint);
22786    }
22787}
22788
22789fn add_log_breakpoint_at_cursor(
22790    editor: &mut Editor,
22791    log_message: &str,
22792    window: &mut Window,
22793    cx: &mut Context<Editor>,
22794) {
22795    let (anchor, bp) = editor
22796        .breakpoints_at_cursors(window, cx)
22797        .first()
22798        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22799        .unwrap_or_else(|| {
22800            let snapshot = editor.snapshot(window, cx);
22801            let cursor_position: Point =
22802                editor.selections.newest(&snapshot.display_snapshot).head();
22803
22804            let breakpoint_position = snapshot
22805                .buffer_snapshot()
22806                .anchor_before(Point::new(cursor_position.row, 0));
22807
22808            (breakpoint_position, Breakpoint::new_log(log_message))
22809        });
22810
22811    editor.edit_breakpoint_at_anchor(
22812        anchor,
22813        bp,
22814        BreakpointEditAction::EditLogMessage(log_message.into()),
22815        cx,
22816    );
22817}
22818
22819#[gpui::test]
22820async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22821    init_test(cx, |_| {});
22822
22823    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22824    let fs = FakeFs::new(cx.executor());
22825    fs.insert_tree(
22826        path!("/a"),
22827        json!({
22828            "main.rs": sample_text,
22829        }),
22830    )
22831    .await;
22832    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22833    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22834    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22835
22836    let fs = FakeFs::new(cx.executor());
22837    fs.insert_tree(
22838        path!("/a"),
22839        json!({
22840            "main.rs": sample_text,
22841        }),
22842    )
22843    .await;
22844    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22845    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22846    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22847    let worktree_id = workspace
22848        .update(cx, |workspace, _window, cx| {
22849            workspace.project().update(cx, |project, cx| {
22850                project.worktrees(cx).next().unwrap().read(cx).id()
22851            })
22852        })
22853        .unwrap();
22854
22855    let buffer = project
22856        .update(cx, |project, cx| {
22857            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22858        })
22859        .await
22860        .unwrap();
22861
22862    let (editor, cx) = cx.add_window_view(|window, cx| {
22863        Editor::new(
22864            EditorMode::full(),
22865            MultiBuffer::build_from_buffer(buffer, cx),
22866            Some(project.clone()),
22867            window,
22868            cx,
22869        )
22870    });
22871
22872    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22873    let abs_path = project.read_with(cx, |project, cx| {
22874        project
22875            .absolute_path(&project_path, cx)
22876            .map(Arc::from)
22877            .unwrap()
22878    });
22879
22880    // assert we can add breakpoint on the first line
22881    editor.update_in(cx, |editor, window, cx| {
22882        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22883        editor.move_to_end(&MoveToEnd, window, cx);
22884        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22885    });
22886
22887    let breakpoints = editor.update(cx, |editor, cx| {
22888        editor
22889            .breakpoint_store()
22890            .as_ref()
22891            .unwrap()
22892            .read(cx)
22893            .all_source_breakpoints(cx)
22894    });
22895
22896    assert_eq!(1, breakpoints.len());
22897    assert_breakpoint(
22898        &breakpoints,
22899        &abs_path,
22900        vec![
22901            (0, Breakpoint::new_standard()),
22902            (3, Breakpoint::new_standard()),
22903        ],
22904    );
22905
22906    editor.update_in(cx, |editor, window, cx| {
22907        editor.move_to_beginning(&MoveToBeginning, window, cx);
22908        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22909    });
22910
22911    let breakpoints = editor.update(cx, |editor, cx| {
22912        editor
22913            .breakpoint_store()
22914            .as_ref()
22915            .unwrap()
22916            .read(cx)
22917            .all_source_breakpoints(cx)
22918    });
22919
22920    assert_eq!(1, breakpoints.len());
22921    assert_breakpoint(
22922        &breakpoints,
22923        &abs_path,
22924        vec![(3, Breakpoint::new_standard())],
22925    );
22926
22927    editor.update_in(cx, |editor, window, cx| {
22928        editor.move_to_end(&MoveToEnd, window, cx);
22929        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22930    });
22931
22932    let breakpoints = editor.update(cx, |editor, cx| {
22933        editor
22934            .breakpoint_store()
22935            .as_ref()
22936            .unwrap()
22937            .read(cx)
22938            .all_source_breakpoints(cx)
22939    });
22940
22941    assert_eq!(0, breakpoints.len());
22942    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22943}
22944
22945#[gpui::test]
22946async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22947    init_test(cx, |_| {});
22948
22949    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22950
22951    let fs = FakeFs::new(cx.executor());
22952    fs.insert_tree(
22953        path!("/a"),
22954        json!({
22955            "main.rs": sample_text,
22956        }),
22957    )
22958    .await;
22959    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22960    let (workspace, cx) =
22961        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22962
22963    let worktree_id = workspace.update(cx, |workspace, cx| {
22964        workspace.project().update(cx, |project, cx| {
22965            project.worktrees(cx).next().unwrap().read(cx).id()
22966        })
22967    });
22968
22969    let buffer = project
22970        .update(cx, |project, cx| {
22971            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22972        })
22973        .await
22974        .unwrap();
22975
22976    let (editor, cx) = cx.add_window_view(|window, cx| {
22977        Editor::new(
22978            EditorMode::full(),
22979            MultiBuffer::build_from_buffer(buffer, cx),
22980            Some(project.clone()),
22981            window,
22982            cx,
22983        )
22984    });
22985
22986    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22987    let abs_path = project.read_with(cx, |project, cx| {
22988        project
22989            .absolute_path(&project_path, cx)
22990            .map(Arc::from)
22991            .unwrap()
22992    });
22993
22994    editor.update_in(cx, |editor, window, cx| {
22995        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22996    });
22997
22998    let breakpoints = editor.update(cx, |editor, cx| {
22999        editor
23000            .breakpoint_store()
23001            .as_ref()
23002            .unwrap()
23003            .read(cx)
23004            .all_source_breakpoints(cx)
23005    });
23006
23007    assert_breakpoint(
23008        &breakpoints,
23009        &abs_path,
23010        vec![(0, Breakpoint::new_log("hello world"))],
23011    );
23012
23013    // Removing a log message from a log breakpoint should remove it
23014    editor.update_in(cx, |editor, window, cx| {
23015        add_log_breakpoint_at_cursor(editor, "", window, cx);
23016    });
23017
23018    let breakpoints = editor.update(cx, |editor, cx| {
23019        editor
23020            .breakpoint_store()
23021            .as_ref()
23022            .unwrap()
23023            .read(cx)
23024            .all_source_breakpoints(cx)
23025    });
23026
23027    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23028
23029    editor.update_in(cx, |editor, window, cx| {
23030        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23031        editor.move_to_end(&MoveToEnd, window, cx);
23032        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23033        // Not adding a log message to a standard breakpoint shouldn't remove it
23034        add_log_breakpoint_at_cursor(editor, "", window, cx);
23035    });
23036
23037    let breakpoints = editor.update(cx, |editor, cx| {
23038        editor
23039            .breakpoint_store()
23040            .as_ref()
23041            .unwrap()
23042            .read(cx)
23043            .all_source_breakpoints(cx)
23044    });
23045
23046    assert_breakpoint(
23047        &breakpoints,
23048        &abs_path,
23049        vec![
23050            (0, Breakpoint::new_standard()),
23051            (3, Breakpoint::new_standard()),
23052        ],
23053    );
23054
23055    editor.update_in(cx, |editor, window, cx| {
23056        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23057    });
23058
23059    let breakpoints = editor.update(cx, |editor, cx| {
23060        editor
23061            .breakpoint_store()
23062            .as_ref()
23063            .unwrap()
23064            .read(cx)
23065            .all_source_breakpoints(cx)
23066    });
23067
23068    assert_breakpoint(
23069        &breakpoints,
23070        &abs_path,
23071        vec![
23072            (0, Breakpoint::new_standard()),
23073            (3, Breakpoint::new_log("hello world")),
23074        ],
23075    );
23076
23077    editor.update_in(cx, |editor, window, cx| {
23078        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23079    });
23080
23081    let breakpoints = editor.update(cx, |editor, cx| {
23082        editor
23083            .breakpoint_store()
23084            .as_ref()
23085            .unwrap()
23086            .read(cx)
23087            .all_source_breakpoints(cx)
23088    });
23089
23090    assert_breakpoint(
23091        &breakpoints,
23092        &abs_path,
23093        vec![
23094            (0, Breakpoint::new_standard()),
23095            (3, Breakpoint::new_log("hello Earth!!")),
23096        ],
23097    );
23098}
23099
23100/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23101/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23102/// or when breakpoints were placed out of order. This tests for a regression too
23103#[gpui::test]
23104async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23105    init_test(cx, |_| {});
23106
23107    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23108    let fs = FakeFs::new(cx.executor());
23109    fs.insert_tree(
23110        path!("/a"),
23111        json!({
23112            "main.rs": sample_text,
23113        }),
23114    )
23115    .await;
23116    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23117    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23118    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23119
23120    let fs = FakeFs::new(cx.executor());
23121    fs.insert_tree(
23122        path!("/a"),
23123        json!({
23124            "main.rs": sample_text,
23125        }),
23126    )
23127    .await;
23128    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23129    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23130    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23131    let worktree_id = workspace
23132        .update(cx, |workspace, _window, cx| {
23133            workspace.project().update(cx, |project, cx| {
23134                project.worktrees(cx).next().unwrap().read(cx).id()
23135            })
23136        })
23137        .unwrap();
23138
23139    let buffer = project
23140        .update(cx, |project, cx| {
23141            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23142        })
23143        .await
23144        .unwrap();
23145
23146    let (editor, cx) = cx.add_window_view(|window, cx| {
23147        Editor::new(
23148            EditorMode::full(),
23149            MultiBuffer::build_from_buffer(buffer, cx),
23150            Some(project.clone()),
23151            window,
23152            cx,
23153        )
23154    });
23155
23156    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23157    let abs_path = project.read_with(cx, |project, cx| {
23158        project
23159            .absolute_path(&project_path, cx)
23160            .map(Arc::from)
23161            .unwrap()
23162    });
23163
23164    // assert we can add breakpoint on the first line
23165    editor.update_in(cx, |editor, window, cx| {
23166        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23167        editor.move_to_end(&MoveToEnd, window, cx);
23168        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23169        editor.move_up(&MoveUp, window, cx);
23170        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23171    });
23172
23173    let breakpoints = editor.update(cx, |editor, cx| {
23174        editor
23175            .breakpoint_store()
23176            .as_ref()
23177            .unwrap()
23178            .read(cx)
23179            .all_source_breakpoints(cx)
23180    });
23181
23182    assert_eq!(1, breakpoints.len());
23183    assert_breakpoint(
23184        &breakpoints,
23185        &abs_path,
23186        vec![
23187            (0, Breakpoint::new_standard()),
23188            (2, Breakpoint::new_standard()),
23189            (3, Breakpoint::new_standard()),
23190        ],
23191    );
23192
23193    editor.update_in(cx, |editor, window, cx| {
23194        editor.move_to_beginning(&MoveToBeginning, window, cx);
23195        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23196        editor.move_to_end(&MoveToEnd, window, cx);
23197        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23198        // Disabling a breakpoint that doesn't exist should do nothing
23199        editor.move_up(&MoveUp, window, cx);
23200        editor.move_up(&MoveUp, window, cx);
23201        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23202    });
23203
23204    let breakpoints = editor.update(cx, |editor, cx| {
23205        editor
23206            .breakpoint_store()
23207            .as_ref()
23208            .unwrap()
23209            .read(cx)
23210            .all_source_breakpoints(cx)
23211    });
23212
23213    let disable_breakpoint = {
23214        let mut bp = Breakpoint::new_standard();
23215        bp.state = BreakpointState::Disabled;
23216        bp
23217    };
23218
23219    assert_eq!(1, breakpoints.len());
23220    assert_breakpoint(
23221        &breakpoints,
23222        &abs_path,
23223        vec![
23224            (0, disable_breakpoint.clone()),
23225            (2, Breakpoint::new_standard()),
23226            (3, disable_breakpoint.clone()),
23227        ],
23228    );
23229
23230    editor.update_in(cx, |editor, window, cx| {
23231        editor.move_to_beginning(&MoveToBeginning, window, cx);
23232        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23233        editor.move_to_end(&MoveToEnd, window, cx);
23234        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23235        editor.move_up(&MoveUp, window, cx);
23236        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23237    });
23238
23239    let breakpoints = editor.update(cx, |editor, cx| {
23240        editor
23241            .breakpoint_store()
23242            .as_ref()
23243            .unwrap()
23244            .read(cx)
23245            .all_source_breakpoints(cx)
23246    });
23247
23248    assert_eq!(1, breakpoints.len());
23249    assert_breakpoint(
23250        &breakpoints,
23251        &abs_path,
23252        vec![
23253            (0, Breakpoint::new_standard()),
23254            (2, disable_breakpoint),
23255            (3, Breakpoint::new_standard()),
23256        ],
23257    );
23258}
23259
23260#[gpui::test]
23261async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23262    init_test(cx, |_| {});
23263    let capabilities = lsp::ServerCapabilities {
23264        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23265            prepare_provider: Some(true),
23266            work_done_progress_options: Default::default(),
23267        })),
23268        ..Default::default()
23269    };
23270    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23271
23272    cx.set_state(indoc! {"
23273        struct Fˇoo {}
23274    "});
23275
23276    cx.update_editor(|editor, _, cx| {
23277        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23278        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23279        editor.highlight_background::<DocumentHighlightRead>(
23280            &[highlight_range],
23281            |theme| theme.colors().editor_document_highlight_read_background,
23282            cx,
23283        );
23284    });
23285
23286    let mut prepare_rename_handler = cx
23287        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23288            move |_, _, _| async move {
23289                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23290                    start: lsp::Position {
23291                        line: 0,
23292                        character: 7,
23293                    },
23294                    end: lsp::Position {
23295                        line: 0,
23296                        character: 10,
23297                    },
23298                })))
23299            },
23300        );
23301    let prepare_rename_task = cx
23302        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23303        .expect("Prepare rename was not started");
23304    prepare_rename_handler.next().await.unwrap();
23305    prepare_rename_task.await.expect("Prepare rename failed");
23306
23307    let mut rename_handler =
23308        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23309            let edit = lsp::TextEdit {
23310                range: lsp::Range {
23311                    start: lsp::Position {
23312                        line: 0,
23313                        character: 7,
23314                    },
23315                    end: lsp::Position {
23316                        line: 0,
23317                        character: 10,
23318                    },
23319                },
23320                new_text: "FooRenamed".to_string(),
23321            };
23322            Ok(Some(lsp::WorkspaceEdit::new(
23323                // Specify the same edit twice
23324                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23325            )))
23326        });
23327    let rename_task = cx
23328        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23329        .expect("Confirm rename was not started");
23330    rename_handler.next().await.unwrap();
23331    rename_task.await.expect("Confirm rename failed");
23332    cx.run_until_parked();
23333
23334    // Despite two edits, only one is actually applied as those are identical
23335    cx.assert_editor_state(indoc! {"
23336        struct FooRenamedˇ {}
23337    "});
23338}
23339
23340#[gpui::test]
23341async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23342    init_test(cx, |_| {});
23343    // These capabilities indicate that the server does not support prepare rename.
23344    let capabilities = lsp::ServerCapabilities {
23345        rename_provider: Some(lsp::OneOf::Left(true)),
23346        ..Default::default()
23347    };
23348    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23349
23350    cx.set_state(indoc! {"
23351        struct Fˇoo {}
23352    "});
23353
23354    cx.update_editor(|editor, _window, cx| {
23355        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23356        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23357        editor.highlight_background::<DocumentHighlightRead>(
23358            &[highlight_range],
23359            |theme| theme.colors().editor_document_highlight_read_background,
23360            cx,
23361        );
23362    });
23363
23364    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23365        .expect("Prepare rename was not started")
23366        .await
23367        .expect("Prepare rename failed");
23368
23369    let mut rename_handler =
23370        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23371            let edit = lsp::TextEdit {
23372                range: lsp::Range {
23373                    start: lsp::Position {
23374                        line: 0,
23375                        character: 7,
23376                    },
23377                    end: lsp::Position {
23378                        line: 0,
23379                        character: 10,
23380                    },
23381                },
23382                new_text: "FooRenamed".to_string(),
23383            };
23384            Ok(Some(lsp::WorkspaceEdit::new(
23385                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23386            )))
23387        });
23388    let rename_task = cx
23389        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23390        .expect("Confirm rename was not started");
23391    rename_handler.next().await.unwrap();
23392    rename_task.await.expect("Confirm rename failed");
23393    cx.run_until_parked();
23394
23395    // Correct range is renamed, as `surrounding_word` is used to find it.
23396    cx.assert_editor_state(indoc! {"
23397        struct FooRenamedˇ {}
23398    "});
23399}
23400
23401#[gpui::test]
23402async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23403    init_test(cx, |_| {});
23404    let mut cx = EditorTestContext::new(cx).await;
23405
23406    let language = Arc::new(
23407        Language::new(
23408            LanguageConfig::default(),
23409            Some(tree_sitter_html::LANGUAGE.into()),
23410        )
23411        .with_brackets_query(
23412            r#"
23413            ("<" @open "/>" @close)
23414            ("</" @open ">" @close)
23415            ("<" @open ">" @close)
23416            ("\"" @open "\"" @close)
23417            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23418        "#,
23419        )
23420        .unwrap(),
23421    );
23422    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23423
23424    cx.set_state(indoc! {"
23425        <span>ˇ</span>
23426    "});
23427    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23428    cx.assert_editor_state(indoc! {"
23429        <span>
23430        ˇ
23431        </span>
23432    "});
23433
23434    cx.set_state(indoc! {"
23435        <span><span></span>ˇ</span>
23436    "});
23437    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23438    cx.assert_editor_state(indoc! {"
23439        <span><span></span>
23440        ˇ</span>
23441    "});
23442
23443    cx.set_state(indoc! {"
23444        <span>ˇ
23445        </span>
23446    "});
23447    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23448    cx.assert_editor_state(indoc! {"
23449        <span>
23450        ˇ
23451        </span>
23452    "});
23453}
23454
23455#[gpui::test(iterations = 10)]
23456async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23457    init_test(cx, |_| {});
23458
23459    let fs = FakeFs::new(cx.executor());
23460    fs.insert_tree(
23461        path!("/dir"),
23462        json!({
23463            "a.ts": "a",
23464        }),
23465    )
23466    .await;
23467
23468    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23469    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23470    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23471
23472    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23473    language_registry.add(Arc::new(Language::new(
23474        LanguageConfig {
23475            name: "TypeScript".into(),
23476            matcher: LanguageMatcher {
23477                path_suffixes: vec!["ts".to_string()],
23478                ..Default::default()
23479            },
23480            ..Default::default()
23481        },
23482        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23483    )));
23484    let mut fake_language_servers = language_registry.register_fake_lsp(
23485        "TypeScript",
23486        FakeLspAdapter {
23487            capabilities: lsp::ServerCapabilities {
23488                code_lens_provider: Some(lsp::CodeLensOptions {
23489                    resolve_provider: Some(true),
23490                }),
23491                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23492                    commands: vec!["_the/command".to_string()],
23493                    ..lsp::ExecuteCommandOptions::default()
23494                }),
23495                ..lsp::ServerCapabilities::default()
23496            },
23497            ..FakeLspAdapter::default()
23498        },
23499    );
23500
23501    let editor = workspace
23502        .update(cx, |workspace, window, cx| {
23503            workspace.open_abs_path(
23504                PathBuf::from(path!("/dir/a.ts")),
23505                OpenOptions::default(),
23506                window,
23507                cx,
23508            )
23509        })
23510        .unwrap()
23511        .await
23512        .unwrap()
23513        .downcast::<Editor>()
23514        .unwrap();
23515    cx.executor().run_until_parked();
23516
23517    let fake_server = fake_language_servers.next().await.unwrap();
23518
23519    let buffer = editor.update(cx, |editor, cx| {
23520        editor
23521            .buffer()
23522            .read(cx)
23523            .as_singleton()
23524            .expect("have opened a single file by path")
23525    });
23526
23527    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23528    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23529    drop(buffer_snapshot);
23530    let actions = cx
23531        .update_window(*workspace, |_, window, cx| {
23532            project.code_actions(&buffer, anchor..anchor, window, cx)
23533        })
23534        .unwrap();
23535
23536    fake_server
23537        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23538            Ok(Some(vec![
23539                lsp::CodeLens {
23540                    range: lsp::Range::default(),
23541                    command: Some(lsp::Command {
23542                        title: "Code lens command".to_owned(),
23543                        command: "_the/command".to_owned(),
23544                        arguments: None,
23545                    }),
23546                    data: None,
23547                },
23548                lsp::CodeLens {
23549                    range: lsp::Range::default(),
23550                    command: Some(lsp::Command {
23551                        title: "Command not in capabilities".to_owned(),
23552                        command: "not in capabilities".to_owned(),
23553                        arguments: None,
23554                    }),
23555                    data: None,
23556                },
23557                lsp::CodeLens {
23558                    range: lsp::Range {
23559                        start: lsp::Position {
23560                            line: 1,
23561                            character: 1,
23562                        },
23563                        end: lsp::Position {
23564                            line: 1,
23565                            character: 1,
23566                        },
23567                    },
23568                    command: Some(lsp::Command {
23569                        title: "Command not in range".to_owned(),
23570                        command: "_the/command".to_owned(),
23571                        arguments: None,
23572                    }),
23573                    data: None,
23574                },
23575            ]))
23576        })
23577        .next()
23578        .await;
23579
23580    let actions = actions.await.unwrap();
23581    assert_eq!(
23582        actions.len(),
23583        1,
23584        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23585    );
23586    let action = actions[0].clone();
23587    let apply = project.update(cx, |project, cx| {
23588        project.apply_code_action(buffer.clone(), action, true, cx)
23589    });
23590
23591    // Resolving the code action does not populate its edits. In absence of
23592    // edits, we must execute the given command.
23593    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23594        |mut lens, _| async move {
23595            let lens_command = lens.command.as_mut().expect("should have a command");
23596            assert_eq!(lens_command.title, "Code lens command");
23597            lens_command.arguments = Some(vec![json!("the-argument")]);
23598            Ok(lens)
23599        },
23600    );
23601
23602    // While executing the command, the language server sends the editor
23603    // a `workspaceEdit` request.
23604    fake_server
23605        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23606            let fake = fake_server.clone();
23607            move |params, _| {
23608                assert_eq!(params.command, "_the/command");
23609                let fake = fake.clone();
23610                async move {
23611                    fake.server
23612                        .request::<lsp::request::ApplyWorkspaceEdit>(
23613                            lsp::ApplyWorkspaceEditParams {
23614                                label: None,
23615                                edit: lsp::WorkspaceEdit {
23616                                    changes: Some(
23617                                        [(
23618                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23619                                            vec![lsp::TextEdit {
23620                                                range: lsp::Range::new(
23621                                                    lsp::Position::new(0, 0),
23622                                                    lsp::Position::new(0, 0),
23623                                                ),
23624                                                new_text: "X".into(),
23625                                            }],
23626                                        )]
23627                                        .into_iter()
23628                                        .collect(),
23629                                    ),
23630                                    ..lsp::WorkspaceEdit::default()
23631                                },
23632                            },
23633                        )
23634                        .await
23635                        .into_response()
23636                        .unwrap();
23637                    Ok(Some(json!(null)))
23638                }
23639            }
23640        })
23641        .next()
23642        .await;
23643
23644    // Applying the code lens command returns a project transaction containing the edits
23645    // sent by the language server in its `workspaceEdit` request.
23646    let transaction = apply.await.unwrap();
23647    assert!(transaction.0.contains_key(&buffer));
23648    buffer.update(cx, |buffer, cx| {
23649        assert_eq!(buffer.text(), "Xa");
23650        buffer.undo(cx);
23651        assert_eq!(buffer.text(), "a");
23652    });
23653
23654    let actions_after_edits = cx
23655        .update_window(*workspace, |_, window, cx| {
23656            project.code_actions(&buffer, anchor..anchor, window, cx)
23657        })
23658        .unwrap()
23659        .await
23660        .unwrap();
23661    assert_eq!(
23662        actions, actions_after_edits,
23663        "For the same selection, same code lens actions should be returned"
23664    );
23665
23666    let _responses =
23667        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23668            panic!("No more code lens requests are expected");
23669        });
23670    editor.update_in(cx, |editor, window, cx| {
23671        editor.select_all(&SelectAll, window, cx);
23672    });
23673    cx.executor().run_until_parked();
23674    let new_actions = cx
23675        .update_window(*workspace, |_, window, cx| {
23676            project.code_actions(&buffer, anchor..anchor, window, cx)
23677        })
23678        .unwrap()
23679        .await
23680        .unwrap();
23681    assert_eq!(
23682        actions, new_actions,
23683        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23684    );
23685}
23686
23687#[gpui::test]
23688async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23689    init_test(cx, |_| {});
23690
23691    let fs = FakeFs::new(cx.executor());
23692    let main_text = r#"fn main() {
23693println!("1");
23694println!("2");
23695println!("3");
23696println!("4");
23697println!("5");
23698}"#;
23699    let lib_text = "mod foo {}";
23700    fs.insert_tree(
23701        path!("/a"),
23702        json!({
23703            "lib.rs": lib_text,
23704            "main.rs": main_text,
23705        }),
23706    )
23707    .await;
23708
23709    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23710    let (workspace, cx) =
23711        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23712    let worktree_id = workspace.update(cx, |workspace, cx| {
23713        workspace.project().update(cx, |project, cx| {
23714            project.worktrees(cx).next().unwrap().read(cx).id()
23715        })
23716    });
23717
23718    let expected_ranges = vec![
23719        Point::new(0, 0)..Point::new(0, 0),
23720        Point::new(1, 0)..Point::new(1, 1),
23721        Point::new(2, 0)..Point::new(2, 2),
23722        Point::new(3, 0)..Point::new(3, 3),
23723    ];
23724
23725    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23726    let editor_1 = workspace
23727        .update_in(cx, |workspace, window, cx| {
23728            workspace.open_path(
23729                (worktree_id, rel_path("main.rs")),
23730                Some(pane_1.downgrade()),
23731                true,
23732                window,
23733                cx,
23734            )
23735        })
23736        .unwrap()
23737        .await
23738        .downcast::<Editor>()
23739        .unwrap();
23740    pane_1.update(cx, |pane, cx| {
23741        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23742        open_editor.update(cx, |editor, cx| {
23743            assert_eq!(
23744                editor.display_text(cx),
23745                main_text,
23746                "Original main.rs text on initial open",
23747            );
23748            assert_eq!(
23749                editor
23750                    .selections
23751                    .all::<Point>(&editor.display_snapshot(cx))
23752                    .into_iter()
23753                    .map(|s| s.range())
23754                    .collect::<Vec<_>>(),
23755                vec![Point::zero()..Point::zero()],
23756                "Default selections on initial open",
23757            );
23758        })
23759    });
23760    editor_1.update_in(cx, |editor, window, cx| {
23761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23762            s.select_ranges(expected_ranges.clone());
23763        });
23764    });
23765
23766    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23767        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23768    });
23769    let editor_2 = workspace
23770        .update_in(cx, |workspace, window, cx| {
23771            workspace.open_path(
23772                (worktree_id, rel_path("main.rs")),
23773                Some(pane_2.downgrade()),
23774                true,
23775                window,
23776                cx,
23777            )
23778        })
23779        .unwrap()
23780        .await
23781        .downcast::<Editor>()
23782        .unwrap();
23783    pane_2.update(cx, |pane, cx| {
23784        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23785        open_editor.update(cx, |editor, cx| {
23786            assert_eq!(
23787                editor.display_text(cx),
23788                main_text,
23789                "Original main.rs text on initial open in another panel",
23790            );
23791            assert_eq!(
23792                editor
23793                    .selections
23794                    .all::<Point>(&editor.display_snapshot(cx))
23795                    .into_iter()
23796                    .map(|s| s.range())
23797                    .collect::<Vec<_>>(),
23798                vec![Point::zero()..Point::zero()],
23799                "Default selections on initial open in another panel",
23800            );
23801        })
23802    });
23803
23804    editor_2.update_in(cx, |editor, window, cx| {
23805        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23806    });
23807
23808    let _other_editor_1 = workspace
23809        .update_in(cx, |workspace, window, cx| {
23810            workspace.open_path(
23811                (worktree_id, rel_path("lib.rs")),
23812                Some(pane_1.downgrade()),
23813                true,
23814                window,
23815                cx,
23816            )
23817        })
23818        .unwrap()
23819        .await
23820        .downcast::<Editor>()
23821        .unwrap();
23822    pane_1
23823        .update_in(cx, |pane, window, cx| {
23824            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23825        })
23826        .await
23827        .unwrap();
23828    drop(editor_1);
23829    pane_1.update(cx, |pane, cx| {
23830        pane.active_item()
23831            .unwrap()
23832            .downcast::<Editor>()
23833            .unwrap()
23834            .update(cx, |editor, cx| {
23835                assert_eq!(
23836                    editor.display_text(cx),
23837                    lib_text,
23838                    "Other file should be open and active",
23839                );
23840            });
23841        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23842    });
23843
23844    let _other_editor_2 = workspace
23845        .update_in(cx, |workspace, window, cx| {
23846            workspace.open_path(
23847                (worktree_id, rel_path("lib.rs")),
23848                Some(pane_2.downgrade()),
23849                true,
23850                window,
23851                cx,
23852            )
23853        })
23854        .unwrap()
23855        .await
23856        .downcast::<Editor>()
23857        .unwrap();
23858    pane_2
23859        .update_in(cx, |pane, window, cx| {
23860            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23861        })
23862        .await
23863        .unwrap();
23864    drop(editor_2);
23865    pane_2.update(cx, |pane, cx| {
23866        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23867        open_editor.update(cx, |editor, cx| {
23868            assert_eq!(
23869                editor.display_text(cx),
23870                lib_text,
23871                "Other file should be open and active in another panel too",
23872            );
23873        });
23874        assert_eq!(
23875            pane.items().count(),
23876            1,
23877            "No other editors should be open in another pane",
23878        );
23879    });
23880
23881    let _editor_1_reopened = workspace
23882        .update_in(cx, |workspace, window, cx| {
23883            workspace.open_path(
23884                (worktree_id, rel_path("main.rs")),
23885                Some(pane_1.downgrade()),
23886                true,
23887                window,
23888                cx,
23889            )
23890        })
23891        .unwrap()
23892        .await
23893        .downcast::<Editor>()
23894        .unwrap();
23895    let _editor_2_reopened = workspace
23896        .update_in(cx, |workspace, window, cx| {
23897            workspace.open_path(
23898                (worktree_id, rel_path("main.rs")),
23899                Some(pane_2.downgrade()),
23900                true,
23901                window,
23902                cx,
23903            )
23904        })
23905        .unwrap()
23906        .await
23907        .downcast::<Editor>()
23908        .unwrap();
23909    pane_1.update(cx, |pane, cx| {
23910        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23911        open_editor.update(cx, |editor, cx| {
23912            assert_eq!(
23913                editor.display_text(cx),
23914                main_text,
23915                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23916            );
23917            assert_eq!(
23918                editor
23919                    .selections
23920                    .all::<Point>(&editor.display_snapshot(cx))
23921                    .into_iter()
23922                    .map(|s| s.range())
23923                    .collect::<Vec<_>>(),
23924                expected_ranges,
23925                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23926            );
23927        })
23928    });
23929    pane_2.update(cx, |pane, cx| {
23930        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23931        open_editor.update(cx, |editor, cx| {
23932            assert_eq!(
23933                editor.display_text(cx),
23934                r#"fn main() {
23935⋯rintln!("1");
23936⋯intln!("2");
23937⋯ntln!("3");
23938println!("4");
23939println!("5");
23940}"#,
23941                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23942            );
23943            assert_eq!(
23944                editor
23945                    .selections
23946                    .all::<Point>(&editor.display_snapshot(cx))
23947                    .into_iter()
23948                    .map(|s| s.range())
23949                    .collect::<Vec<_>>(),
23950                vec![Point::zero()..Point::zero()],
23951                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23952            );
23953        })
23954    });
23955}
23956
23957#[gpui::test]
23958async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23959    init_test(cx, |_| {});
23960
23961    let fs = FakeFs::new(cx.executor());
23962    let main_text = r#"fn main() {
23963println!("1");
23964println!("2");
23965println!("3");
23966println!("4");
23967println!("5");
23968}"#;
23969    let lib_text = "mod foo {}";
23970    fs.insert_tree(
23971        path!("/a"),
23972        json!({
23973            "lib.rs": lib_text,
23974            "main.rs": main_text,
23975        }),
23976    )
23977    .await;
23978
23979    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23980    let (workspace, cx) =
23981        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23982    let worktree_id = workspace.update(cx, |workspace, cx| {
23983        workspace.project().update(cx, |project, cx| {
23984            project.worktrees(cx).next().unwrap().read(cx).id()
23985        })
23986    });
23987
23988    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23989    let editor = workspace
23990        .update_in(cx, |workspace, window, cx| {
23991            workspace.open_path(
23992                (worktree_id, rel_path("main.rs")),
23993                Some(pane.downgrade()),
23994                true,
23995                window,
23996                cx,
23997            )
23998        })
23999        .unwrap()
24000        .await
24001        .downcast::<Editor>()
24002        .unwrap();
24003    pane.update(cx, |pane, cx| {
24004        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24005        open_editor.update(cx, |editor, cx| {
24006            assert_eq!(
24007                editor.display_text(cx),
24008                main_text,
24009                "Original main.rs text on initial open",
24010            );
24011        })
24012    });
24013    editor.update_in(cx, |editor, window, cx| {
24014        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24015    });
24016
24017    cx.update_global(|store: &mut SettingsStore, cx| {
24018        store.update_user_settings(cx, |s| {
24019            s.workspace.restore_on_file_reopen = Some(false);
24020        });
24021    });
24022    editor.update_in(cx, |editor, window, cx| {
24023        editor.fold_ranges(
24024            vec![
24025                Point::new(1, 0)..Point::new(1, 1),
24026                Point::new(2, 0)..Point::new(2, 2),
24027                Point::new(3, 0)..Point::new(3, 3),
24028            ],
24029            false,
24030            window,
24031            cx,
24032        );
24033    });
24034    pane.update_in(cx, |pane, window, cx| {
24035        pane.close_all_items(&CloseAllItems::default(), window, cx)
24036    })
24037    .await
24038    .unwrap();
24039    pane.update(cx, |pane, _| {
24040        assert!(pane.active_item().is_none());
24041    });
24042    cx.update_global(|store: &mut SettingsStore, cx| {
24043        store.update_user_settings(cx, |s| {
24044            s.workspace.restore_on_file_reopen = Some(true);
24045        });
24046    });
24047
24048    let _editor_reopened = workspace
24049        .update_in(cx, |workspace, window, cx| {
24050            workspace.open_path(
24051                (worktree_id, rel_path("main.rs")),
24052                Some(pane.downgrade()),
24053                true,
24054                window,
24055                cx,
24056            )
24057        })
24058        .unwrap()
24059        .await
24060        .downcast::<Editor>()
24061        .unwrap();
24062    pane.update(cx, |pane, cx| {
24063        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24064        open_editor.update(cx, |editor, cx| {
24065            assert_eq!(
24066                editor.display_text(cx),
24067                main_text,
24068                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24069            );
24070        })
24071    });
24072}
24073
24074#[gpui::test]
24075async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24076    struct EmptyModalView {
24077        focus_handle: gpui::FocusHandle,
24078    }
24079    impl EventEmitter<DismissEvent> for EmptyModalView {}
24080    impl Render for EmptyModalView {
24081        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24082            div()
24083        }
24084    }
24085    impl Focusable for EmptyModalView {
24086        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24087            self.focus_handle.clone()
24088        }
24089    }
24090    impl workspace::ModalView for EmptyModalView {}
24091    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24092        EmptyModalView {
24093            focus_handle: cx.focus_handle(),
24094        }
24095    }
24096
24097    init_test(cx, |_| {});
24098
24099    let fs = FakeFs::new(cx.executor());
24100    let project = Project::test(fs, [], cx).await;
24101    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24102    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24103    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24104    let editor = cx.new_window_entity(|window, cx| {
24105        Editor::new(
24106            EditorMode::full(),
24107            buffer,
24108            Some(project.clone()),
24109            window,
24110            cx,
24111        )
24112    });
24113    workspace
24114        .update(cx, |workspace, window, cx| {
24115            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24116        })
24117        .unwrap();
24118    editor.update_in(cx, |editor, window, cx| {
24119        editor.open_context_menu(&OpenContextMenu, window, cx);
24120        assert!(editor.mouse_context_menu.is_some());
24121    });
24122    workspace
24123        .update(cx, |workspace, window, cx| {
24124            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24125        })
24126        .unwrap();
24127    cx.read(|cx| {
24128        assert!(editor.read(cx).mouse_context_menu.is_none());
24129    });
24130}
24131
24132fn set_linked_edit_ranges(
24133    opening: (Point, Point),
24134    closing: (Point, Point),
24135    editor: &mut Editor,
24136    cx: &mut Context<Editor>,
24137) {
24138    let Some((buffer, _)) = editor
24139        .buffer
24140        .read(cx)
24141        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24142    else {
24143        panic!("Failed to get buffer for selection position");
24144    };
24145    let buffer = buffer.read(cx);
24146    let buffer_id = buffer.remote_id();
24147    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24148    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24149    let mut linked_ranges = HashMap::default();
24150    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24151    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24152}
24153
24154#[gpui::test]
24155async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24156    init_test(cx, |_| {});
24157
24158    let fs = FakeFs::new(cx.executor());
24159    fs.insert_file(path!("/file.html"), Default::default())
24160        .await;
24161
24162    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24163
24164    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24165    let html_language = Arc::new(Language::new(
24166        LanguageConfig {
24167            name: "HTML".into(),
24168            matcher: LanguageMatcher {
24169                path_suffixes: vec!["html".to_string()],
24170                ..LanguageMatcher::default()
24171            },
24172            brackets: BracketPairConfig {
24173                pairs: vec![BracketPair {
24174                    start: "<".into(),
24175                    end: ">".into(),
24176                    close: true,
24177                    ..Default::default()
24178                }],
24179                ..Default::default()
24180            },
24181            ..Default::default()
24182        },
24183        Some(tree_sitter_html::LANGUAGE.into()),
24184    ));
24185    language_registry.add(html_language);
24186    let mut fake_servers = language_registry.register_fake_lsp(
24187        "HTML",
24188        FakeLspAdapter {
24189            capabilities: lsp::ServerCapabilities {
24190                completion_provider: Some(lsp::CompletionOptions {
24191                    resolve_provider: Some(true),
24192                    ..Default::default()
24193                }),
24194                ..Default::default()
24195            },
24196            ..Default::default()
24197        },
24198    );
24199
24200    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24201    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24202
24203    let worktree_id = workspace
24204        .update(cx, |workspace, _window, cx| {
24205            workspace.project().update(cx, |project, cx| {
24206                project.worktrees(cx).next().unwrap().read(cx).id()
24207            })
24208        })
24209        .unwrap();
24210    project
24211        .update(cx, |project, cx| {
24212            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24213        })
24214        .await
24215        .unwrap();
24216    let editor = workspace
24217        .update(cx, |workspace, window, cx| {
24218            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24219        })
24220        .unwrap()
24221        .await
24222        .unwrap()
24223        .downcast::<Editor>()
24224        .unwrap();
24225
24226    let fake_server = fake_servers.next().await.unwrap();
24227    editor.update_in(cx, |editor, window, cx| {
24228        editor.set_text("<ad></ad>", window, cx);
24229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24230            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24231        });
24232        set_linked_edit_ranges(
24233            (Point::new(0, 1), Point::new(0, 3)),
24234            (Point::new(0, 6), Point::new(0, 8)),
24235            editor,
24236            cx,
24237        );
24238    });
24239    let mut completion_handle =
24240        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24241            Ok(Some(lsp::CompletionResponse::Array(vec![
24242                lsp::CompletionItem {
24243                    label: "head".to_string(),
24244                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24245                        lsp::InsertReplaceEdit {
24246                            new_text: "head".to_string(),
24247                            insert: lsp::Range::new(
24248                                lsp::Position::new(0, 1),
24249                                lsp::Position::new(0, 3),
24250                            ),
24251                            replace: lsp::Range::new(
24252                                lsp::Position::new(0, 1),
24253                                lsp::Position::new(0, 3),
24254                            ),
24255                        },
24256                    )),
24257                    ..Default::default()
24258                },
24259            ])))
24260        });
24261    editor.update_in(cx, |editor, window, cx| {
24262        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24263    });
24264    cx.run_until_parked();
24265    completion_handle.next().await.unwrap();
24266    editor.update(cx, |editor, _| {
24267        assert!(
24268            editor.context_menu_visible(),
24269            "Completion menu should be visible"
24270        );
24271    });
24272    editor.update_in(cx, |editor, window, cx| {
24273        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24274    });
24275    cx.executor().run_until_parked();
24276    editor.update(cx, |editor, cx| {
24277        assert_eq!(editor.text(cx), "<head></head>");
24278    });
24279}
24280
24281#[gpui::test]
24282async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24283    init_test(cx, |_| {});
24284
24285    let mut cx = EditorTestContext::new(cx).await;
24286    let language = Arc::new(Language::new(
24287        LanguageConfig {
24288            name: "TSX".into(),
24289            matcher: LanguageMatcher {
24290                path_suffixes: vec!["tsx".to_string()],
24291                ..LanguageMatcher::default()
24292            },
24293            brackets: BracketPairConfig {
24294                pairs: vec![BracketPair {
24295                    start: "<".into(),
24296                    end: ">".into(),
24297                    close: true,
24298                    ..Default::default()
24299                }],
24300                ..Default::default()
24301            },
24302            linked_edit_characters: HashSet::from_iter(['.']),
24303            ..Default::default()
24304        },
24305        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24306    ));
24307    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24308
24309    // Test typing > does not extend linked pair
24310    cx.set_state("<divˇ<div></div>");
24311    cx.update_editor(|editor, _, cx| {
24312        set_linked_edit_ranges(
24313            (Point::new(0, 1), Point::new(0, 4)),
24314            (Point::new(0, 11), Point::new(0, 14)),
24315            editor,
24316            cx,
24317        );
24318    });
24319    cx.update_editor(|editor, window, cx| {
24320        editor.handle_input(">", window, cx);
24321    });
24322    cx.assert_editor_state("<div>ˇ<div></div>");
24323
24324    // Test typing . do extend linked pair
24325    cx.set_state("<Animatedˇ></Animated>");
24326    cx.update_editor(|editor, _, cx| {
24327        set_linked_edit_ranges(
24328            (Point::new(0, 1), Point::new(0, 9)),
24329            (Point::new(0, 12), Point::new(0, 20)),
24330            editor,
24331            cx,
24332        );
24333    });
24334    cx.update_editor(|editor, window, cx| {
24335        editor.handle_input(".", window, cx);
24336    });
24337    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24338    cx.update_editor(|editor, _, cx| {
24339        set_linked_edit_ranges(
24340            (Point::new(0, 1), Point::new(0, 10)),
24341            (Point::new(0, 13), Point::new(0, 21)),
24342            editor,
24343            cx,
24344        );
24345    });
24346    cx.update_editor(|editor, window, cx| {
24347        editor.handle_input("V", window, cx);
24348    });
24349    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24350}
24351
24352#[gpui::test]
24353async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24354    init_test(cx, |_| {});
24355
24356    let fs = FakeFs::new(cx.executor());
24357    fs.insert_tree(
24358        path!("/root"),
24359        json!({
24360            "a": {
24361                "main.rs": "fn main() {}",
24362            },
24363            "foo": {
24364                "bar": {
24365                    "external_file.rs": "pub mod external {}",
24366                }
24367            }
24368        }),
24369    )
24370    .await;
24371
24372    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24373    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24374    language_registry.add(rust_lang());
24375    let _fake_servers = language_registry.register_fake_lsp(
24376        "Rust",
24377        FakeLspAdapter {
24378            ..FakeLspAdapter::default()
24379        },
24380    );
24381    let (workspace, cx) =
24382        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24383    let worktree_id = workspace.update(cx, |workspace, cx| {
24384        workspace.project().update(cx, |project, cx| {
24385            project.worktrees(cx).next().unwrap().read(cx).id()
24386        })
24387    });
24388
24389    let assert_language_servers_count =
24390        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24391            project.update(cx, |project, cx| {
24392                let current = project
24393                    .lsp_store()
24394                    .read(cx)
24395                    .as_local()
24396                    .unwrap()
24397                    .language_servers
24398                    .len();
24399                assert_eq!(expected, current, "{context}");
24400            });
24401        };
24402
24403    assert_language_servers_count(
24404        0,
24405        "No servers should be running before any file is open",
24406        cx,
24407    );
24408    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24409    let main_editor = workspace
24410        .update_in(cx, |workspace, window, cx| {
24411            workspace.open_path(
24412                (worktree_id, rel_path("main.rs")),
24413                Some(pane.downgrade()),
24414                true,
24415                window,
24416                cx,
24417            )
24418        })
24419        .unwrap()
24420        .await
24421        .downcast::<Editor>()
24422        .unwrap();
24423    pane.update(cx, |pane, cx| {
24424        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24425        open_editor.update(cx, |editor, cx| {
24426            assert_eq!(
24427                editor.display_text(cx),
24428                "fn main() {}",
24429                "Original main.rs text on initial open",
24430            );
24431        });
24432        assert_eq!(open_editor, main_editor);
24433    });
24434    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24435
24436    let external_editor = workspace
24437        .update_in(cx, |workspace, window, cx| {
24438            workspace.open_abs_path(
24439                PathBuf::from("/root/foo/bar/external_file.rs"),
24440                OpenOptions::default(),
24441                window,
24442                cx,
24443            )
24444        })
24445        .await
24446        .expect("opening external file")
24447        .downcast::<Editor>()
24448        .expect("downcasted external file's open element to editor");
24449    pane.update(cx, |pane, cx| {
24450        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24451        open_editor.update(cx, |editor, cx| {
24452            assert_eq!(
24453                editor.display_text(cx),
24454                "pub mod external {}",
24455                "External file is open now",
24456            );
24457        });
24458        assert_eq!(open_editor, external_editor);
24459    });
24460    assert_language_servers_count(
24461        1,
24462        "Second, external, *.rs file should join the existing server",
24463        cx,
24464    );
24465
24466    pane.update_in(cx, |pane, window, cx| {
24467        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24468    })
24469    .await
24470    .unwrap();
24471    pane.update_in(cx, |pane, window, cx| {
24472        pane.navigate_backward(&Default::default(), window, cx);
24473    });
24474    cx.run_until_parked();
24475    pane.update(cx, |pane, cx| {
24476        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24477        open_editor.update(cx, |editor, cx| {
24478            assert_eq!(
24479                editor.display_text(cx),
24480                "pub mod external {}",
24481                "External file is open now",
24482            );
24483        });
24484    });
24485    assert_language_servers_count(
24486        1,
24487        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24488        cx,
24489    );
24490
24491    cx.update(|_, cx| {
24492        workspace::reload(cx);
24493    });
24494    assert_language_servers_count(
24495        1,
24496        "After reloading the worktree with local and external files opened, only one project should be started",
24497        cx,
24498    );
24499}
24500
24501#[gpui::test]
24502async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24503    init_test(cx, |_| {});
24504
24505    let mut cx = EditorTestContext::new(cx).await;
24506    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24507    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24508
24509    // test cursor move to start of each line on tab
24510    // for `if`, `elif`, `else`, `while`, `with` and `for`
24511    cx.set_state(indoc! {"
24512        def main():
24513        ˇ    for item in items:
24514        ˇ        while item.active:
24515        ˇ            if item.value > 10:
24516        ˇ                continue
24517        ˇ            elif item.value < 0:
24518        ˇ                break
24519        ˇ            else:
24520        ˇ                with item.context() as ctx:
24521        ˇ                    yield count
24522        ˇ        else:
24523        ˇ            log('while else')
24524        ˇ    else:
24525        ˇ        log('for else')
24526    "});
24527    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24528    cx.assert_editor_state(indoc! {"
24529        def main():
24530            ˇfor item in items:
24531                ˇwhile item.active:
24532                    ˇif item.value > 10:
24533                        ˇcontinue
24534                    ˇelif item.value < 0:
24535                        ˇbreak
24536                    ˇelse:
24537                        ˇwith item.context() as ctx:
24538                            ˇyield count
24539                ˇelse:
24540                    ˇlog('while else')
24541            ˇelse:
24542                ˇlog('for else')
24543    "});
24544    // test relative indent is preserved when tab
24545    // for `if`, `elif`, `else`, `while`, `with` and `for`
24546    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24547    cx.assert_editor_state(indoc! {"
24548        def main():
24549                ˇfor item in items:
24550                    ˇwhile item.active:
24551                        ˇif item.value > 10:
24552                            ˇcontinue
24553                        ˇelif item.value < 0:
24554                            ˇbreak
24555                        ˇelse:
24556                            ˇwith item.context() as ctx:
24557                                ˇyield count
24558                    ˇelse:
24559                        ˇlog('while else')
24560                ˇelse:
24561                    ˇlog('for else')
24562    "});
24563
24564    // test cursor move to start of each line on tab
24565    // for `try`, `except`, `else`, `finally`, `match` and `def`
24566    cx.set_state(indoc! {"
24567        def main():
24568        ˇ    try:
24569        ˇ        fetch()
24570        ˇ    except ValueError:
24571        ˇ        handle_error()
24572        ˇ    else:
24573        ˇ        match value:
24574        ˇ            case _:
24575        ˇ    finally:
24576        ˇ        def status():
24577        ˇ            return 0
24578    "});
24579    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24580    cx.assert_editor_state(indoc! {"
24581        def main():
24582            ˇtry:
24583                ˇfetch()
24584            ˇexcept ValueError:
24585                ˇhandle_error()
24586            ˇelse:
24587                ˇmatch value:
24588                    ˇcase _:
24589            ˇfinally:
24590                ˇdef status():
24591                    ˇreturn 0
24592    "});
24593    // test relative indent is preserved when tab
24594    // for `try`, `except`, `else`, `finally`, `match` and `def`
24595    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24596    cx.assert_editor_state(indoc! {"
24597        def main():
24598                ˇtry:
24599                    ˇfetch()
24600                ˇexcept ValueError:
24601                    ˇhandle_error()
24602                ˇelse:
24603                    ˇmatch value:
24604                        ˇcase _:
24605                ˇfinally:
24606                    ˇdef status():
24607                        ˇreturn 0
24608    "});
24609}
24610
24611#[gpui::test]
24612async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24613    init_test(cx, |_| {});
24614
24615    let mut cx = EditorTestContext::new(cx).await;
24616    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24617    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24618
24619    // test `else` auto outdents when typed inside `if` block
24620    cx.set_state(indoc! {"
24621        def main():
24622            if i == 2:
24623                return
24624                ˇ
24625    "});
24626    cx.update_editor(|editor, window, cx| {
24627        editor.handle_input("else:", window, cx);
24628    });
24629    cx.assert_editor_state(indoc! {"
24630        def main():
24631            if i == 2:
24632                return
24633            else:ˇ
24634    "});
24635
24636    // test `except` auto outdents when typed inside `try` block
24637    cx.set_state(indoc! {"
24638        def main():
24639            try:
24640                i = 2
24641                ˇ
24642    "});
24643    cx.update_editor(|editor, window, cx| {
24644        editor.handle_input("except:", window, cx);
24645    });
24646    cx.assert_editor_state(indoc! {"
24647        def main():
24648            try:
24649                i = 2
24650            except:ˇ
24651    "});
24652
24653    // test `else` auto outdents when typed inside `except` block
24654    cx.set_state(indoc! {"
24655        def main():
24656            try:
24657                i = 2
24658            except:
24659                j = 2
24660                ˇ
24661    "});
24662    cx.update_editor(|editor, window, cx| {
24663        editor.handle_input("else:", window, cx);
24664    });
24665    cx.assert_editor_state(indoc! {"
24666        def main():
24667            try:
24668                i = 2
24669            except:
24670                j = 2
24671            else:ˇ
24672    "});
24673
24674    // test `finally` auto outdents when typed inside `else` block
24675    cx.set_state(indoc! {"
24676        def main():
24677            try:
24678                i = 2
24679            except:
24680                j = 2
24681            else:
24682                k = 2
24683                ˇ
24684    "});
24685    cx.update_editor(|editor, window, cx| {
24686        editor.handle_input("finally:", window, cx);
24687    });
24688    cx.assert_editor_state(indoc! {"
24689        def main():
24690            try:
24691                i = 2
24692            except:
24693                j = 2
24694            else:
24695                k = 2
24696            finally:ˇ
24697    "});
24698
24699    // test `else` does not outdents when typed inside `except` block right after for block
24700    cx.set_state(indoc! {"
24701        def main():
24702            try:
24703                i = 2
24704            except:
24705                for i in range(n):
24706                    pass
24707                ˇ
24708    "});
24709    cx.update_editor(|editor, window, cx| {
24710        editor.handle_input("else:", window, cx);
24711    });
24712    cx.assert_editor_state(indoc! {"
24713        def main():
24714            try:
24715                i = 2
24716            except:
24717                for i in range(n):
24718                    pass
24719                else:ˇ
24720    "});
24721
24722    // test `finally` auto outdents when typed inside `else` block right after for block
24723    cx.set_state(indoc! {"
24724        def main():
24725            try:
24726                i = 2
24727            except:
24728                j = 2
24729            else:
24730                for i in range(n):
24731                    pass
24732                ˇ
24733    "});
24734    cx.update_editor(|editor, window, cx| {
24735        editor.handle_input("finally:", window, cx);
24736    });
24737    cx.assert_editor_state(indoc! {"
24738        def main():
24739            try:
24740                i = 2
24741            except:
24742                j = 2
24743            else:
24744                for i in range(n):
24745                    pass
24746            finally:ˇ
24747    "});
24748
24749    // test `except` outdents to inner "try" block
24750    cx.set_state(indoc! {"
24751        def main():
24752            try:
24753                i = 2
24754                if i == 2:
24755                    try:
24756                        i = 3
24757                        ˇ
24758    "});
24759    cx.update_editor(|editor, window, cx| {
24760        editor.handle_input("except:", window, cx);
24761    });
24762    cx.assert_editor_state(indoc! {"
24763        def main():
24764            try:
24765                i = 2
24766                if i == 2:
24767                    try:
24768                        i = 3
24769                    except:ˇ
24770    "});
24771
24772    // test `except` outdents to outer "try" block
24773    cx.set_state(indoc! {"
24774        def main():
24775            try:
24776                i = 2
24777                if i == 2:
24778                    try:
24779                        i = 3
24780                ˇ
24781    "});
24782    cx.update_editor(|editor, window, cx| {
24783        editor.handle_input("except:", window, cx);
24784    });
24785    cx.assert_editor_state(indoc! {"
24786        def main():
24787            try:
24788                i = 2
24789                if i == 2:
24790                    try:
24791                        i = 3
24792            except:ˇ
24793    "});
24794
24795    // test `else` stays at correct indent when typed after `for` block
24796    cx.set_state(indoc! {"
24797        def main():
24798            for i in range(10):
24799                if i == 3:
24800                    break
24801            ˇ
24802    "});
24803    cx.update_editor(|editor, window, cx| {
24804        editor.handle_input("else:", window, cx);
24805    });
24806    cx.assert_editor_state(indoc! {"
24807        def main():
24808            for i in range(10):
24809                if i == 3:
24810                    break
24811            else:ˇ
24812    "});
24813
24814    // test does not outdent on typing after line with square brackets
24815    cx.set_state(indoc! {"
24816        def f() -> list[str]:
24817            ˇ
24818    "});
24819    cx.update_editor(|editor, window, cx| {
24820        editor.handle_input("a", window, cx);
24821    });
24822    cx.assert_editor_state(indoc! {"
24823        def f() -> list[str]:
2482424825    "});
24826
24827    // test does not outdent on typing : after case keyword
24828    cx.set_state(indoc! {"
24829        match 1:
24830            caseˇ
24831    "});
24832    cx.update_editor(|editor, window, cx| {
24833        editor.handle_input(":", window, cx);
24834    });
24835    cx.assert_editor_state(indoc! {"
24836        match 1:
24837            case:ˇ
24838    "});
24839}
24840
24841#[gpui::test]
24842async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24843    init_test(cx, |_| {});
24844    update_test_language_settings(cx, |settings| {
24845        settings.defaults.extend_comment_on_newline = Some(false);
24846    });
24847    let mut cx = EditorTestContext::new(cx).await;
24848    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24849    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24850
24851    // test correct indent after newline on comment
24852    cx.set_state(indoc! {"
24853        # COMMENT:ˇ
24854    "});
24855    cx.update_editor(|editor, window, cx| {
24856        editor.newline(&Newline, window, cx);
24857    });
24858    cx.assert_editor_state(indoc! {"
24859        # COMMENT:
24860        ˇ
24861    "});
24862
24863    // test correct indent after newline in brackets
24864    cx.set_state(indoc! {"
24865        {ˇ}
24866    "});
24867    cx.update_editor(|editor, window, cx| {
24868        editor.newline(&Newline, window, cx);
24869    });
24870    cx.run_until_parked();
24871    cx.assert_editor_state(indoc! {"
24872        {
24873            ˇ
24874        }
24875    "});
24876
24877    cx.set_state(indoc! {"
24878        (ˇ)
24879    "});
24880    cx.update_editor(|editor, window, cx| {
24881        editor.newline(&Newline, window, cx);
24882    });
24883    cx.run_until_parked();
24884    cx.assert_editor_state(indoc! {"
24885        (
24886            ˇ
24887        )
24888    "});
24889
24890    // do not indent after empty lists or dictionaries
24891    cx.set_state(indoc! {"
24892        a = []ˇ
24893    "});
24894    cx.update_editor(|editor, window, cx| {
24895        editor.newline(&Newline, window, cx);
24896    });
24897    cx.run_until_parked();
24898    cx.assert_editor_state(indoc! {"
24899        a = []
24900        ˇ
24901    "});
24902}
24903
24904#[gpui::test]
24905async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24906    init_test(cx, |_| {});
24907
24908    let mut cx = EditorTestContext::new(cx).await;
24909    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24910    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24911
24912    // test cursor move to start of each line on tab
24913    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24914    cx.set_state(indoc! {"
24915        function main() {
24916        ˇ    for item in $items; do
24917        ˇ        while [ -n \"$item\" ]; do
24918        ˇ            if [ \"$value\" -gt 10 ]; then
24919        ˇ                continue
24920        ˇ            elif [ \"$value\" -lt 0 ]; then
24921        ˇ                break
24922        ˇ            else
24923        ˇ                echo \"$item\"
24924        ˇ            fi
24925        ˇ        done
24926        ˇ    done
24927        ˇ}
24928    "});
24929    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24930    cx.assert_editor_state(indoc! {"
24931        function main() {
24932            ˇfor item in $items; do
24933                ˇwhile [ -n \"$item\" ]; do
24934                    ˇif [ \"$value\" -gt 10 ]; then
24935                        ˇcontinue
24936                    ˇelif [ \"$value\" -lt 0 ]; then
24937                        ˇbreak
24938                    ˇelse
24939                        ˇecho \"$item\"
24940                    ˇfi
24941                ˇdone
24942            ˇdone
24943        ˇ}
24944    "});
24945    // test relative indent is preserved when tab
24946    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24947    cx.assert_editor_state(indoc! {"
24948        function main() {
24949                ˇfor item in $items; do
24950                    ˇwhile [ -n \"$item\" ]; do
24951                        ˇif [ \"$value\" -gt 10 ]; then
24952                            ˇcontinue
24953                        ˇelif [ \"$value\" -lt 0 ]; then
24954                            ˇbreak
24955                        ˇelse
24956                            ˇecho \"$item\"
24957                        ˇfi
24958                    ˇdone
24959                ˇdone
24960            ˇ}
24961    "});
24962
24963    // test cursor move to start of each line on tab
24964    // for `case` statement with patterns
24965    cx.set_state(indoc! {"
24966        function handle() {
24967        ˇ    case \"$1\" in
24968        ˇ        start)
24969        ˇ            echo \"a\"
24970        ˇ            ;;
24971        ˇ        stop)
24972        ˇ            echo \"b\"
24973        ˇ            ;;
24974        ˇ        *)
24975        ˇ            echo \"c\"
24976        ˇ            ;;
24977        ˇ    esac
24978        ˇ}
24979    "});
24980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24981    cx.assert_editor_state(indoc! {"
24982        function handle() {
24983            ˇcase \"$1\" in
24984                ˇstart)
24985                    ˇecho \"a\"
24986                    ˇ;;
24987                ˇstop)
24988                    ˇecho \"b\"
24989                    ˇ;;
24990                ˇ*)
24991                    ˇecho \"c\"
24992                    ˇ;;
24993            ˇesac
24994        ˇ}
24995    "});
24996}
24997
24998#[gpui::test]
24999async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25000    init_test(cx, |_| {});
25001
25002    let mut cx = EditorTestContext::new(cx).await;
25003    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25004    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25005
25006    // test indents on comment insert
25007    cx.set_state(indoc! {"
25008        function main() {
25009        ˇ    for item in $items; do
25010        ˇ        while [ -n \"$item\" ]; do
25011        ˇ            if [ \"$value\" -gt 10 ]; then
25012        ˇ                continue
25013        ˇ            elif [ \"$value\" -lt 0 ]; then
25014        ˇ                break
25015        ˇ            else
25016        ˇ                echo \"$item\"
25017        ˇ            fi
25018        ˇ        done
25019        ˇ    done
25020        ˇ}
25021    "});
25022    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25023    cx.assert_editor_state(indoc! {"
25024        function main() {
25025        #ˇ    for item in $items; do
25026        #ˇ        while [ -n \"$item\" ]; do
25027        #ˇ            if [ \"$value\" -gt 10 ]; then
25028        #ˇ                continue
25029        #ˇ            elif [ \"$value\" -lt 0 ]; then
25030        #ˇ                break
25031        #ˇ            else
25032        #ˇ                echo \"$item\"
25033        #ˇ            fi
25034        #ˇ        done
25035        #ˇ    done
25036        #ˇ}
25037    "});
25038}
25039
25040#[gpui::test]
25041async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25042    init_test(cx, |_| {});
25043
25044    let mut cx = EditorTestContext::new(cx).await;
25045    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25046    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25047
25048    // test `else` auto outdents when typed inside `if` block
25049    cx.set_state(indoc! {"
25050        if [ \"$1\" = \"test\" ]; then
25051            echo \"foo bar\"
25052            ˇ
25053    "});
25054    cx.update_editor(|editor, window, cx| {
25055        editor.handle_input("else", window, cx);
25056    });
25057    cx.assert_editor_state(indoc! {"
25058        if [ \"$1\" = \"test\" ]; then
25059            echo \"foo bar\"
25060        elseˇ
25061    "});
25062
25063    // test `elif` auto outdents when typed inside `if` block
25064    cx.set_state(indoc! {"
25065        if [ \"$1\" = \"test\" ]; then
25066            echo \"foo bar\"
25067            ˇ
25068    "});
25069    cx.update_editor(|editor, window, cx| {
25070        editor.handle_input("elif", window, cx);
25071    });
25072    cx.assert_editor_state(indoc! {"
25073        if [ \"$1\" = \"test\" ]; then
25074            echo \"foo bar\"
25075        elifˇ
25076    "});
25077
25078    // test `fi` auto outdents when typed inside `else` block
25079    cx.set_state(indoc! {"
25080        if [ \"$1\" = \"test\" ]; then
25081            echo \"foo bar\"
25082        else
25083            echo \"bar baz\"
25084            ˇ
25085    "});
25086    cx.update_editor(|editor, window, cx| {
25087        editor.handle_input("fi", window, cx);
25088    });
25089    cx.assert_editor_state(indoc! {"
25090        if [ \"$1\" = \"test\" ]; then
25091            echo \"foo bar\"
25092        else
25093            echo \"bar baz\"
25094        fiˇ
25095    "});
25096
25097    // test `done` auto outdents when typed inside `while` block
25098    cx.set_state(indoc! {"
25099        while read line; do
25100            echo \"$line\"
25101            ˇ
25102    "});
25103    cx.update_editor(|editor, window, cx| {
25104        editor.handle_input("done", window, cx);
25105    });
25106    cx.assert_editor_state(indoc! {"
25107        while read line; do
25108            echo \"$line\"
25109        doneˇ
25110    "});
25111
25112    // test `done` auto outdents when typed inside `for` block
25113    cx.set_state(indoc! {"
25114        for file in *.txt; do
25115            cat \"$file\"
25116            ˇ
25117    "});
25118    cx.update_editor(|editor, window, cx| {
25119        editor.handle_input("done", window, cx);
25120    });
25121    cx.assert_editor_state(indoc! {"
25122        for file in *.txt; do
25123            cat \"$file\"
25124        doneˇ
25125    "});
25126
25127    // test `esac` auto outdents when typed inside `case` block
25128    cx.set_state(indoc! {"
25129        case \"$1\" in
25130            start)
25131                echo \"foo bar\"
25132                ;;
25133            stop)
25134                echo \"bar baz\"
25135                ;;
25136            ˇ
25137    "});
25138    cx.update_editor(|editor, window, cx| {
25139        editor.handle_input("esac", window, cx);
25140    });
25141    cx.assert_editor_state(indoc! {"
25142        case \"$1\" in
25143            start)
25144                echo \"foo bar\"
25145                ;;
25146            stop)
25147                echo \"bar baz\"
25148                ;;
25149        esacˇ
25150    "});
25151
25152    // test `*)` auto outdents when typed inside `case` block
25153    cx.set_state(indoc! {"
25154        case \"$1\" in
25155            start)
25156                echo \"foo bar\"
25157                ;;
25158                ˇ
25159    "});
25160    cx.update_editor(|editor, window, cx| {
25161        editor.handle_input("*)", window, cx);
25162    });
25163    cx.assert_editor_state(indoc! {"
25164        case \"$1\" in
25165            start)
25166                echo \"foo bar\"
25167                ;;
25168            *)ˇ
25169    "});
25170
25171    // test `fi` outdents to correct level with nested if blocks
25172    cx.set_state(indoc! {"
25173        if [ \"$1\" = \"test\" ]; then
25174            echo \"outer if\"
25175            if [ \"$2\" = \"debug\" ]; then
25176                echo \"inner if\"
25177                ˇ
25178    "});
25179    cx.update_editor(|editor, window, cx| {
25180        editor.handle_input("fi", window, cx);
25181    });
25182    cx.assert_editor_state(indoc! {"
25183        if [ \"$1\" = \"test\" ]; then
25184            echo \"outer if\"
25185            if [ \"$2\" = \"debug\" ]; then
25186                echo \"inner if\"
25187            fiˇ
25188    "});
25189}
25190
25191#[gpui::test]
25192async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25193    init_test(cx, |_| {});
25194    update_test_language_settings(cx, |settings| {
25195        settings.defaults.extend_comment_on_newline = Some(false);
25196    });
25197    let mut cx = EditorTestContext::new(cx).await;
25198    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25200
25201    // test correct indent after newline on comment
25202    cx.set_state(indoc! {"
25203        # COMMENT:ˇ
25204    "});
25205    cx.update_editor(|editor, window, cx| {
25206        editor.newline(&Newline, window, cx);
25207    });
25208    cx.assert_editor_state(indoc! {"
25209        # COMMENT:
25210        ˇ
25211    "});
25212
25213    // test correct indent after newline after `then`
25214    cx.set_state(indoc! {"
25215
25216        if [ \"$1\" = \"test\" ]; thenˇ
25217    "});
25218    cx.update_editor(|editor, window, cx| {
25219        editor.newline(&Newline, window, cx);
25220    });
25221    cx.run_until_parked();
25222    cx.assert_editor_state(indoc! {"
25223
25224        if [ \"$1\" = \"test\" ]; then
25225            ˇ
25226    "});
25227
25228    // test correct indent after newline after `else`
25229    cx.set_state(indoc! {"
25230        if [ \"$1\" = \"test\" ]; then
25231        elseˇ
25232    "});
25233    cx.update_editor(|editor, window, cx| {
25234        editor.newline(&Newline, window, cx);
25235    });
25236    cx.run_until_parked();
25237    cx.assert_editor_state(indoc! {"
25238        if [ \"$1\" = \"test\" ]; then
25239        else
25240            ˇ
25241    "});
25242
25243    // test correct indent after newline after `elif`
25244    cx.set_state(indoc! {"
25245        if [ \"$1\" = \"test\" ]; then
25246        elifˇ
25247    "});
25248    cx.update_editor(|editor, window, cx| {
25249        editor.newline(&Newline, window, cx);
25250    });
25251    cx.run_until_parked();
25252    cx.assert_editor_state(indoc! {"
25253        if [ \"$1\" = \"test\" ]; then
25254        elif
25255            ˇ
25256    "});
25257
25258    // test correct indent after newline after `do`
25259    cx.set_state(indoc! {"
25260        for file in *.txt; doˇ
25261    "});
25262    cx.update_editor(|editor, window, cx| {
25263        editor.newline(&Newline, window, cx);
25264    });
25265    cx.run_until_parked();
25266    cx.assert_editor_state(indoc! {"
25267        for file in *.txt; do
25268            ˇ
25269    "});
25270
25271    // test correct indent after newline after case pattern
25272    cx.set_state(indoc! {"
25273        case \"$1\" in
25274            start)ˇ
25275    "});
25276    cx.update_editor(|editor, window, cx| {
25277        editor.newline(&Newline, window, cx);
25278    });
25279    cx.run_until_parked();
25280    cx.assert_editor_state(indoc! {"
25281        case \"$1\" in
25282            start)
25283                ˇ
25284    "});
25285
25286    // test correct indent after newline after case pattern
25287    cx.set_state(indoc! {"
25288        case \"$1\" in
25289            start)
25290                ;;
25291            *)ˇ
25292    "});
25293    cx.update_editor(|editor, window, cx| {
25294        editor.newline(&Newline, window, cx);
25295    });
25296    cx.run_until_parked();
25297    cx.assert_editor_state(indoc! {"
25298        case \"$1\" in
25299            start)
25300                ;;
25301            *)
25302                ˇ
25303    "});
25304
25305    // test correct indent after newline after function opening brace
25306    cx.set_state(indoc! {"
25307        function test() {ˇ}
25308    "});
25309    cx.update_editor(|editor, window, cx| {
25310        editor.newline(&Newline, window, cx);
25311    });
25312    cx.run_until_parked();
25313    cx.assert_editor_state(indoc! {"
25314        function test() {
25315            ˇ
25316        }
25317    "});
25318
25319    // test no extra indent after semicolon on same line
25320    cx.set_state(indoc! {"
25321        echo \"test\"25322    "});
25323    cx.update_editor(|editor, window, cx| {
25324        editor.newline(&Newline, window, cx);
25325    });
25326    cx.run_until_parked();
25327    cx.assert_editor_state(indoc! {"
25328        echo \"test\";
25329        ˇ
25330    "});
25331}
25332
25333fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25334    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25335    point..point
25336}
25337
25338#[track_caller]
25339fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25340    let (text, ranges) = marked_text_ranges(marked_text, true);
25341    assert_eq!(editor.text(cx), text);
25342    assert_eq!(
25343        editor.selections.ranges(&editor.display_snapshot(cx)),
25344        ranges,
25345        "Assert selections are {}",
25346        marked_text
25347    );
25348}
25349
25350pub fn handle_signature_help_request(
25351    cx: &mut EditorLspTestContext,
25352    mocked_response: lsp::SignatureHelp,
25353) -> impl Future<Output = ()> + use<> {
25354    let mut request =
25355        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25356            let mocked_response = mocked_response.clone();
25357            async move { Ok(Some(mocked_response)) }
25358        });
25359
25360    async move {
25361        request.next().await;
25362    }
25363}
25364
25365#[track_caller]
25366pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25367    cx.update_editor(|editor, _, _| {
25368        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25369            let entries = menu.entries.borrow();
25370            let entries = entries
25371                .iter()
25372                .map(|entry| entry.string.as_str())
25373                .collect::<Vec<_>>();
25374            assert_eq!(entries, expected);
25375        } else {
25376            panic!("Expected completions menu");
25377        }
25378    });
25379}
25380
25381/// Handle completion request passing a marked string specifying where the completion
25382/// should be triggered from using '|' character, what range should be replaced, and what completions
25383/// should be returned using '<' and '>' to delimit the range.
25384///
25385/// Also see `handle_completion_request_with_insert_and_replace`.
25386#[track_caller]
25387pub fn handle_completion_request(
25388    marked_string: &str,
25389    completions: Vec<&'static str>,
25390    is_incomplete: bool,
25391    counter: Arc<AtomicUsize>,
25392    cx: &mut EditorLspTestContext,
25393) -> impl Future<Output = ()> {
25394    let complete_from_marker: TextRangeMarker = '|'.into();
25395    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25396    let (_, mut marked_ranges) = marked_text_ranges_by(
25397        marked_string,
25398        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25399    );
25400
25401    let complete_from_position =
25402        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25403    let replace_range =
25404        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25405
25406    let mut request =
25407        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25408            let completions = completions.clone();
25409            counter.fetch_add(1, atomic::Ordering::Release);
25410            async move {
25411                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25412                assert_eq!(
25413                    params.text_document_position.position,
25414                    complete_from_position
25415                );
25416                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25417                    is_incomplete,
25418                    item_defaults: None,
25419                    items: completions
25420                        .iter()
25421                        .map(|completion_text| lsp::CompletionItem {
25422                            label: completion_text.to_string(),
25423                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25424                                range: replace_range,
25425                                new_text: completion_text.to_string(),
25426                            })),
25427                            ..Default::default()
25428                        })
25429                        .collect(),
25430                })))
25431            }
25432        });
25433
25434    async move {
25435        request.next().await;
25436    }
25437}
25438
25439/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25440/// given instead, which also contains an `insert` range.
25441///
25442/// This function uses markers to define ranges:
25443/// - `|` marks the cursor position
25444/// - `<>` marks the replace range
25445/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25446pub fn handle_completion_request_with_insert_and_replace(
25447    cx: &mut EditorLspTestContext,
25448    marked_string: &str,
25449    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25450    counter: Arc<AtomicUsize>,
25451) -> impl Future<Output = ()> {
25452    let complete_from_marker: TextRangeMarker = '|'.into();
25453    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25454    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25455
25456    let (_, mut marked_ranges) = marked_text_ranges_by(
25457        marked_string,
25458        vec![
25459            complete_from_marker.clone(),
25460            replace_range_marker.clone(),
25461            insert_range_marker.clone(),
25462        ],
25463    );
25464
25465    let complete_from_position =
25466        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25467    let replace_range =
25468        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25469
25470    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25471        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25472        _ => lsp::Range {
25473            start: replace_range.start,
25474            end: complete_from_position,
25475        },
25476    };
25477
25478    let mut request =
25479        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25480            let completions = completions.clone();
25481            counter.fetch_add(1, atomic::Ordering::Release);
25482            async move {
25483                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25484                assert_eq!(
25485                    params.text_document_position.position, complete_from_position,
25486                    "marker `|` position doesn't match",
25487                );
25488                Ok(Some(lsp::CompletionResponse::Array(
25489                    completions
25490                        .iter()
25491                        .map(|(label, new_text)| lsp::CompletionItem {
25492                            label: label.to_string(),
25493                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25494                                lsp::InsertReplaceEdit {
25495                                    insert: insert_range,
25496                                    replace: replace_range,
25497                                    new_text: new_text.to_string(),
25498                                },
25499                            )),
25500                            ..Default::default()
25501                        })
25502                        .collect(),
25503                )))
25504            }
25505        });
25506
25507    async move {
25508        request.next().await;
25509    }
25510}
25511
25512fn handle_resolve_completion_request(
25513    cx: &mut EditorLspTestContext,
25514    edits: Option<Vec<(&'static str, &'static str)>>,
25515) -> impl Future<Output = ()> {
25516    let edits = edits.map(|edits| {
25517        edits
25518            .iter()
25519            .map(|(marked_string, new_text)| {
25520                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25521                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25522                lsp::TextEdit::new(replace_range, new_text.to_string())
25523            })
25524            .collect::<Vec<_>>()
25525    });
25526
25527    let mut request =
25528        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25529            let edits = edits.clone();
25530            async move {
25531                Ok(lsp::CompletionItem {
25532                    additional_text_edits: edits,
25533                    ..Default::default()
25534                })
25535            }
25536        });
25537
25538    async move {
25539        request.next().await;
25540    }
25541}
25542
25543pub(crate) fn update_test_language_settings(
25544    cx: &mut TestAppContext,
25545    f: impl Fn(&mut AllLanguageSettingsContent),
25546) {
25547    cx.update(|cx| {
25548        SettingsStore::update_global(cx, |store, cx| {
25549            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25550        });
25551    });
25552}
25553
25554pub(crate) fn update_test_project_settings(
25555    cx: &mut TestAppContext,
25556    f: impl Fn(&mut ProjectSettingsContent),
25557) {
25558    cx.update(|cx| {
25559        SettingsStore::update_global(cx, |store, cx| {
25560            store.update_user_settings(cx, |settings| f(&mut settings.project));
25561        });
25562    });
25563}
25564
25565pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25566    cx.update(|cx| {
25567        assets::Assets.load_test_fonts(cx);
25568        let store = SettingsStore::test(cx);
25569        cx.set_global(store);
25570        theme::init(theme::LoadThemes::JustBase, cx);
25571        release_channel::init(SemanticVersion::default(), cx);
25572        client::init_settings(cx);
25573        language::init(cx);
25574        Project::init_settings(cx);
25575        workspace::init_settings(cx);
25576        crate::init(cx);
25577    });
25578    zlog::init_test();
25579    update_test_language_settings(cx, f);
25580}
25581
25582#[track_caller]
25583fn assert_hunk_revert(
25584    not_reverted_text_with_selections: &str,
25585    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25586    expected_reverted_text_with_selections: &str,
25587    base_text: &str,
25588    cx: &mut EditorLspTestContext,
25589) {
25590    cx.set_state(not_reverted_text_with_selections);
25591    cx.set_head_text(base_text);
25592    cx.executor().run_until_parked();
25593
25594    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25595        let snapshot = editor.snapshot(window, cx);
25596        let reverted_hunk_statuses = snapshot
25597            .buffer_snapshot()
25598            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25599            .map(|hunk| hunk.status().kind)
25600            .collect::<Vec<_>>();
25601
25602        editor.git_restore(&Default::default(), window, cx);
25603        reverted_hunk_statuses
25604    });
25605    cx.executor().run_until_parked();
25606    cx.assert_editor_state(expected_reverted_text_with_selections);
25607    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25608}
25609
25610#[gpui::test(iterations = 10)]
25611async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25612    init_test(cx, |_| {});
25613
25614    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25615    let counter = diagnostic_requests.clone();
25616
25617    let fs = FakeFs::new(cx.executor());
25618    fs.insert_tree(
25619        path!("/a"),
25620        json!({
25621            "first.rs": "fn main() { let a = 5; }",
25622            "second.rs": "// Test file",
25623        }),
25624    )
25625    .await;
25626
25627    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25628    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25629    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25630
25631    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25632    language_registry.add(rust_lang());
25633    let mut fake_servers = language_registry.register_fake_lsp(
25634        "Rust",
25635        FakeLspAdapter {
25636            capabilities: lsp::ServerCapabilities {
25637                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25638                    lsp::DiagnosticOptions {
25639                        identifier: None,
25640                        inter_file_dependencies: true,
25641                        workspace_diagnostics: true,
25642                        work_done_progress_options: Default::default(),
25643                    },
25644                )),
25645                ..Default::default()
25646            },
25647            ..Default::default()
25648        },
25649    );
25650
25651    let editor = workspace
25652        .update(cx, |workspace, window, cx| {
25653            workspace.open_abs_path(
25654                PathBuf::from(path!("/a/first.rs")),
25655                OpenOptions::default(),
25656                window,
25657                cx,
25658            )
25659        })
25660        .unwrap()
25661        .await
25662        .unwrap()
25663        .downcast::<Editor>()
25664        .unwrap();
25665    let fake_server = fake_servers.next().await.unwrap();
25666    let server_id = fake_server.server.server_id();
25667    let mut first_request = fake_server
25668        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25669            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25670            let result_id = Some(new_result_id.to_string());
25671            assert_eq!(
25672                params.text_document.uri,
25673                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25674            );
25675            async move {
25676                Ok(lsp::DocumentDiagnosticReportResult::Report(
25677                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25678                        related_documents: None,
25679                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25680                            items: Vec::new(),
25681                            result_id,
25682                        },
25683                    }),
25684                ))
25685            }
25686        });
25687
25688    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25689        project.update(cx, |project, cx| {
25690            let buffer_id = editor
25691                .read(cx)
25692                .buffer()
25693                .read(cx)
25694                .as_singleton()
25695                .expect("created a singleton buffer")
25696                .read(cx)
25697                .remote_id();
25698            let buffer_result_id = project
25699                .lsp_store()
25700                .read(cx)
25701                .result_id(server_id, buffer_id, cx);
25702            assert_eq!(expected, buffer_result_id);
25703        });
25704    };
25705
25706    ensure_result_id(None, cx);
25707    cx.executor().advance_clock(Duration::from_millis(60));
25708    cx.executor().run_until_parked();
25709    assert_eq!(
25710        diagnostic_requests.load(atomic::Ordering::Acquire),
25711        1,
25712        "Opening file should trigger diagnostic request"
25713    );
25714    first_request
25715        .next()
25716        .await
25717        .expect("should have sent the first diagnostics pull request");
25718    ensure_result_id(Some("1".to_string()), cx);
25719
25720    // Editing should trigger diagnostics
25721    editor.update_in(cx, |editor, window, cx| {
25722        editor.handle_input("2", window, cx)
25723    });
25724    cx.executor().advance_clock(Duration::from_millis(60));
25725    cx.executor().run_until_parked();
25726    assert_eq!(
25727        diagnostic_requests.load(atomic::Ordering::Acquire),
25728        2,
25729        "Editing should trigger diagnostic request"
25730    );
25731    ensure_result_id(Some("2".to_string()), cx);
25732
25733    // Moving cursor should not trigger diagnostic request
25734    editor.update_in(cx, |editor, window, cx| {
25735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25736            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25737        });
25738    });
25739    cx.executor().advance_clock(Duration::from_millis(60));
25740    cx.executor().run_until_parked();
25741    assert_eq!(
25742        diagnostic_requests.load(atomic::Ordering::Acquire),
25743        2,
25744        "Cursor movement should not trigger diagnostic request"
25745    );
25746    ensure_result_id(Some("2".to_string()), cx);
25747    // Multiple rapid edits should be debounced
25748    for _ in 0..5 {
25749        editor.update_in(cx, |editor, window, cx| {
25750            editor.handle_input("x", window, cx)
25751        });
25752    }
25753    cx.executor().advance_clock(Duration::from_millis(60));
25754    cx.executor().run_until_parked();
25755
25756    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25757    assert!(
25758        final_requests <= 4,
25759        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25760    );
25761    ensure_result_id(Some(final_requests.to_string()), cx);
25762}
25763
25764#[gpui::test]
25765async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25766    // Regression test for issue #11671
25767    // Previously, adding a cursor after moving multiple cursors would reset
25768    // the cursor count instead of adding to the existing cursors.
25769    init_test(cx, |_| {});
25770    let mut cx = EditorTestContext::new(cx).await;
25771
25772    // Create a simple buffer with cursor at start
25773    cx.set_state(indoc! {"
25774        ˇaaaa
25775        bbbb
25776        cccc
25777        dddd
25778        eeee
25779        ffff
25780        gggg
25781        hhhh"});
25782
25783    // Add 2 cursors below (so we have 3 total)
25784    cx.update_editor(|editor, window, cx| {
25785        editor.add_selection_below(&Default::default(), window, cx);
25786        editor.add_selection_below(&Default::default(), window, cx);
25787    });
25788
25789    // Verify we have 3 cursors
25790    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25791    assert_eq!(
25792        initial_count, 3,
25793        "Should have 3 cursors after adding 2 below"
25794    );
25795
25796    // Move down one line
25797    cx.update_editor(|editor, window, cx| {
25798        editor.move_down(&MoveDown, window, cx);
25799    });
25800
25801    // Add another cursor below
25802    cx.update_editor(|editor, window, cx| {
25803        editor.add_selection_below(&Default::default(), window, cx);
25804    });
25805
25806    // Should now have 4 cursors (3 original + 1 new)
25807    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25808    assert_eq!(
25809        final_count, 4,
25810        "Should have 4 cursors after moving and adding another"
25811    );
25812}
25813
25814#[gpui::test]
25815async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25816    init_test(cx, |_| {});
25817
25818    let mut cx = EditorTestContext::new(cx).await;
25819
25820    cx.set_state(indoc!(
25821        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25822           Second line here"#
25823    ));
25824
25825    cx.update_editor(|editor, window, cx| {
25826        // Enable soft wrapping with a narrow width to force soft wrapping and
25827        // confirm that more than 2 rows are being displayed.
25828        editor.set_wrap_width(Some(100.0.into()), cx);
25829        assert!(editor.display_text(cx).lines().count() > 2);
25830
25831        editor.add_selection_below(
25832            &AddSelectionBelow {
25833                skip_soft_wrap: true,
25834            },
25835            window,
25836            cx,
25837        );
25838
25839        assert_eq!(
25840            editor.selections.display_ranges(cx),
25841            &[
25842                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25843                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25844            ]
25845        );
25846
25847        editor.add_selection_above(
25848            &AddSelectionAbove {
25849                skip_soft_wrap: true,
25850            },
25851            window,
25852            cx,
25853        );
25854
25855        assert_eq!(
25856            editor.selections.display_ranges(cx),
25857            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25858        );
25859
25860        editor.add_selection_below(
25861            &AddSelectionBelow {
25862                skip_soft_wrap: false,
25863            },
25864            window,
25865            cx,
25866        );
25867
25868        assert_eq!(
25869            editor.selections.display_ranges(cx),
25870            &[
25871                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25872                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25873            ]
25874        );
25875
25876        editor.add_selection_above(
25877            &AddSelectionAbove {
25878                skip_soft_wrap: false,
25879            },
25880            window,
25881            cx,
25882        );
25883
25884        assert_eq!(
25885            editor.selections.display_ranges(cx),
25886            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25887        );
25888    });
25889}
25890
25891#[gpui::test(iterations = 10)]
25892async fn test_document_colors(cx: &mut TestAppContext) {
25893    let expected_color = Rgba {
25894        r: 0.33,
25895        g: 0.33,
25896        b: 0.33,
25897        a: 0.33,
25898    };
25899
25900    init_test(cx, |_| {});
25901
25902    let fs = FakeFs::new(cx.executor());
25903    fs.insert_tree(
25904        path!("/a"),
25905        json!({
25906            "first.rs": "fn main() { let a = 5; }",
25907        }),
25908    )
25909    .await;
25910
25911    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25912    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25913    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25914
25915    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25916    language_registry.add(rust_lang());
25917    let mut fake_servers = language_registry.register_fake_lsp(
25918        "Rust",
25919        FakeLspAdapter {
25920            capabilities: lsp::ServerCapabilities {
25921                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25922                ..lsp::ServerCapabilities::default()
25923            },
25924            name: "rust-analyzer",
25925            ..FakeLspAdapter::default()
25926        },
25927    );
25928    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25929        "Rust",
25930        FakeLspAdapter {
25931            capabilities: lsp::ServerCapabilities {
25932                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25933                ..lsp::ServerCapabilities::default()
25934            },
25935            name: "not-rust-analyzer",
25936            ..FakeLspAdapter::default()
25937        },
25938    );
25939
25940    let editor = workspace
25941        .update(cx, |workspace, window, cx| {
25942            workspace.open_abs_path(
25943                PathBuf::from(path!("/a/first.rs")),
25944                OpenOptions::default(),
25945                window,
25946                cx,
25947            )
25948        })
25949        .unwrap()
25950        .await
25951        .unwrap()
25952        .downcast::<Editor>()
25953        .unwrap();
25954    let fake_language_server = fake_servers.next().await.unwrap();
25955    let fake_language_server_without_capabilities =
25956        fake_servers_without_capabilities.next().await.unwrap();
25957    let requests_made = Arc::new(AtomicUsize::new(0));
25958    let closure_requests_made = Arc::clone(&requests_made);
25959    let mut color_request_handle = fake_language_server
25960        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25961            let requests_made = Arc::clone(&closure_requests_made);
25962            async move {
25963                assert_eq!(
25964                    params.text_document.uri,
25965                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25966                );
25967                requests_made.fetch_add(1, atomic::Ordering::Release);
25968                Ok(vec![
25969                    lsp::ColorInformation {
25970                        range: lsp::Range {
25971                            start: lsp::Position {
25972                                line: 0,
25973                                character: 0,
25974                            },
25975                            end: lsp::Position {
25976                                line: 0,
25977                                character: 1,
25978                            },
25979                        },
25980                        color: lsp::Color {
25981                            red: 0.33,
25982                            green: 0.33,
25983                            blue: 0.33,
25984                            alpha: 0.33,
25985                        },
25986                    },
25987                    lsp::ColorInformation {
25988                        range: lsp::Range {
25989                            start: lsp::Position {
25990                                line: 0,
25991                                character: 0,
25992                            },
25993                            end: lsp::Position {
25994                                line: 0,
25995                                character: 1,
25996                            },
25997                        },
25998                        color: lsp::Color {
25999                            red: 0.33,
26000                            green: 0.33,
26001                            blue: 0.33,
26002                            alpha: 0.33,
26003                        },
26004                    },
26005                ])
26006            }
26007        });
26008
26009    let _handle = fake_language_server_without_capabilities
26010        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26011            panic!("Should not be called");
26012        });
26013    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26014    color_request_handle.next().await.unwrap();
26015    cx.run_until_parked();
26016    assert_eq!(
26017        1,
26018        requests_made.load(atomic::Ordering::Acquire),
26019        "Should query for colors once per editor open"
26020    );
26021    editor.update_in(cx, |editor, _, cx| {
26022        assert_eq!(
26023            vec![expected_color],
26024            extract_color_inlays(editor, cx),
26025            "Should have an initial inlay"
26026        );
26027    });
26028
26029    // opening another file in a split should not influence the LSP query counter
26030    workspace
26031        .update(cx, |workspace, window, cx| {
26032            assert_eq!(
26033                workspace.panes().len(),
26034                1,
26035                "Should have one pane with one editor"
26036            );
26037            workspace.move_item_to_pane_in_direction(
26038                &MoveItemToPaneInDirection {
26039                    direction: SplitDirection::Right,
26040                    focus: false,
26041                    clone: true,
26042                },
26043                window,
26044                cx,
26045            );
26046        })
26047        .unwrap();
26048    cx.run_until_parked();
26049    workspace
26050        .update(cx, |workspace, _, cx| {
26051            let panes = workspace.panes();
26052            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26053            for pane in panes {
26054                let editor = pane
26055                    .read(cx)
26056                    .active_item()
26057                    .and_then(|item| item.downcast::<Editor>())
26058                    .expect("Should have opened an editor in each split");
26059                let editor_file = editor
26060                    .read(cx)
26061                    .buffer()
26062                    .read(cx)
26063                    .as_singleton()
26064                    .expect("test deals with singleton buffers")
26065                    .read(cx)
26066                    .file()
26067                    .expect("test buffese should have a file")
26068                    .path();
26069                assert_eq!(
26070                    editor_file.as_ref(),
26071                    rel_path("first.rs"),
26072                    "Both editors should be opened for the same file"
26073                )
26074            }
26075        })
26076        .unwrap();
26077
26078    cx.executor().advance_clock(Duration::from_millis(500));
26079    let save = editor.update_in(cx, |editor, window, cx| {
26080        editor.move_to_end(&MoveToEnd, window, cx);
26081        editor.handle_input("dirty", window, cx);
26082        editor.save(
26083            SaveOptions {
26084                format: true,
26085                autosave: true,
26086            },
26087            project.clone(),
26088            window,
26089            cx,
26090        )
26091    });
26092    save.await.unwrap();
26093
26094    color_request_handle.next().await.unwrap();
26095    cx.run_until_parked();
26096    assert_eq!(
26097        2,
26098        requests_made.load(atomic::Ordering::Acquire),
26099        "Should query for colors once per save (deduplicated) and once per formatting after save"
26100    );
26101
26102    drop(editor);
26103    let close = workspace
26104        .update(cx, |workspace, window, cx| {
26105            workspace.active_pane().update(cx, |pane, cx| {
26106                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26107            })
26108        })
26109        .unwrap();
26110    close.await.unwrap();
26111    let close = workspace
26112        .update(cx, |workspace, window, cx| {
26113            workspace.active_pane().update(cx, |pane, cx| {
26114                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26115            })
26116        })
26117        .unwrap();
26118    close.await.unwrap();
26119    assert_eq!(
26120        2,
26121        requests_made.load(atomic::Ordering::Acquire),
26122        "After saving and closing all editors, no extra requests should be made"
26123    );
26124    workspace
26125        .update(cx, |workspace, _, cx| {
26126            assert!(
26127                workspace.active_item(cx).is_none(),
26128                "Should close all editors"
26129            )
26130        })
26131        .unwrap();
26132
26133    workspace
26134        .update(cx, |workspace, window, cx| {
26135            workspace.active_pane().update(cx, |pane, cx| {
26136                pane.navigate_backward(&workspace::GoBack, window, cx);
26137            })
26138        })
26139        .unwrap();
26140    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26141    cx.run_until_parked();
26142    let editor = workspace
26143        .update(cx, |workspace, _, cx| {
26144            workspace
26145                .active_item(cx)
26146                .expect("Should have reopened the editor again after navigating back")
26147                .downcast::<Editor>()
26148                .expect("Should be an editor")
26149        })
26150        .unwrap();
26151
26152    assert_eq!(
26153        2,
26154        requests_made.load(atomic::Ordering::Acquire),
26155        "Cache should be reused on buffer close and reopen"
26156    );
26157    editor.update(cx, |editor, cx| {
26158        assert_eq!(
26159            vec![expected_color],
26160            extract_color_inlays(editor, cx),
26161            "Should have an initial inlay"
26162        );
26163    });
26164
26165    drop(color_request_handle);
26166    let closure_requests_made = Arc::clone(&requests_made);
26167    let mut empty_color_request_handle = fake_language_server
26168        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26169            let requests_made = Arc::clone(&closure_requests_made);
26170            async move {
26171                assert_eq!(
26172                    params.text_document.uri,
26173                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26174                );
26175                requests_made.fetch_add(1, atomic::Ordering::Release);
26176                Ok(Vec::new())
26177            }
26178        });
26179    let save = editor.update_in(cx, |editor, window, cx| {
26180        editor.move_to_end(&MoveToEnd, window, cx);
26181        editor.handle_input("dirty_again", window, cx);
26182        editor.save(
26183            SaveOptions {
26184                format: false,
26185                autosave: true,
26186            },
26187            project.clone(),
26188            window,
26189            cx,
26190        )
26191    });
26192    save.await.unwrap();
26193
26194    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26195    empty_color_request_handle.next().await.unwrap();
26196    cx.run_until_parked();
26197    assert_eq!(
26198        3,
26199        requests_made.load(atomic::Ordering::Acquire),
26200        "Should query for colors once per save only, as formatting was not requested"
26201    );
26202    editor.update(cx, |editor, cx| {
26203        assert_eq!(
26204            Vec::<Rgba>::new(),
26205            extract_color_inlays(editor, cx),
26206            "Should clear all colors when the server returns an empty response"
26207        );
26208    });
26209}
26210
26211#[gpui::test]
26212async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26213    init_test(cx, |_| {});
26214    let (editor, cx) = cx.add_window_view(Editor::single_line);
26215    editor.update_in(cx, |editor, window, cx| {
26216        editor.set_text("oops\n\nwow\n", window, cx)
26217    });
26218    cx.run_until_parked();
26219    editor.update(cx, |editor, cx| {
26220        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26221    });
26222    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26223    cx.run_until_parked();
26224    editor.update(cx, |editor, cx| {
26225        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26226    });
26227}
26228
26229#[gpui::test]
26230async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26231    init_test(cx, |_| {});
26232
26233    cx.update(|cx| {
26234        register_project_item::<Editor>(cx);
26235    });
26236
26237    let fs = FakeFs::new(cx.executor());
26238    fs.insert_tree("/root1", json!({})).await;
26239    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26240        .await;
26241
26242    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26243    let (workspace, cx) =
26244        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26245
26246    let worktree_id = project.update(cx, |project, cx| {
26247        project.worktrees(cx).next().unwrap().read(cx).id()
26248    });
26249
26250    let handle = workspace
26251        .update_in(cx, |workspace, window, cx| {
26252            let project_path = (worktree_id, rel_path("one.pdf"));
26253            workspace.open_path(project_path, None, true, window, cx)
26254        })
26255        .await
26256        .unwrap();
26257
26258    assert_eq!(
26259        handle.to_any().entity_type(),
26260        TypeId::of::<InvalidItemView>()
26261    );
26262}
26263
26264#[gpui::test]
26265async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26266    init_test(cx, |_| {});
26267
26268    let language = Arc::new(Language::new(
26269        LanguageConfig::default(),
26270        Some(tree_sitter_rust::LANGUAGE.into()),
26271    ));
26272
26273    // Test hierarchical sibling navigation
26274    let text = r#"
26275        fn outer() {
26276            if condition {
26277                let a = 1;
26278            }
26279            let b = 2;
26280        }
26281
26282        fn another() {
26283            let c = 3;
26284        }
26285    "#;
26286
26287    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26288    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26289    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26290
26291    // Wait for parsing to complete
26292    editor
26293        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26294        .await;
26295
26296    editor.update_in(cx, |editor, window, cx| {
26297        // Start by selecting "let a = 1;" inside the if block
26298        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26299            s.select_display_ranges([
26300                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26301            ]);
26302        });
26303
26304        let initial_selection = editor.selections.display_ranges(cx);
26305        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26306
26307        // Test select next sibling - should move up levels to find the next sibling
26308        // Since "let a = 1;" has no siblings in the if block, it should move up
26309        // to find "let b = 2;" which is a sibling of the if block
26310        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26311        let next_selection = editor.selections.display_ranges(cx);
26312
26313        // Should have a selection and it should be different from the initial
26314        assert_eq!(
26315            next_selection.len(),
26316            1,
26317            "Should have one selection after next"
26318        );
26319        assert_ne!(
26320            next_selection[0], initial_selection[0],
26321            "Next sibling selection should be different"
26322        );
26323
26324        // Test hierarchical navigation by going to the end of the current function
26325        // and trying to navigate to the next function
26326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26327            s.select_display_ranges([
26328                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26329            ]);
26330        });
26331
26332        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26333        let function_next_selection = editor.selections.display_ranges(cx);
26334
26335        // Should move to the next function
26336        assert_eq!(
26337            function_next_selection.len(),
26338            1,
26339            "Should have one selection after function next"
26340        );
26341
26342        // Test select previous sibling navigation
26343        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26344        let prev_selection = editor.selections.display_ranges(cx);
26345
26346        // Should have a selection and it should be different
26347        assert_eq!(
26348            prev_selection.len(),
26349            1,
26350            "Should have one selection after prev"
26351        );
26352        assert_ne!(
26353            prev_selection[0], function_next_selection[0],
26354            "Previous sibling selection should be different from next"
26355        );
26356    });
26357}
26358
26359#[gpui::test]
26360async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26361    init_test(cx, |_| {});
26362
26363    let mut cx = EditorTestContext::new(cx).await;
26364    cx.set_state(
26365        "let ˇvariable = 42;
26366let another = variable + 1;
26367let result = variable * 2;",
26368    );
26369
26370    // Set up document highlights manually (simulating LSP response)
26371    cx.update_editor(|editor, _window, cx| {
26372        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26373
26374        // Create highlights for "variable" occurrences
26375        let highlight_ranges = [
26376            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26377            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26378            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26379        ];
26380
26381        let anchor_ranges: Vec<_> = highlight_ranges
26382            .iter()
26383            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26384            .collect();
26385
26386        editor.highlight_background::<DocumentHighlightRead>(
26387            &anchor_ranges,
26388            |theme| theme.colors().editor_document_highlight_read_background,
26389            cx,
26390        );
26391    });
26392
26393    // Go to next highlight - should move to second "variable"
26394    cx.update_editor(|editor, window, cx| {
26395        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26396    });
26397    cx.assert_editor_state(
26398        "let variable = 42;
26399let another = ˇvariable + 1;
26400let result = variable * 2;",
26401    );
26402
26403    // Go to next highlight - should move to third "variable"
26404    cx.update_editor(|editor, window, cx| {
26405        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26406    });
26407    cx.assert_editor_state(
26408        "let variable = 42;
26409let another = variable + 1;
26410let result = ˇvariable * 2;",
26411    );
26412
26413    // Go to next highlight - should stay at third "variable" (no wrap-around)
26414    cx.update_editor(|editor, window, cx| {
26415        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26416    });
26417    cx.assert_editor_state(
26418        "let variable = 42;
26419let another = variable + 1;
26420let result = ˇvariable * 2;",
26421    );
26422
26423    // Now test going backwards from third position
26424    cx.update_editor(|editor, window, cx| {
26425        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26426    });
26427    cx.assert_editor_state(
26428        "let variable = 42;
26429let another = ˇvariable + 1;
26430let result = variable * 2;",
26431    );
26432
26433    // Go to previous highlight - should move to first "variable"
26434    cx.update_editor(|editor, window, cx| {
26435        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26436    });
26437    cx.assert_editor_state(
26438        "let ˇvariable = 42;
26439let another = variable + 1;
26440let result = variable * 2;",
26441    );
26442
26443    // Go to previous highlight - should stay on first "variable"
26444    cx.update_editor(|editor, window, cx| {
26445        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26446    });
26447    cx.assert_editor_state(
26448        "let ˇvariable = 42;
26449let another = variable + 1;
26450let result = variable * 2;",
26451    );
26452}
26453
26454#[gpui::test]
26455async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26456    cx: &mut gpui::TestAppContext,
26457) {
26458    init_test(cx, |_| {});
26459
26460    let url = "https://zed.dev";
26461
26462    let markdown_language = Arc::new(Language::new(
26463        LanguageConfig {
26464            name: "Markdown".into(),
26465            ..LanguageConfig::default()
26466        },
26467        None,
26468    ));
26469
26470    let mut cx = EditorTestContext::new(cx).await;
26471    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26472    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26473
26474    cx.update_editor(|editor, window, cx| {
26475        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26476        editor.paste(&Paste, window, cx);
26477    });
26478
26479    cx.assert_editor_state(&format!(
26480        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26481    ));
26482}
26483
26484#[gpui::test]
26485async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26486    cx: &mut gpui::TestAppContext,
26487) {
26488    init_test(cx, |_| {});
26489
26490    let url = "https://zed.dev";
26491
26492    let markdown_language = Arc::new(Language::new(
26493        LanguageConfig {
26494            name: "Markdown".into(),
26495            ..LanguageConfig::default()
26496        },
26497        None,
26498    ));
26499
26500    let mut cx = EditorTestContext::new(cx).await;
26501    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26502    cx.set_state(&format!(
26503        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26504    ));
26505
26506    cx.update_editor(|editor, window, cx| {
26507        editor.copy(&Copy, window, cx);
26508    });
26509
26510    cx.set_state(&format!(
26511        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26512    ));
26513
26514    cx.update_editor(|editor, window, cx| {
26515        editor.paste(&Paste, window, cx);
26516    });
26517
26518    cx.assert_editor_state(&format!(
26519        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26520    ));
26521}
26522
26523#[gpui::test]
26524async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26525    cx: &mut gpui::TestAppContext,
26526) {
26527    init_test(cx, |_| {});
26528
26529    let url = "https://zed.dev";
26530
26531    let markdown_language = Arc::new(Language::new(
26532        LanguageConfig {
26533            name: "Markdown".into(),
26534            ..LanguageConfig::default()
26535        },
26536        None,
26537    ));
26538
26539    let mut cx = EditorTestContext::new(cx).await;
26540    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26541    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26542
26543    cx.update_editor(|editor, window, cx| {
26544        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26545        editor.paste(&Paste, window, cx);
26546    });
26547
26548    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26549}
26550
26551#[gpui::test]
26552async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26553    cx: &mut gpui::TestAppContext,
26554) {
26555    init_test(cx, |_| {});
26556
26557    let text = "Awesome";
26558
26559    let markdown_language = Arc::new(Language::new(
26560        LanguageConfig {
26561            name: "Markdown".into(),
26562            ..LanguageConfig::default()
26563        },
26564        None,
26565    ));
26566
26567    let mut cx = EditorTestContext::new(cx).await;
26568    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26569    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26570
26571    cx.update_editor(|editor, window, cx| {
26572        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26573        editor.paste(&Paste, window, cx);
26574    });
26575
26576    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26577}
26578
26579#[gpui::test]
26580async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26581    cx: &mut gpui::TestAppContext,
26582) {
26583    init_test(cx, |_| {});
26584
26585    let url = "https://zed.dev";
26586
26587    let markdown_language = Arc::new(Language::new(
26588        LanguageConfig {
26589            name: "Rust".into(),
26590            ..LanguageConfig::default()
26591        },
26592        None,
26593    ));
26594
26595    let mut cx = EditorTestContext::new(cx).await;
26596    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26597    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26598
26599    cx.update_editor(|editor, window, cx| {
26600        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26601        editor.paste(&Paste, window, cx);
26602    });
26603
26604    cx.assert_editor_state(&format!(
26605        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26606    ));
26607}
26608
26609#[gpui::test]
26610async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26611    cx: &mut TestAppContext,
26612) {
26613    init_test(cx, |_| {});
26614
26615    let url = "https://zed.dev";
26616
26617    let markdown_language = Arc::new(Language::new(
26618        LanguageConfig {
26619            name: "Markdown".into(),
26620            ..LanguageConfig::default()
26621        },
26622        None,
26623    ));
26624
26625    let (editor, cx) = cx.add_window_view(|window, cx| {
26626        let multi_buffer = MultiBuffer::build_multi(
26627            [
26628                ("this will embed -> link", vec![Point::row_range(0..1)]),
26629                ("this will replace -> link", vec![Point::row_range(0..1)]),
26630            ],
26631            cx,
26632        );
26633        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26635            s.select_ranges(vec![
26636                Point::new(0, 19)..Point::new(0, 23),
26637                Point::new(1, 21)..Point::new(1, 25),
26638            ])
26639        });
26640        let first_buffer_id = multi_buffer
26641            .read(cx)
26642            .excerpt_buffer_ids()
26643            .into_iter()
26644            .next()
26645            .unwrap();
26646        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26647        first_buffer.update(cx, |buffer, cx| {
26648            buffer.set_language(Some(markdown_language.clone()), cx);
26649        });
26650
26651        editor
26652    });
26653    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26654
26655    cx.update_editor(|editor, window, cx| {
26656        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26657        editor.paste(&Paste, window, cx);
26658    });
26659
26660    cx.assert_editor_state(&format!(
26661        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26662    ));
26663}
26664
26665#[gpui::test]
26666async fn test_non_linux_line_endings_registration(cx: &mut TestAppContext) {
26667    init_test(cx, |_| {});
26668
26669    let unix_newlines_file_text = "fn main() {
26670        let a = 5;
26671    }";
26672    let clrf_file_text = unix_newlines_file_text.lines().join("\r\n");
26673
26674    let fs = FakeFs::new(cx.executor());
26675    fs.insert_tree(
26676        path!("/a"),
26677        json!({
26678            "first.rs": &clrf_file_text,
26679        }),
26680    )
26681    .await;
26682
26683    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26684    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26685    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26686
26687    let registered_text = Arc::new(Mutex::new(Vec::new()));
26688    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26689    language_registry.add(rust_lang());
26690    let mut fake_servers = language_registry.register_fake_lsp(
26691        "Rust",
26692        FakeLspAdapter {
26693            capabilities: lsp::ServerCapabilities {
26694                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26695                ..lsp::ServerCapabilities::default()
26696            },
26697            name: "rust-analyzer",
26698            initializer: Some({
26699                let registered_text = registered_text.clone();
26700                Box::new(move |fake_server| {
26701                    fake_server.handle_notification::<lsp::notification::DidOpenTextDocument, _>({
26702                        let registered_text = registered_text.clone();
26703                        move |params, _| {
26704                            registered_text.lock().push(params.text_document.text);
26705                        }
26706                    });
26707                })
26708            }),
26709            ..FakeLspAdapter::default()
26710        },
26711    );
26712
26713    let editor = workspace
26714        .update(cx, |workspace, window, cx| {
26715            workspace.open_abs_path(
26716                PathBuf::from(path!("/a/first.rs")),
26717                OpenOptions::default(),
26718                window,
26719                cx,
26720            )
26721        })
26722        .unwrap()
26723        .await
26724        .unwrap()
26725        .downcast::<Editor>()
26726        .unwrap();
26727    let _fake_language_server = fake_servers.next().await.unwrap();
26728    cx.executor().run_until_parked();
26729
26730    assert_eq!(
26731        editor.update(cx, |editor, cx| editor.text(cx)),
26732        unix_newlines_file_text,
26733        "Default text API returns \n-separated text",
26734    );
26735    assert_eq!(
26736        vec![clrf_file_text],
26737        registered_text.lock().drain(..).collect::<Vec<_>>(),
26738        "Expected the language server to receive the exact same text from the FS",
26739    );
26740}
26741
26742#[gpui::test]
26743async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26744    init_test(cx, |_| {});
26745
26746    let fs = FakeFs::new(cx.executor());
26747    fs.insert_tree(
26748        path!("/project"),
26749        json!({
26750            "first.rs": "# First Document\nSome content here.",
26751            "second.rs": "Plain text content for second file.",
26752        }),
26753    )
26754    .await;
26755
26756    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26758    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26759
26760    let language = rust_lang();
26761    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26762    language_registry.add(language.clone());
26763    let mut fake_servers = language_registry.register_fake_lsp(
26764        "Rust",
26765        FakeLspAdapter {
26766            ..FakeLspAdapter::default()
26767        },
26768    );
26769
26770    let buffer1 = project
26771        .update(cx, |project, cx| {
26772            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26773        })
26774        .await
26775        .unwrap();
26776    let buffer2 = project
26777        .update(cx, |project, cx| {
26778            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26779        })
26780        .await
26781        .unwrap();
26782
26783    let multi_buffer = cx.new(|cx| {
26784        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26785        multi_buffer.set_excerpts_for_path(
26786            PathKey::for_buffer(&buffer1, cx),
26787            buffer1.clone(),
26788            [Point::zero()..buffer1.read(cx).max_point()],
26789            3,
26790            cx,
26791        );
26792        multi_buffer.set_excerpts_for_path(
26793            PathKey::for_buffer(&buffer2, cx),
26794            buffer2.clone(),
26795            [Point::zero()..buffer1.read(cx).max_point()],
26796            3,
26797            cx,
26798        );
26799        multi_buffer
26800    });
26801
26802    let (editor, cx) = cx.add_window_view(|window, cx| {
26803        Editor::new(
26804            EditorMode::full(),
26805            multi_buffer,
26806            Some(project.clone()),
26807            window,
26808            cx,
26809        )
26810    });
26811
26812    let fake_language_server = fake_servers.next().await.unwrap();
26813
26814    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26815
26816    let save = editor.update_in(cx, |editor, window, cx| {
26817        assert!(editor.is_dirty(cx));
26818
26819        editor.save(
26820            SaveOptions {
26821                format: true,
26822                autosave: true,
26823            },
26824            project,
26825            window,
26826            cx,
26827        )
26828    });
26829    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26830    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26831    let mut done_edit_rx = Some(done_edit_rx);
26832    let mut start_edit_tx = Some(start_edit_tx);
26833
26834    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26835        start_edit_tx.take().unwrap().send(()).unwrap();
26836        let done_edit_rx = done_edit_rx.take().unwrap();
26837        async move {
26838            done_edit_rx.await.unwrap();
26839            Ok(None)
26840        }
26841    });
26842
26843    start_edit_rx.await.unwrap();
26844    buffer2
26845        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26846        .unwrap();
26847
26848    done_edit_tx.send(()).unwrap();
26849
26850    save.await.unwrap();
26851    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26852}
26853
26854#[track_caller]
26855fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26856    editor
26857        .all_inlays(cx)
26858        .into_iter()
26859        .filter_map(|inlay| inlay.get_color())
26860        .map(Rgba::from)
26861        .collect()
26862}
26863
26864#[gpui::test]
26865fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26866    init_test(cx, |_| {});
26867
26868    let editor = cx.add_window(|window, cx| {
26869        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26870        build_editor(buffer, window, cx)
26871    });
26872
26873    editor
26874        .update(cx, |editor, window, cx| {
26875            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26876                s.select_display_ranges([
26877                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26878                ])
26879            });
26880
26881            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26882
26883            assert_eq!(
26884                editor.display_text(cx),
26885                "line1\nline2\nline2",
26886                "Duplicating last line upward should create duplicate above, not on same line"
26887            );
26888
26889            assert_eq!(
26890                editor.selections.display_ranges(cx),
26891                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26892                "Selection should move to the duplicated line"
26893            );
26894        })
26895        .unwrap();
26896}
26897
26898#[gpui::test]
26899async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26900    init_test(cx, |_| {});
26901
26902    let mut cx = EditorTestContext::new(cx).await;
26903
26904    cx.set_state("line1\nline2ˇ");
26905
26906    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26907
26908    let clipboard_text = cx
26909        .read_from_clipboard()
26910        .and_then(|item| item.text().as_deref().map(str::to_string));
26911
26912    assert_eq!(
26913        clipboard_text,
26914        Some("line2\n".to_string()),
26915        "Copying a line without trailing newline should include a newline"
26916    );
26917
26918    cx.set_state("line1\nˇ");
26919
26920    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26921
26922    cx.assert_editor_state("line1\nline2\nˇ");
26923}
26924
26925#[gpui::test]
26926async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26927    init_test(cx, |_| {});
26928
26929    let mut cx = EditorTestContext::new(cx).await;
26930
26931    cx.set_state("line1\nline2ˇ");
26932    cx.update_editor(|e, window, cx| {
26933        e.set_mode(EditorMode::SingleLine);
26934        assert!(e.key_context(window, cx).contains("end_of_input"));
26935    });
26936    cx.set_state("ˇline1\nline2");
26937    cx.update_editor(|e, window, cx| {
26938        assert!(!e.key_context(window, cx).contains("end_of_input"));
26939    });
26940    cx.set_state("line1ˇ\nline2");
26941    cx.update_editor(|e, window, cx| {
26942        assert!(!e.key_context(window, cx).contains("end_of_input"));
26943    });
26944}