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 `move_upwards` the selections stay in place, except for
 5650    // the lines inserted above them
 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(6), 0)..DisplayPoint::new(DisplayRow(6), 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    cx.run_until_parked();
12619    // Set up a buffer white some trailing whitespace and no trailing newline.
12620    cx.set_state(
12621        &[
12622            "one ",   //
12623            "twoˇ",   //
12624            "three ", //
12625            "four",   //
12626        ]
12627        .join("\n"),
12628    );
12629
12630    // Record which buffer changes have been sent to the language server
12631    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12632    cx.lsp
12633        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12634            let buffer_changes = buffer_changes.clone();
12635            move |params, _| {
12636                buffer_changes.lock().extend(
12637                    params
12638                        .content_changes
12639                        .into_iter()
12640                        .map(|e| (e.range.unwrap(), e.text)),
12641                );
12642            }
12643        });
12644    cx.run_until_parked();
12645
12646    // Handle formatting requests to the language server.
12647    cx.lsp
12648        .set_request_handler::<lsp::request::Formatting, _, _>({
12649            let buffer_changes = buffer_changes.clone();
12650            move |_, _| {
12651                let buffer_changes = buffer_changes.clone();
12652                // Insert blank lines between each line of the buffer.
12653                async move {
12654                    // When formatting is requested, trailing whitespace has already been stripped,
12655                    // and the trailing newline has already been added.
12656                    assert_eq!(
12657                        &buffer_changes.lock()[1..],
12658                        &[
12659                            (
12660                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12661                                "".into()
12662                            ),
12663                            (
12664                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12665                                "".into()
12666                            ),
12667                            (
12668                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12669                                "\n".into()
12670                            ),
12671                        ]
12672                    );
12673
12674                    Ok(Some(vec![
12675                        lsp::TextEdit {
12676                            range: lsp::Range::new(
12677                                lsp::Position::new(1, 0),
12678                                lsp::Position::new(1, 0),
12679                            ),
12680                            new_text: "\n".into(),
12681                        },
12682                        lsp::TextEdit {
12683                            range: lsp::Range::new(
12684                                lsp::Position::new(2, 0),
12685                                lsp::Position::new(2, 0),
12686                            ),
12687                            new_text: "\n".into(),
12688                        },
12689                    ]))
12690                }
12691            }
12692        });
12693
12694    // Submit a format request.
12695    let format = cx
12696        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12697        .unwrap();
12698
12699    cx.run_until_parked();
12700    // After formatting the buffer, the trailing whitespace is stripped,
12701    // a newline is appended, and the edits provided by the language server
12702    // have been applied.
12703    format.await.unwrap();
12704
12705    cx.assert_editor_state(
12706        &[
12707            "one",   //
12708            "",      //
12709            "twoˇ",  //
12710            "",      //
12711            "three", //
12712            "four",  //
12713            "",      //
12714        ]
12715        .join("\n"),
12716    );
12717
12718    // Undoing the formatting undoes the trailing whitespace removal, the
12719    // trailing newline, and the LSP edits.
12720    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12721    cx.assert_editor_state(
12722        &[
12723            "one ",   //
12724            "twoˇ",   //
12725            "three ", //
12726            "four",   //
12727        ]
12728        .join("\n"),
12729    );
12730}
12731
12732#[gpui::test]
12733async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12734    cx: &mut TestAppContext,
12735) {
12736    init_test(cx, |_| {});
12737
12738    cx.update(|cx| {
12739        cx.update_global::<SettingsStore, _>(|settings, cx| {
12740            settings.update_user_settings(cx, |settings| {
12741                settings.editor.auto_signature_help = Some(true);
12742            });
12743        });
12744    });
12745
12746    let mut cx = EditorLspTestContext::new_rust(
12747        lsp::ServerCapabilities {
12748            signature_help_provider: Some(lsp::SignatureHelpOptions {
12749                ..Default::default()
12750            }),
12751            ..Default::default()
12752        },
12753        cx,
12754    )
12755    .await;
12756
12757    let language = Language::new(
12758        LanguageConfig {
12759            name: "Rust".into(),
12760            brackets: BracketPairConfig {
12761                pairs: vec![
12762                    BracketPair {
12763                        start: "{".to_string(),
12764                        end: "}".to_string(),
12765                        close: true,
12766                        surround: true,
12767                        newline: true,
12768                    },
12769                    BracketPair {
12770                        start: "(".to_string(),
12771                        end: ")".to_string(),
12772                        close: true,
12773                        surround: true,
12774                        newline: true,
12775                    },
12776                    BracketPair {
12777                        start: "/*".to_string(),
12778                        end: " */".to_string(),
12779                        close: true,
12780                        surround: true,
12781                        newline: true,
12782                    },
12783                    BracketPair {
12784                        start: "[".to_string(),
12785                        end: "]".to_string(),
12786                        close: false,
12787                        surround: false,
12788                        newline: true,
12789                    },
12790                    BracketPair {
12791                        start: "\"".to_string(),
12792                        end: "\"".to_string(),
12793                        close: true,
12794                        surround: true,
12795                        newline: false,
12796                    },
12797                    BracketPair {
12798                        start: "<".to_string(),
12799                        end: ">".to_string(),
12800                        close: false,
12801                        surround: true,
12802                        newline: true,
12803                    },
12804                ],
12805                ..Default::default()
12806            },
12807            autoclose_before: "})]".to_string(),
12808            ..Default::default()
12809        },
12810        Some(tree_sitter_rust::LANGUAGE.into()),
12811    );
12812    let language = Arc::new(language);
12813
12814    cx.language_registry().add(language.clone());
12815    cx.update_buffer(|buffer, cx| {
12816        buffer.set_language(Some(language), cx);
12817    });
12818
12819    cx.set_state(
12820        &r#"
12821            fn main() {
12822                sampleˇ
12823            }
12824        "#
12825        .unindent(),
12826    );
12827
12828    cx.update_editor(|editor, window, cx| {
12829        editor.handle_input("(", window, cx);
12830    });
12831    cx.assert_editor_state(
12832        &"
12833            fn main() {
12834                sample(ˇ)
12835            }
12836        "
12837        .unindent(),
12838    );
12839
12840    let mocked_response = lsp::SignatureHelp {
12841        signatures: vec![lsp::SignatureInformation {
12842            label: "fn sample(param1: u8, param2: u8)".to_string(),
12843            documentation: None,
12844            parameters: Some(vec![
12845                lsp::ParameterInformation {
12846                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12847                    documentation: None,
12848                },
12849                lsp::ParameterInformation {
12850                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12851                    documentation: None,
12852                },
12853            ]),
12854            active_parameter: None,
12855        }],
12856        active_signature: Some(0),
12857        active_parameter: Some(0),
12858    };
12859    handle_signature_help_request(&mut cx, mocked_response).await;
12860
12861    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12862        .await;
12863
12864    cx.editor(|editor, _, _| {
12865        let signature_help_state = editor.signature_help_state.popover().cloned();
12866        let signature = signature_help_state.unwrap();
12867        assert_eq!(
12868            signature.signatures[signature.current_signature].label,
12869            "fn sample(param1: u8, param2: u8)"
12870        );
12871    });
12872}
12873
12874#[gpui::test]
12875async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12876    init_test(cx, |_| {});
12877
12878    cx.update(|cx| {
12879        cx.update_global::<SettingsStore, _>(|settings, cx| {
12880            settings.update_user_settings(cx, |settings| {
12881                settings.editor.auto_signature_help = Some(false);
12882                settings.editor.show_signature_help_after_edits = Some(false);
12883            });
12884        });
12885    });
12886
12887    let mut cx = EditorLspTestContext::new_rust(
12888        lsp::ServerCapabilities {
12889            signature_help_provider: Some(lsp::SignatureHelpOptions {
12890                ..Default::default()
12891            }),
12892            ..Default::default()
12893        },
12894        cx,
12895    )
12896    .await;
12897
12898    let language = Language::new(
12899        LanguageConfig {
12900            name: "Rust".into(),
12901            brackets: BracketPairConfig {
12902                pairs: vec![
12903                    BracketPair {
12904                        start: "{".to_string(),
12905                        end: "}".to_string(),
12906                        close: true,
12907                        surround: true,
12908                        newline: true,
12909                    },
12910                    BracketPair {
12911                        start: "(".to_string(),
12912                        end: ")".to_string(),
12913                        close: true,
12914                        surround: true,
12915                        newline: true,
12916                    },
12917                    BracketPair {
12918                        start: "/*".to_string(),
12919                        end: " */".to_string(),
12920                        close: true,
12921                        surround: true,
12922                        newline: true,
12923                    },
12924                    BracketPair {
12925                        start: "[".to_string(),
12926                        end: "]".to_string(),
12927                        close: false,
12928                        surround: false,
12929                        newline: true,
12930                    },
12931                    BracketPair {
12932                        start: "\"".to_string(),
12933                        end: "\"".to_string(),
12934                        close: true,
12935                        surround: true,
12936                        newline: false,
12937                    },
12938                    BracketPair {
12939                        start: "<".to_string(),
12940                        end: ">".to_string(),
12941                        close: false,
12942                        surround: true,
12943                        newline: true,
12944                    },
12945                ],
12946                ..Default::default()
12947            },
12948            autoclose_before: "})]".to_string(),
12949            ..Default::default()
12950        },
12951        Some(tree_sitter_rust::LANGUAGE.into()),
12952    );
12953    let language = Arc::new(language);
12954
12955    cx.language_registry().add(language.clone());
12956    cx.update_buffer(|buffer, cx| {
12957        buffer.set_language(Some(language), cx);
12958    });
12959
12960    // Ensure that signature_help is not called when no signature help is enabled.
12961    cx.set_state(
12962        &r#"
12963            fn main() {
12964                sampleˇ
12965            }
12966        "#
12967        .unindent(),
12968    );
12969    cx.update_editor(|editor, window, cx| {
12970        editor.handle_input("(", window, cx);
12971    });
12972    cx.assert_editor_state(
12973        &"
12974            fn main() {
12975                sample(ˇ)
12976            }
12977        "
12978        .unindent(),
12979    );
12980    cx.editor(|editor, _, _| {
12981        assert!(editor.signature_help_state.task().is_none());
12982    });
12983
12984    let mocked_response = lsp::SignatureHelp {
12985        signatures: vec![lsp::SignatureInformation {
12986            label: "fn sample(param1: u8, param2: u8)".to_string(),
12987            documentation: None,
12988            parameters: Some(vec![
12989                lsp::ParameterInformation {
12990                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12991                    documentation: None,
12992                },
12993                lsp::ParameterInformation {
12994                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12995                    documentation: None,
12996                },
12997            ]),
12998            active_parameter: None,
12999        }],
13000        active_signature: Some(0),
13001        active_parameter: Some(0),
13002    };
13003
13004    // Ensure that signature_help is called when enabled afte edits
13005    cx.update(|_, cx| {
13006        cx.update_global::<SettingsStore, _>(|settings, cx| {
13007            settings.update_user_settings(cx, |settings| {
13008                settings.editor.auto_signature_help = Some(false);
13009                settings.editor.show_signature_help_after_edits = Some(true);
13010            });
13011        });
13012    });
13013    cx.set_state(
13014        &r#"
13015            fn main() {
13016                sampleˇ
13017            }
13018        "#
13019        .unindent(),
13020    );
13021    cx.update_editor(|editor, window, cx| {
13022        editor.handle_input("(", window, cx);
13023    });
13024    cx.assert_editor_state(
13025        &"
13026            fn main() {
13027                sample(ˇ)
13028            }
13029        "
13030        .unindent(),
13031    );
13032    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13033    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13034        .await;
13035    cx.update_editor(|editor, _, _| {
13036        let signature_help_state = editor.signature_help_state.popover().cloned();
13037        assert!(signature_help_state.is_some());
13038        let signature = signature_help_state.unwrap();
13039        assert_eq!(
13040            signature.signatures[signature.current_signature].label,
13041            "fn sample(param1: u8, param2: u8)"
13042        );
13043        editor.signature_help_state = SignatureHelpState::default();
13044    });
13045
13046    // Ensure that signature_help is called when auto signature help override is enabled
13047    cx.update(|_, cx| {
13048        cx.update_global::<SettingsStore, _>(|settings, cx| {
13049            settings.update_user_settings(cx, |settings| {
13050                settings.editor.auto_signature_help = Some(true);
13051                settings.editor.show_signature_help_after_edits = Some(false);
13052            });
13053        });
13054    });
13055    cx.set_state(
13056        &r#"
13057            fn main() {
13058                sampleˇ
13059            }
13060        "#
13061        .unindent(),
13062    );
13063    cx.update_editor(|editor, window, cx| {
13064        editor.handle_input("(", window, cx);
13065    });
13066    cx.assert_editor_state(
13067        &"
13068            fn main() {
13069                sample(ˇ)
13070            }
13071        "
13072        .unindent(),
13073    );
13074    handle_signature_help_request(&mut cx, mocked_response).await;
13075    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13076        .await;
13077    cx.editor(|editor, _, _| {
13078        let signature_help_state = editor.signature_help_state.popover().cloned();
13079        assert!(signature_help_state.is_some());
13080        let signature = signature_help_state.unwrap();
13081        assert_eq!(
13082            signature.signatures[signature.current_signature].label,
13083            "fn sample(param1: u8, param2: u8)"
13084        );
13085    });
13086}
13087
13088#[gpui::test]
13089async fn test_signature_help(cx: &mut TestAppContext) {
13090    init_test(cx, |_| {});
13091    cx.update(|cx| {
13092        cx.update_global::<SettingsStore, _>(|settings, cx| {
13093            settings.update_user_settings(cx, |settings| {
13094                settings.editor.auto_signature_help = Some(true);
13095            });
13096        });
13097    });
13098
13099    let mut cx = EditorLspTestContext::new_rust(
13100        lsp::ServerCapabilities {
13101            signature_help_provider: Some(lsp::SignatureHelpOptions {
13102                ..Default::default()
13103            }),
13104            ..Default::default()
13105        },
13106        cx,
13107    )
13108    .await;
13109
13110    // A test that directly calls `show_signature_help`
13111    cx.update_editor(|editor, window, cx| {
13112        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13113    });
13114
13115    let mocked_response = lsp::SignatureHelp {
13116        signatures: vec![lsp::SignatureInformation {
13117            label: "fn sample(param1: u8, param2: u8)".to_string(),
13118            documentation: None,
13119            parameters: Some(vec![
13120                lsp::ParameterInformation {
13121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13122                    documentation: None,
13123                },
13124                lsp::ParameterInformation {
13125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13126                    documentation: None,
13127                },
13128            ]),
13129            active_parameter: None,
13130        }],
13131        active_signature: Some(0),
13132        active_parameter: Some(0),
13133    };
13134    handle_signature_help_request(&mut cx, mocked_response).await;
13135
13136    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13137        .await;
13138
13139    cx.editor(|editor, _, _| {
13140        let signature_help_state = editor.signature_help_state.popover().cloned();
13141        assert!(signature_help_state.is_some());
13142        let signature = signature_help_state.unwrap();
13143        assert_eq!(
13144            signature.signatures[signature.current_signature].label,
13145            "fn sample(param1: u8, param2: u8)"
13146        );
13147    });
13148
13149    // When exiting outside from inside the brackets, `signature_help` is closed.
13150    cx.set_state(indoc! {"
13151        fn main() {
13152            sample(ˇ);
13153        }
13154
13155        fn sample(param1: u8, param2: u8) {}
13156    "});
13157
13158    cx.update_editor(|editor, window, cx| {
13159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13160            s.select_ranges([0..0])
13161        });
13162    });
13163
13164    let mocked_response = lsp::SignatureHelp {
13165        signatures: Vec::new(),
13166        active_signature: None,
13167        active_parameter: None,
13168    };
13169    handle_signature_help_request(&mut cx, mocked_response).await;
13170
13171    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13172        .await;
13173
13174    cx.editor(|editor, _, _| {
13175        assert!(!editor.signature_help_state.is_shown());
13176    });
13177
13178    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13179    cx.set_state(indoc! {"
13180        fn main() {
13181            sample(ˇ);
13182        }
13183
13184        fn sample(param1: u8, param2: u8) {}
13185    "});
13186
13187    let mocked_response = lsp::SignatureHelp {
13188        signatures: vec![lsp::SignatureInformation {
13189            label: "fn sample(param1: u8, param2: u8)".to_string(),
13190            documentation: None,
13191            parameters: Some(vec![
13192                lsp::ParameterInformation {
13193                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13194                    documentation: None,
13195                },
13196                lsp::ParameterInformation {
13197                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13198                    documentation: None,
13199                },
13200            ]),
13201            active_parameter: None,
13202        }],
13203        active_signature: Some(0),
13204        active_parameter: Some(0),
13205    };
13206    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13207    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13208        .await;
13209    cx.editor(|editor, _, _| {
13210        assert!(editor.signature_help_state.is_shown());
13211    });
13212
13213    // Restore the popover with more parameter input
13214    cx.set_state(indoc! {"
13215        fn main() {
13216            sample(param1, param2ˇ);
13217        }
13218
13219        fn sample(param1: u8, param2: u8) {}
13220    "});
13221
13222    let mocked_response = lsp::SignatureHelp {
13223        signatures: vec![lsp::SignatureInformation {
13224            label: "fn sample(param1: u8, param2: u8)".to_string(),
13225            documentation: None,
13226            parameters: Some(vec![
13227                lsp::ParameterInformation {
13228                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13229                    documentation: None,
13230                },
13231                lsp::ParameterInformation {
13232                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13233                    documentation: None,
13234                },
13235            ]),
13236            active_parameter: None,
13237        }],
13238        active_signature: Some(0),
13239        active_parameter: Some(1),
13240    };
13241    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13242    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13243        .await;
13244
13245    // When selecting a range, the popover is gone.
13246    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13247    cx.update_editor(|editor, window, cx| {
13248        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13249            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13250        })
13251    });
13252    cx.assert_editor_state(indoc! {"
13253        fn main() {
13254            sample(param1, «ˇparam2»);
13255        }
13256
13257        fn sample(param1: u8, param2: u8) {}
13258    "});
13259    cx.editor(|editor, _, _| {
13260        assert!(!editor.signature_help_state.is_shown());
13261    });
13262
13263    // When unselecting again, the popover is back if within the brackets.
13264    cx.update_editor(|editor, window, cx| {
13265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13266            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13267        })
13268    });
13269    cx.assert_editor_state(indoc! {"
13270        fn main() {
13271            sample(param1, ˇparam2);
13272        }
13273
13274        fn sample(param1: u8, param2: u8) {}
13275    "});
13276    handle_signature_help_request(&mut cx, mocked_response).await;
13277    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13278        .await;
13279    cx.editor(|editor, _, _| {
13280        assert!(editor.signature_help_state.is_shown());
13281    });
13282
13283    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13284    cx.update_editor(|editor, window, cx| {
13285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13286            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13287            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13288        })
13289    });
13290    cx.assert_editor_state(indoc! {"
13291        fn main() {
13292            sample(param1, ˇparam2);
13293        }
13294
13295        fn sample(param1: u8, param2: u8) {}
13296    "});
13297
13298    let mocked_response = lsp::SignatureHelp {
13299        signatures: vec![lsp::SignatureInformation {
13300            label: "fn sample(param1: u8, param2: u8)".to_string(),
13301            documentation: None,
13302            parameters: Some(vec![
13303                lsp::ParameterInformation {
13304                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13305                    documentation: None,
13306                },
13307                lsp::ParameterInformation {
13308                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13309                    documentation: None,
13310                },
13311            ]),
13312            active_parameter: None,
13313        }],
13314        active_signature: Some(0),
13315        active_parameter: Some(1),
13316    };
13317    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13318    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13319        .await;
13320    cx.update_editor(|editor, _, cx| {
13321        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13322    });
13323    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13324        .await;
13325    cx.update_editor(|editor, window, cx| {
13326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13327            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13328        })
13329    });
13330    cx.assert_editor_state(indoc! {"
13331        fn main() {
13332            sample(param1, «ˇparam2»);
13333        }
13334
13335        fn sample(param1: u8, param2: u8) {}
13336    "});
13337    cx.update_editor(|editor, window, cx| {
13338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13339            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13340        })
13341    });
13342    cx.assert_editor_state(indoc! {"
13343        fn main() {
13344            sample(param1, ˇparam2);
13345        }
13346
13347        fn sample(param1: u8, param2: u8) {}
13348    "});
13349    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13350        .await;
13351}
13352
13353#[gpui::test]
13354async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13355    init_test(cx, |_| {});
13356
13357    let mut cx = EditorLspTestContext::new_rust(
13358        lsp::ServerCapabilities {
13359            signature_help_provider: Some(lsp::SignatureHelpOptions {
13360                ..Default::default()
13361            }),
13362            ..Default::default()
13363        },
13364        cx,
13365    )
13366    .await;
13367
13368    cx.set_state(indoc! {"
13369        fn main() {
13370            overloadedˇ
13371        }
13372    "});
13373
13374    cx.update_editor(|editor, window, cx| {
13375        editor.handle_input("(", window, cx);
13376        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13377    });
13378
13379    // Mock response with 3 signatures
13380    let mocked_response = lsp::SignatureHelp {
13381        signatures: vec![
13382            lsp::SignatureInformation {
13383                label: "fn overloaded(x: i32)".to_string(),
13384                documentation: None,
13385                parameters: Some(vec![lsp::ParameterInformation {
13386                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13387                    documentation: None,
13388                }]),
13389                active_parameter: None,
13390            },
13391            lsp::SignatureInformation {
13392                label: "fn overloaded(x: i32, y: i32)".to_string(),
13393                documentation: None,
13394                parameters: Some(vec![
13395                    lsp::ParameterInformation {
13396                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13397                        documentation: None,
13398                    },
13399                    lsp::ParameterInformation {
13400                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13401                        documentation: None,
13402                    },
13403                ]),
13404                active_parameter: None,
13405            },
13406            lsp::SignatureInformation {
13407                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13408                documentation: None,
13409                parameters: Some(vec![
13410                    lsp::ParameterInformation {
13411                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13412                        documentation: None,
13413                    },
13414                    lsp::ParameterInformation {
13415                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13416                        documentation: None,
13417                    },
13418                    lsp::ParameterInformation {
13419                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13420                        documentation: None,
13421                    },
13422                ]),
13423                active_parameter: None,
13424            },
13425        ],
13426        active_signature: Some(1),
13427        active_parameter: Some(0),
13428    };
13429    handle_signature_help_request(&mut cx, mocked_response).await;
13430
13431    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13432        .await;
13433
13434    // Verify we have multiple signatures and the right one is selected
13435    cx.editor(|editor, _, _| {
13436        let popover = editor.signature_help_state.popover().cloned().unwrap();
13437        assert_eq!(popover.signatures.len(), 3);
13438        // active_signature was 1, so that should be the current
13439        assert_eq!(popover.current_signature, 1);
13440        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13441        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13442        assert_eq!(
13443            popover.signatures[2].label,
13444            "fn overloaded(x: i32, y: i32, z: i32)"
13445        );
13446    });
13447
13448    // Test navigation functionality
13449    cx.update_editor(|editor, window, cx| {
13450        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13451    });
13452
13453    cx.editor(|editor, _, _| {
13454        let popover = editor.signature_help_state.popover().cloned().unwrap();
13455        assert_eq!(popover.current_signature, 2);
13456    });
13457
13458    // Test wrap around
13459    cx.update_editor(|editor, window, cx| {
13460        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13461    });
13462
13463    cx.editor(|editor, _, _| {
13464        let popover = editor.signature_help_state.popover().cloned().unwrap();
13465        assert_eq!(popover.current_signature, 0);
13466    });
13467
13468    // Test previous navigation
13469    cx.update_editor(|editor, window, cx| {
13470        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13471    });
13472
13473    cx.editor(|editor, _, _| {
13474        let popover = editor.signature_help_state.popover().cloned().unwrap();
13475        assert_eq!(popover.current_signature, 2);
13476    });
13477}
13478
13479#[gpui::test]
13480async fn test_completion_mode(cx: &mut TestAppContext) {
13481    init_test(cx, |_| {});
13482    let mut cx = EditorLspTestContext::new_rust(
13483        lsp::ServerCapabilities {
13484            completion_provider: Some(lsp::CompletionOptions {
13485                resolve_provider: Some(true),
13486                ..Default::default()
13487            }),
13488            ..Default::default()
13489        },
13490        cx,
13491    )
13492    .await;
13493
13494    struct Run {
13495        run_description: &'static str,
13496        initial_state: String,
13497        buffer_marked_text: String,
13498        completion_label: &'static str,
13499        completion_text: &'static str,
13500        expected_with_insert_mode: String,
13501        expected_with_replace_mode: String,
13502        expected_with_replace_subsequence_mode: String,
13503        expected_with_replace_suffix_mode: String,
13504    }
13505
13506    let runs = [
13507        Run {
13508            run_description: "Start of word matches completion text",
13509            initial_state: "before ediˇ after".into(),
13510            buffer_marked_text: "before <edi|> after".into(),
13511            completion_label: "editor",
13512            completion_text: "editor",
13513            expected_with_insert_mode: "before editorˇ after".into(),
13514            expected_with_replace_mode: "before editorˇ after".into(),
13515            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13516            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13517        },
13518        Run {
13519            run_description: "Accept same text at the middle of the word",
13520            initial_state: "before ediˇtor after".into(),
13521            buffer_marked_text: "before <edi|tor> after".into(),
13522            completion_label: "editor",
13523            completion_text: "editor",
13524            expected_with_insert_mode: "before editorˇtor after".into(),
13525            expected_with_replace_mode: "before editorˇ after".into(),
13526            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13527            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13528        },
13529        Run {
13530            run_description: "End of word matches completion text -- cursor at end",
13531            initial_state: "before torˇ after".into(),
13532            buffer_marked_text: "before <tor|> after".into(),
13533            completion_label: "editor",
13534            completion_text: "editor",
13535            expected_with_insert_mode: "before editorˇ after".into(),
13536            expected_with_replace_mode: "before editorˇ after".into(),
13537            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13538            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13539        },
13540        Run {
13541            run_description: "End of word matches completion text -- cursor at start",
13542            initial_state: "before ˇtor after".into(),
13543            buffer_marked_text: "before <|tor> after".into(),
13544            completion_label: "editor",
13545            completion_text: "editor",
13546            expected_with_insert_mode: "before editorˇtor after".into(),
13547            expected_with_replace_mode: "before editorˇ after".into(),
13548            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13549            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13550        },
13551        Run {
13552            run_description: "Prepend text containing whitespace",
13553            initial_state: "pˇfield: bool".into(),
13554            buffer_marked_text: "<p|field>: bool".into(),
13555            completion_label: "pub ",
13556            completion_text: "pub ",
13557            expected_with_insert_mode: "pub ˇfield: bool".into(),
13558            expected_with_replace_mode: "pub ˇ: bool".into(),
13559            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13560            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13561        },
13562        Run {
13563            run_description: "Add element to start of list",
13564            initial_state: "[element_ˇelement_2]".into(),
13565            buffer_marked_text: "[<element_|element_2>]".into(),
13566            completion_label: "element_1",
13567            completion_text: "element_1",
13568            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13569            expected_with_replace_mode: "[element_1ˇ]".into(),
13570            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13571            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13572        },
13573        Run {
13574            run_description: "Add element to start of list -- first and second elements are equal",
13575            initial_state: "[elˇelement]".into(),
13576            buffer_marked_text: "[<el|element>]".into(),
13577            completion_label: "element",
13578            completion_text: "element",
13579            expected_with_insert_mode: "[elementˇelement]".into(),
13580            expected_with_replace_mode: "[elementˇ]".into(),
13581            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13582            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13583        },
13584        Run {
13585            run_description: "Ends with matching suffix",
13586            initial_state: "SubˇError".into(),
13587            buffer_marked_text: "<Sub|Error>".into(),
13588            completion_label: "SubscriptionError",
13589            completion_text: "SubscriptionError",
13590            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13591            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13592            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13593            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13594        },
13595        Run {
13596            run_description: "Suffix is a subsequence -- contiguous",
13597            initial_state: "SubˇErr".into(),
13598            buffer_marked_text: "<Sub|Err>".into(),
13599            completion_label: "SubscriptionError",
13600            completion_text: "SubscriptionError",
13601            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13602            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13603            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13604            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13605        },
13606        Run {
13607            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13608            initial_state: "Suˇscrirr".into(),
13609            buffer_marked_text: "<Su|scrirr>".into(),
13610            completion_label: "SubscriptionError",
13611            completion_text: "SubscriptionError",
13612            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13613            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13614            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13615            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13616        },
13617        Run {
13618            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13619            initial_state: "foo(indˇix)".into(),
13620            buffer_marked_text: "foo(<ind|ix>)".into(),
13621            completion_label: "node_index",
13622            completion_text: "node_index",
13623            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13624            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13625            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13626            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13627        },
13628        Run {
13629            run_description: "Replace range ends before cursor - should extend to cursor",
13630            initial_state: "before editˇo after".into(),
13631            buffer_marked_text: "before <{ed}>it|o after".into(),
13632            completion_label: "editor",
13633            completion_text: "editor",
13634            expected_with_insert_mode: "before editorˇo after".into(),
13635            expected_with_replace_mode: "before editorˇo after".into(),
13636            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13637            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13638        },
13639        Run {
13640            run_description: "Uses label for suffix matching",
13641            initial_state: "before ediˇtor after".into(),
13642            buffer_marked_text: "before <edi|tor> after".into(),
13643            completion_label: "editor",
13644            completion_text: "editor()",
13645            expected_with_insert_mode: "before editor()ˇtor after".into(),
13646            expected_with_replace_mode: "before editor()ˇ after".into(),
13647            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13648            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13649        },
13650        Run {
13651            run_description: "Case insensitive subsequence and suffix matching",
13652            initial_state: "before EDiˇtoR after".into(),
13653            buffer_marked_text: "before <EDi|toR> after".into(),
13654            completion_label: "editor",
13655            completion_text: "editor",
13656            expected_with_insert_mode: "before editorˇtoR after".into(),
13657            expected_with_replace_mode: "before editorˇ after".into(),
13658            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13659            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13660        },
13661    ];
13662
13663    for run in runs {
13664        let run_variations = [
13665            (LspInsertMode::Insert, run.expected_with_insert_mode),
13666            (LspInsertMode::Replace, run.expected_with_replace_mode),
13667            (
13668                LspInsertMode::ReplaceSubsequence,
13669                run.expected_with_replace_subsequence_mode,
13670            ),
13671            (
13672                LspInsertMode::ReplaceSuffix,
13673                run.expected_with_replace_suffix_mode,
13674            ),
13675        ];
13676
13677        for (lsp_insert_mode, expected_text) in run_variations {
13678            eprintln!(
13679                "run = {:?}, mode = {lsp_insert_mode:.?}",
13680                run.run_description,
13681            );
13682
13683            update_test_language_settings(&mut cx, |settings| {
13684                settings.defaults.completions = Some(CompletionSettingsContent {
13685                    lsp_insert_mode: Some(lsp_insert_mode),
13686                    words: Some(WordsCompletionMode::Disabled),
13687                    words_min_length: Some(0),
13688                    ..Default::default()
13689                });
13690            });
13691
13692            cx.set_state(&run.initial_state);
13693            cx.update_editor(|editor, window, cx| {
13694                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13695            });
13696
13697            let counter = Arc::new(AtomicUsize::new(0));
13698            handle_completion_request_with_insert_and_replace(
13699                &mut cx,
13700                &run.buffer_marked_text,
13701                vec![(run.completion_label, run.completion_text)],
13702                counter.clone(),
13703            )
13704            .await;
13705            cx.condition(|editor, _| editor.context_menu_visible())
13706                .await;
13707            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13708
13709            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13710                editor
13711                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13712                    .unwrap()
13713            });
13714            cx.assert_editor_state(&expected_text);
13715            handle_resolve_completion_request(&mut cx, None).await;
13716            apply_additional_edits.await.unwrap();
13717        }
13718    }
13719}
13720
13721#[gpui::test]
13722async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13723    init_test(cx, |_| {});
13724    let mut cx = EditorLspTestContext::new_rust(
13725        lsp::ServerCapabilities {
13726            completion_provider: Some(lsp::CompletionOptions {
13727                resolve_provider: Some(true),
13728                ..Default::default()
13729            }),
13730            ..Default::default()
13731        },
13732        cx,
13733    )
13734    .await;
13735
13736    let initial_state = "SubˇError";
13737    let buffer_marked_text = "<Sub|Error>";
13738    let completion_text = "SubscriptionError";
13739    let expected_with_insert_mode = "SubscriptionErrorˇError";
13740    let expected_with_replace_mode = "SubscriptionErrorˇ";
13741
13742    update_test_language_settings(&mut cx, |settings| {
13743        settings.defaults.completions = Some(CompletionSettingsContent {
13744            words: Some(WordsCompletionMode::Disabled),
13745            words_min_length: Some(0),
13746            // set the opposite here to ensure that the action is overriding the default behavior
13747            lsp_insert_mode: Some(LspInsertMode::Insert),
13748            ..Default::default()
13749        });
13750    });
13751
13752    cx.set_state(initial_state);
13753    cx.update_editor(|editor, window, cx| {
13754        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13755    });
13756
13757    let counter = Arc::new(AtomicUsize::new(0));
13758    handle_completion_request_with_insert_and_replace(
13759        &mut cx,
13760        buffer_marked_text,
13761        vec![(completion_text, completion_text)],
13762        counter.clone(),
13763    )
13764    .await;
13765    cx.condition(|editor, _| editor.context_menu_visible())
13766        .await;
13767    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13768
13769    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13770        editor
13771            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13772            .unwrap()
13773    });
13774    cx.assert_editor_state(expected_with_replace_mode);
13775    handle_resolve_completion_request(&mut cx, None).await;
13776    apply_additional_edits.await.unwrap();
13777
13778    update_test_language_settings(&mut cx, |settings| {
13779        settings.defaults.completions = Some(CompletionSettingsContent {
13780            words: Some(WordsCompletionMode::Disabled),
13781            words_min_length: Some(0),
13782            // set the opposite here to ensure that the action is overriding the default behavior
13783            lsp_insert_mode: Some(LspInsertMode::Replace),
13784            ..Default::default()
13785        });
13786    });
13787
13788    cx.set_state(initial_state);
13789    cx.update_editor(|editor, window, cx| {
13790        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13791    });
13792    handle_completion_request_with_insert_and_replace(
13793        &mut cx,
13794        buffer_marked_text,
13795        vec![(completion_text, completion_text)],
13796        counter.clone(),
13797    )
13798    .await;
13799    cx.condition(|editor, _| editor.context_menu_visible())
13800        .await;
13801    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13802
13803    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13804        editor
13805            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13806            .unwrap()
13807    });
13808    cx.assert_editor_state(expected_with_insert_mode);
13809    handle_resolve_completion_request(&mut cx, None).await;
13810    apply_additional_edits.await.unwrap();
13811}
13812
13813#[gpui::test]
13814async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13815    init_test(cx, |_| {});
13816    let mut cx = EditorLspTestContext::new_rust(
13817        lsp::ServerCapabilities {
13818            completion_provider: Some(lsp::CompletionOptions {
13819                resolve_provider: Some(true),
13820                ..Default::default()
13821            }),
13822            ..Default::default()
13823        },
13824        cx,
13825    )
13826    .await;
13827
13828    // scenario: surrounding text matches completion text
13829    let completion_text = "to_offset";
13830    let initial_state = indoc! {"
13831        1. buf.to_offˇsuffix
13832        2. buf.to_offˇsuf
13833        3. buf.to_offˇfix
13834        4. buf.to_offˇ
13835        5. into_offˇensive
13836        6. ˇsuffix
13837        7. let ˇ //
13838        8. aaˇzz
13839        9. buf.to_off«zzzzzˇ»suffix
13840        10. buf.«ˇzzzzz»suffix
13841        11. to_off«ˇzzzzz»
13842
13843        buf.to_offˇsuffix  // newest cursor
13844    "};
13845    let completion_marked_buffer = indoc! {"
13846        1. buf.to_offsuffix
13847        2. buf.to_offsuf
13848        3. buf.to_offfix
13849        4. buf.to_off
13850        5. into_offensive
13851        6. suffix
13852        7. let  //
13853        8. aazz
13854        9. buf.to_offzzzzzsuffix
13855        10. buf.zzzzzsuffix
13856        11. to_offzzzzz
13857
13858        buf.<to_off|suffix>  // newest cursor
13859    "};
13860    let expected = indoc! {"
13861        1. buf.to_offsetˇ
13862        2. buf.to_offsetˇsuf
13863        3. buf.to_offsetˇfix
13864        4. buf.to_offsetˇ
13865        5. into_offsetˇensive
13866        6. to_offsetˇsuffix
13867        7. let to_offsetˇ //
13868        8. aato_offsetˇzz
13869        9. buf.to_offsetˇ
13870        10. buf.to_offsetˇsuffix
13871        11. to_offsetˇ
13872
13873        buf.to_offsetˇ  // newest cursor
13874    "};
13875    cx.set_state(initial_state);
13876    cx.update_editor(|editor, window, cx| {
13877        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13878    });
13879    handle_completion_request_with_insert_and_replace(
13880        &mut cx,
13881        completion_marked_buffer,
13882        vec![(completion_text, completion_text)],
13883        Arc::new(AtomicUsize::new(0)),
13884    )
13885    .await;
13886    cx.condition(|editor, _| editor.context_menu_visible())
13887        .await;
13888    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13889        editor
13890            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13891            .unwrap()
13892    });
13893    cx.assert_editor_state(expected);
13894    handle_resolve_completion_request(&mut cx, None).await;
13895    apply_additional_edits.await.unwrap();
13896
13897    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13898    let completion_text = "foo_and_bar";
13899    let initial_state = indoc! {"
13900        1. ooanbˇ
13901        2. zooanbˇ
13902        3. ooanbˇz
13903        4. zooanbˇz
13904        5. ooanˇ
13905        6. oanbˇ
13906
13907        ooanbˇ
13908    "};
13909    let completion_marked_buffer = indoc! {"
13910        1. ooanb
13911        2. zooanb
13912        3. ooanbz
13913        4. zooanbz
13914        5. ooan
13915        6. oanb
13916
13917        <ooanb|>
13918    "};
13919    let expected = indoc! {"
13920        1. foo_and_barˇ
13921        2. zfoo_and_barˇ
13922        3. foo_and_barˇz
13923        4. zfoo_and_barˇz
13924        5. ooanfoo_and_barˇ
13925        6. oanbfoo_and_barˇ
13926
13927        foo_and_barˇ
13928    "};
13929    cx.set_state(initial_state);
13930    cx.update_editor(|editor, window, cx| {
13931        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13932    });
13933    handle_completion_request_with_insert_and_replace(
13934        &mut cx,
13935        completion_marked_buffer,
13936        vec![(completion_text, completion_text)],
13937        Arc::new(AtomicUsize::new(0)),
13938    )
13939    .await;
13940    cx.condition(|editor, _| editor.context_menu_visible())
13941        .await;
13942    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13943        editor
13944            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13945            .unwrap()
13946    });
13947    cx.assert_editor_state(expected);
13948    handle_resolve_completion_request(&mut cx, None).await;
13949    apply_additional_edits.await.unwrap();
13950
13951    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13952    // (expects the same as if it was inserted at the end)
13953    let completion_text = "foo_and_bar";
13954    let initial_state = indoc! {"
13955        1. ooˇanb
13956        2. zooˇanb
13957        3. ooˇanbz
13958        4. zooˇanbz
13959
13960        ooˇanb
13961    "};
13962    let completion_marked_buffer = indoc! {"
13963        1. ooanb
13964        2. zooanb
13965        3. ooanbz
13966        4. zooanbz
13967
13968        <oo|anb>
13969    "};
13970    let expected = indoc! {"
13971        1. foo_and_barˇ
13972        2. zfoo_and_barˇ
13973        3. foo_and_barˇz
13974        4. zfoo_and_barˇz
13975
13976        foo_and_barˇ
13977    "};
13978    cx.set_state(initial_state);
13979    cx.update_editor(|editor, window, cx| {
13980        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13981    });
13982    handle_completion_request_with_insert_and_replace(
13983        &mut cx,
13984        completion_marked_buffer,
13985        vec![(completion_text, completion_text)],
13986        Arc::new(AtomicUsize::new(0)),
13987    )
13988    .await;
13989    cx.condition(|editor, _| editor.context_menu_visible())
13990        .await;
13991    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13992        editor
13993            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13994            .unwrap()
13995    });
13996    cx.assert_editor_state(expected);
13997    handle_resolve_completion_request(&mut cx, None).await;
13998    apply_additional_edits.await.unwrap();
13999}
14000
14001// This used to crash
14002#[gpui::test]
14003async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14004    init_test(cx, |_| {});
14005
14006    let buffer_text = indoc! {"
14007        fn main() {
14008            10.satu;
14009
14010            //
14011            // separate cursors so they open in different excerpts (manually reproducible)
14012            //
14013
14014            10.satu20;
14015        }
14016    "};
14017    let multibuffer_text_with_selections = indoc! {"
14018        fn main() {
14019            10.satuˇ;
14020
14021            //
14022
14023            //
14024
14025            10.satuˇ20;
14026        }
14027    "};
14028    let expected_multibuffer = indoc! {"
14029        fn main() {
14030            10.saturating_sub()ˇ;
14031
14032            //
14033
14034            //
14035
14036            10.saturating_sub()ˇ;
14037        }
14038    "};
14039
14040    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14041    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14042
14043    let fs = FakeFs::new(cx.executor());
14044    fs.insert_tree(
14045        path!("/a"),
14046        json!({
14047            "main.rs": buffer_text,
14048        }),
14049    )
14050    .await;
14051
14052    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14053    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14054    language_registry.add(rust_lang());
14055    let mut fake_servers = language_registry.register_fake_lsp(
14056        "Rust",
14057        FakeLspAdapter {
14058            capabilities: lsp::ServerCapabilities {
14059                completion_provider: Some(lsp::CompletionOptions {
14060                    resolve_provider: None,
14061                    ..lsp::CompletionOptions::default()
14062                }),
14063                ..lsp::ServerCapabilities::default()
14064            },
14065            ..FakeLspAdapter::default()
14066        },
14067    );
14068    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14069    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14070    let buffer = project
14071        .update(cx, |project, cx| {
14072            project.open_local_buffer(path!("/a/main.rs"), cx)
14073        })
14074        .await
14075        .unwrap();
14076
14077    let multi_buffer = cx.new(|cx| {
14078        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14079        multi_buffer.push_excerpts(
14080            buffer.clone(),
14081            [ExcerptRange::new(0..first_excerpt_end)],
14082            cx,
14083        );
14084        multi_buffer.push_excerpts(
14085            buffer.clone(),
14086            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14087            cx,
14088        );
14089        multi_buffer
14090    });
14091
14092    let editor = workspace
14093        .update(cx, |_, window, cx| {
14094            cx.new(|cx| {
14095                Editor::new(
14096                    EditorMode::Full {
14097                        scale_ui_elements_with_buffer_font_size: false,
14098                        show_active_line_background: false,
14099                        sized_by_content: false,
14100                    },
14101                    multi_buffer.clone(),
14102                    Some(project.clone()),
14103                    window,
14104                    cx,
14105                )
14106            })
14107        })
14108        .unwrap();
14109
14110    let pane = workspace
14111        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14112        .unwrap();
14113    pane.update_in(cx, |pane, window, cx| {
14114        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14115    });
14116
14117    let fake_server = fake_servers.next().await.unwrap();
14118
14119    editor.update_in(cx, |editor, window, cx| {
14120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14121            s.select_ranges([
14122                Point::new(1, 11)..Point::new(1, 11),
14123                Point::new(7, 11)..Point::new(7, 11),
14124            ])
14125        });
14126
14127        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14128    });
14129
14130    editor.update_in(cx, |editor, window, cx| {
14131        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14132    });
14133
14134    fake_server
14135        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14136            let completion_item = lsp::CompletionItem {
14137                label: "saturating_sub()".into(),
14138                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14139                    lsp::InsertReplaceEdit {
14140                        new_text: "saturating_sub()".to_owned(),
14141                        insert: lsp::Range::new(
14142                            lsp::Position::new(7, 7),
14143                            lsp::Position::new(7, 11),
14144                        ),
14145                        replace: lsp::Range::new(
14146                            lsp::Position::new(7, 7),
14147                            lsp::Position::new(7, 13),
14148                        ),
14149                    },
14150                )),
14151                ..lsp::CompletionItem::default()
14152            };
14153
14154            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14155        })
14156        .next()
14157        .await
14158        .unwrap();
14159
14160    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14161        .await;
14162
14163    editor
14164        .update_in(cx, |editor, window, cx| {
14165            editor
14166                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14167                .unwrap()
14168        })
14169        .await
14170        .unwrap();
14171
14172    editor.update(cx, |editor, cx| {
14173        assert_text_with_selections(editor, expected_multibuffer, cx);
14174    })
14175}
14176
14177#[gpui::test]
14178async fn test_completion(cx: &mut TestAppContext) {
14179    init_test(cx, |_| {});
14180
14181    let mut cx = EditorLspTestContext::new_rust(
14182        lsp::ServerCapabilities {
14183            completion_provider: Some(lsp::CompletionOptions {
14184                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14185                resolve_provider: Some(true),
14186                ..Default::default()
14187            }),
14188            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14189            ..Default::default()
14190        },
14191        cx,
14192    )
14193    .await;
14194    let counter = Arc::new(AtomicUsize::new(0));
14195
14196    cx.set_state(indoc! {"
14197        oneˇ
14198        two
14199        three
14200    "});
14201    cx.simulate_keystroke(".");
14202    handle_completion_request(
14203        indoc! {"
14204            one.|<>
14205            two
14206            three
14207        "},
14208        vec!["first_completion", "second_completion"],
14209        true,
14210        counter.clone(),
14211        &mut cx,
14212    )
14213    .await;
14214    cx.condition(|editor, _| editor.context_menu_visible())
14215        .await;
14216    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14217
14218    let _handler = handle_signature_help_request(
14219        &mut cx,
14220        lsp::SignatureHelp {
14221            signatures: vec![lsp::SignatureInformation {
14222                label: "test signature".to_string(),
14223                documentation: None,
14224                parameters: Some(vec![lsp::ParameterInformation {
14225                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14226                    documentation: None,
14227                }]),
14228                active_parameter: None,
14229            }],
14230            active_signature: None,
14231            active_parameter: None,
14232        },
14233    );
14234    cx.update_editor(|editor, window, cx| {
14235        assert!(
14236            !editor.signature_help_state.is_shown(),
14237            "No signature help was called for"
14238        );
14239        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14240    });
14241    cx.run_until_parked();
14242    cx.update_editor(|editor, _, _| {
14243        assert!(
14244            !editor.signature_help_state.is_shown(),
14245            "No signature help should be shown when completions menu is open"
14246        );
14247    });
14248
14249    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14250        editor.context_menu_next(&Default::default(), window, cx);
14251        editor
14252            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14253            .unwrap()
14254    });
14255    cx.assert_editor_state(indoc! {"
14256        one.second_completionˇ
14257        two
14258        three
14259    "});
14260
14261    handle_resolve_completion_request(
14262        &mut cx,
14263        Some(vec![
14264            (
14265                //This overlaps with the primary completion edit which is
14266                //misbehavior from the LSP spec, test that we filter it out
14267                indoc! {"
14268                    one.second_ˇcompletion
14269                    two
14270                    threeˇ
14271                "},
14272                "overlapping additional edit",
14273            ),
14274            (
14275                indoc! {"
14276                    one.second_completion
14277                    two
14278                    threeˇ
14279                "},
14280                "\nadditional edit",
14281            ),
14282        ]),
14283    )
14284    .await;
14285    apply_additional_edits.await.unwrap();
14286    cx.assert_editor_state(indoc! {"
14287        one.second_completionˇ
14288        two
14289        three
14290        additional edit
14291    "});
14292
14293    cx.set_state(indoc! {"
14294        one.second_completion
14295        twoˇ
14296        threeˇ
14297        additional edit
14298    "});
14299    cx.simulate_keystroke(" ");
14300    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14301    cx.simulate_keystroke("s");
14302    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14303
14304    cx.assert_editor_state(indoc! {"
14305        one.second_completion
14306        two sˇ
14307        three sˇ
14308        additional edit
14309    "});
14310    handle_completion_request(
14311        indoc! {"
14312            one.second_completion
14313            two s
14314            three <s|>
14315            additional edit
14316        "},
14317        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14318        true,
14319        counter.clone(),
14320        &mut cx,
14321    )
14322    .await;
14323    cx.condition(|editor, _| editor.context_menu_visible())
14324        .await;
14325    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14326
14327    cx.simulate_keystroke("i");
14328
14329    handle_completion_request(
14330        indoc! {"
14331            one.second_completion
14332            two si
14333            three <si|>
14334            additional edit
14335        "},
14336        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14337        true,
14338        counter.clone(),
14339        &mut cx,
14340    )
14341    .await;
14342    cx.condition(|editor, _| editor.context_menu_visible())
14343        .await;
14344    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14345
14346    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14347        editor
14348            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14349            .unwrap()
14350    });
14351    cx.assert_editor_state(indoc! {"
14352        one.second_completion
14353        two sixth_completionˇ
14354        three sixth_completionˇ
14355        additional edit
14356    "});
14357
14358    apply_additional_edits.await.unwrap();
14359
14360    update_test_language_settings(&mut cx, |settings| {
14361        settings.defaults.show_completions_on_input = Some(false);
14362    });
14363    cx.set_state("editorˇ");
14364    cx.simulate_keystroke(".");
14365    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14366    cx.simulate_keystrokes("c l o");
14367    cx.assert_editor_state("editor.cloˇ");
14368    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14369    cx.update_editor(|editor, window, cx| {
14370        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14371    });
14372    handle_completion_request(
14373        "editor.<clo|>",
14374        vec!["close", "clobber"],
14375        true,
14376        counter.clone(),
14377        &mut cx,
14378    )
14379    .await;
14380    cx.condition(|editor, _| editor.context_menu_visible())
14381        .await;
14382    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14383
14384    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14385        editor
14386            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14387            .unwrap()
14388    });
14389    cx.assert_editor_state("editor.clobberˇ");
14390    handle_resolve_completion_request(&mut cx, None).await;
14391    apply_additional_edits.await.unwrap();
14392}
14393
14394#[gpui::test]
14395async fn test_completion_reuse(cx: &mut TestAppContext) {
14396    init_test(cx, |_| {});
14397
14398    let mut cx = EditorLspTestContext::new_rust(
14399        lsp::ServerCapabilities {
14400            completion_provider: Some(lsp::CompletionOptions {
14401                trigger_characters: Some(vec![".".to_string()]),
14402                ..Default::default()
14403            }),
14404            ..Default::default()
14405        },
14406        cx,
14407    )
14408    .await;
14409
14410    let counter = Arc::new(AtomicUsize::new(0));
14411    cx.set_state("objˇ");
14412    cx.simulate_keystroke(".");
14413
14414    // Initial completion request returns complete results
14415    let is_incomplete = false;
14416    handle_completion_request(
14417        "obj.|<>",
14418        vec!["a", "ab", "abc"],
14419        is_incomplete,
14420        counter.clone(),
14421        &mut cx,
14422    )
14423    .await;
14424    cx.run_until_parked();
14425    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14426    cx.assert_editor_state("obj.ˇ");
14427    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14428
14429    // Type "a" - filters existing completions
14430    cx.simulate_keystroke("a");
14431    cx.run_until_parked();
14432    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14433    cx.assert_editor_state("obj.aˇ");
14434    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14435
14436    // Type "b" - filters existing completions
14437    cx.simulate_keystroke("b");
14438    cx.run_until_parked();
14439    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14440    cx.assert_editor_state("obj.abˇ");
14441    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14442
14443    // Type "c" - filters existing completions
14444    cx.simulate_keystroke("c");
14445    cx.run_until_parked();
14446    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14447    cx.assert_editor_state("obj.abcˇ");
14448    check_displayed_completions(vec!["abc"], &mut cx);
14449
14450    // Backspace to delete "c" - filters existing completions
14451    cx.update_editor(|editor, window, cx| {
14452        editor.backspace(&Backspace, window, cx);
14453    });
14454    cx.run_until_parked();
14455    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14456    cx.assert_editor_state("obj.abˇ");
14457    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14458
14459    // Moving cursor to the left dismisses menu.
14460    cx.update_editor(|editor, window, cx| {
14461        editor.move_left(&MoveLeft, window, cx);
14462    });
14463    cx.run_until_parked();
14464    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14465    cx.assert_editor_state("obj.aˇb");
14466    cx.update_editor(|editor, _, _| {
14467        assert_eq!(editor.context_menu_visible(), false);
14468    });
14469
14470    // Type "b" - new request
14471    cx.simulate_keystroke("b");
14472    let is_incomplete = false;
14473    handle_completion_request(
14474        "obj.<ab|>a",
14475        vec!["ab", "abc"],
14476        is_incomplete,
14477        counter.clone(),
14478        &mut cx,
14479    )
14480    .await;
14481    cx.run_until_parked();
14482    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14483    cx.assert_editor_state("obj.abˇb");
14484    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14485
14486    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14487    cx.update_editor(|editor, window, cx| {
14488        editor.backspace(&Backspace, window, cx);
14489    });
14490    let is_incomplete = false;
14491    handle_completion_request(
14492        "obj.<a|>b",
14493        vec!["a", "ab", "abc"],
14494        is_incomplete,
14495        counter.clone(),
14496        &mut cx,
14497    )
14498    .await;
14499    cx.run_until_parked();
14500    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14501    cx.assert_editor_state("obj.aˇb");
14502    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14503
14504    // Backspace to delete "a" - dismisses menu.
14505    cx.update_editor(|editor, window, cx| {
14506        editor.backspace(&Backspace, window, cx);
14507    });
14508    cx.run_until_parked();
14509    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14510    cx.assert_editor_state("obj.ˇb");
14511    cx.update_editor(|editor, _, _| {
14512        assert_eq!(editor.context_menu_visible(), false);
14513    });
14514}
14515
14516#[gpui::test]
14517async fn test_word_completion(cx: &mut TestAppContext) {
14518    let lsp_fetch_timeout_ms = 10;
14519    init_test(cx, |language_settings| {
14520        language_settings.defaults.completions = Some(CompletionSettingsContent {
14521            words_min_length: Some(0),
14522            lsp_fetch_timeout_ms: Some(10),
14523            lsp_insert_mode: Some(LspInsertMode::Insert),
14524            ..Default::default()
14525        });
14526    });
14527
14528    let mut cx = EditorLspTestContext::new_rust(
14529        lsp::ServerCapabilities {
14530            completion_provider: Some(lsp::CompletionOptions {
14531                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14532                ..lsp::CompletionOptions::default()
14533            }),
14534            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14535            ..lsp::ServerCapabilities::default()
14536        },
14537        cx,
14538    )
14539    .await;
14540
14541    let throttle_completions = Arc::new(AtomicBool::new(false));
14542
14543    let lsp_throttle_completions = throttle_completions.clone();
14544    let _completion_requests_handler =
14545        cx.lsp
14546            .server
14547            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14548                let lsp_throttle_completions = lsp_throttle_completions.clone();
14549                let cx = cx.clone();
14550                async move {
14551                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14552                        cx.background_executor()
14553                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14554                            .await;
14555                    }
14556                    Ok(Some(lsp::CompletionResponse::Array(vec![
14557                        lsp::CompletionItem {
14558                            label: "first".into(),
14559                            ..lsp::CompletionItem::default()
14560                        },
14561                        lsp::CompletionItem {
14562                            label: "last".into(),
14563                            ..lsp::CompletionItem::default()
14564                        },
14565                    ])))
14566                }
14567            });
14568
14569    cx.set_state(indoc! {"
14570        oneˇ
14571        two
14572        three
14573    "});
14574    cx.simulate_keystroke(".");
14575    cx.executor().run_until_parked();
14576    cx.condition(|editor, _| editor.context_menu_visible())
14577        .await;
14578    cx.update_editor(|editor, window, cx| {
14579        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14580        {
14581            assert_eq!(
14582                completion_menu_entries(menu),
14583                &["first", "last"],
14584                "When LSP server is fast to reply, no fallback word completions are used"
14585            );
14586        } else {
14587            panic!("expected completion menu to be open");
14588        }
14589        editor.cancel(&Cancel, window, cx);
14590    });
14591    cx.executor().run_until_parked();
14592    cx.condition(|editor, _| !editor.context_menu_visible())
14593        .await;
14594
14595    throttle_completions.store(true, atomic::Ordering::Release);
14596    cx.simulate_keystroke(".");
14597    cx.executor()
14598        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14599    cx.executor().run_until_parked();
14600    cx.condition(|editor, _| editor.context_menu_visible())
14601        .await;
14602    cx.update_editor(|editor, _, _| {
14603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14604        {
14605            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14606                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14607        } else {
14608            panic!("expected completion menu to be open");
14609        }
14610    });
14611}
14612
14613#[gpui::test]
14614async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14615    init_test(cx, |language_settings| {
14616        language_settings.defaults.completions = Some(CompletionSettingsContent {
14617            words: Some(WordsCompletionMode::Enabled),
14618            words_min_length: Some(0),
14619            lsp_insert_mode: Some(LspInsertMode::Insert),
14620            ..Default::default()
14621        });
14622    });
14623
14624    let mut cx = EditorLspTestContext::new_rust(
14625        lsp::ServerCapabilities {
14626            completion_provider: Some(lsp::CompletionOptions {
14627                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14628                ..lsp::CompletionOptions::default()
14629            }),
14630            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14631            ..lsp::ServerCapabilities::default()
14632        },
14633        cx,
14634    )
14635    .await;
14636
14637    let _completion_requests_handler =
14638        cx.lsp
14639            .server
14640            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14641                Ok(Some(lsp::CompletionResponse::Array(vec![
14642                    lsp::CompletionItem {
14643                        label: "first".into(),
14644                        ..lsp::CompletionItem::default()
14645                    },
14646                    lsp::CompletionItem {
14647                        label: "last".into(),
14648                        ..lsp::CompletionItem::default()
14649                    },
14650                ])))
14651            });
14652
14653    cx.set_state(indoc! {"ˇ
14654        first
14655        last
14656        second
14657    "});
14658    cx.simulate_keystroke(".");
14659    cx.executor().run_until_parked();
14660    cx.condition(|editor, _| editor.context_menu_visible())
14661        .await;
14662    cx.update_editor(|editor, _, _| {
14663        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14664        {
14665            assert_eq!(
14666                completion_menu_entries(menu),
14667                &["first", "last", "second"],
14668                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14669            );
14670        } else {
14671            panic!("expected completion menu to be open");
14672        }
14673    });
14674}
14675
14676#[gpui::test]
14677async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14678    init_test(cx, |language_settings| {
14679        language_settings.defaults.completions = Some(CompletionSettingsContent {
14680            words: Some(WordsCompletionMode::Disabled),
14681            words_min_length: Some(0),
14682            lsp_insert_mode: Some(LspInsertMode::Insert),
14683            ..Default::default()
14684        });
14685    });
14686
14687    let mut cx = EditorLspTestContext::new_rust(
14688        lsp::ServerCapabilities {
14689            completion_provider: Some(lsp::CompletionOptions {
14690                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14691                ..lsp::CompletionOptions::default()
14692            }),
14693            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14694            ..lsp::ServerCapabilities::default()
14695        },
14696        cx,
14697    )
14698    .await;
14699
14700    let _completion_requests_handler =
14701        cx.lsp
14702            .server
14703            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14704                panic!("LSP completions should not be queried when dealing with word completions")
14705            });
14706
14707    cx.set_state(indoc! {"ˇ
14708        first
14709        last
14710        second
14711    "});
14712    cx.update_editor(|editor, window, cx| {
14713        editor.show_word_completions(&ShowWordCompletions, window, cx);
14714    });
14715    cx.executor().run_until_parked();
14716    cx.condition(|editor, _| editor.context_menu_visible())
14717        .await;
14718    cx.update_editor(|editor, _, _| {
14719        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14720        {
14721            assert_eq!(
14722                completion_menu_entries(menu),
14723                &["first", "last", "second"],
14724                "`ShowWordCompletions` action should show word completions"
14725            );
14726        } else {
14727            panic!("expected completion menu to be open");
14728        }
14729    });
14730
14731    cx.simulate_keystroke("l");
14732    cx.executor().run_until_parked();
14733    cx.condition(|editor, _| editor.context_menu_visible())
14734        .await;
14735    cx.update_editor(|editor, _, _| {
14736        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14737        {
14738            assert_eq!(
14739                completion_menu_entries(menu),
14740                &["last"],
14741                "After showing word completions, further editing should filter them and not query the LSP"
14742            );
14743        } else {
14744            panic!("expected completion menu to be open");
14745        }
14746    });
14747}
14748
14749#[gpui::test]
14750async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14751    init_test(cx, |language_settings| {
14752        language_settings.defaults.completions = Some(CompletionSettingsContent {
14753            words_min_length: Some(0),
14754            lsp: Some(false),
14755            lsp_insert_mode: Some(LspInsertMode::Insert),
14756            ..Default::default()
14757        });
14758    });
14759
14760    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14761
14762    cx.set_state(indoc! {"ˇ
14763        0_usize
14764        let
14765        33
14766        4.5f32
14767    "});
14768    cx.update_editor(|editor, window, cx| {
14769        editor.show_completions(&ShowCompletions::default(), window, cx);
14770    });
14771    cx.executor().run_until_parked();
14772    cx.condition(|editor, _| editor.context_menu_visible())
14773        .await;
14774    cx.update_editor(|editor, window, cx| {
14775        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14776        {
14777            assert_eq!(
14778                completion_menu_entries(menu),
14779                &["let"],
14780                "With no digits in the completion query, no digits should be in the word completions"
14781            );
14782        } else {
14783            panic!("expected completion menu to be open");
14784        }
14785        editor.cancel(&Cancel, window, cx);
14786    });
14787
14788    cx.set_state(indoc! {"14789        0_usize
14790        let
14791        3
14792        33.35f32
14793    "});
14794    cx.update_editor(|editor, window, cx| {
14795        editor.show_completions(&ShowCompletions::default(), window, cx);
14796    });
14797    cx.executor().run_until_parked();
14798    cx.condition(|editor, _| editor.context_menu_visible())
14799        .await;
14800    cx.update_editor(|editor, _, _| {
14801        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14802        {
14803            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14804                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14805        } else {
14806            panic!("expected completion menu to be open");
14807        }
14808    });
14809}
14810
14811#[gpui::test]
14812async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14813    init_test(cx, |language_settings| {
14814        language_settings.defaults.completions = Some(CompletionSettingsContent {
14815            words: Some(WordsCompletionMode::Enabled),
14816            words_min_length: Some(3),
14817            lsp_insert_mode: Some(LspInsertMode::Insert),
14818            ..Default::default()
14819        });
14820    });
14821
14822    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14823    cx.set_state(indoc! {"ˇ
14824        wow
14825        wowen
14826        wowser
14827    "});
14828    cx.simulate_keystroke("w");
14829    cx.executor().run_until_parked();
14830    cx.update_editor(|editor, _, _| {
14831        if editor.context_menu.borrow_mut().is_some() {
14832            panic!(
14833                "expected completion menu to be hidden, as words completion threshold is not met"
14834            );
14835        }
14836    });
14837
14838    cx.update_editor(|editor, window, cx| {
14839        editor.show_word_completions(&ShowWordCompletions, window, cx);
14840    });
14841    cx.executor().run_until_parked();
14842    cx.update_editor(|editor, window, cx| {
14843        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14844        {
14845            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");
14846        } else {
14847            panic!("expected completion menu to be open after the word completions are called with an action");
14848        }
14849
14850        editor.cancel(&Cancel, window, cx);
14851    });
14852    cx.update_editor(|editor, _, _| {
14853        if editor.context_menu.borrow_mut().is_some() {
14854            panic!("expected completion menu to be hidden after canceling");
14855        }
14856    });
14857
14858    cx.simulate_keystroke("o");
14859    cx.executor().run_until_parked();
14860    cx.update_editor(|editor, _, _| {
14861        if editor.context_menu.borrow_mut().is_some() {
14862            panic!(
14863                "expected completion menu to be hidden, as words completion threshold is not met still"
14864            );
14865        }
14866    });
14867
14868    cx.simulate_keystroke("w");
14869    cx.executor().run_until_parked();
14870    cx.update_editor(|editor, _, _| {
14871        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14872        {
14873            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14874        } else {
14875            panic!("expected completion menu to be open after the word completions threshold is met");
14876        }
14877    });
14878}
14879
14880#[gpui::test]
14881async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14882    init_test(cx, |language_settings| {
14883        language_settings.defaults.completions = Some(CompletionSettingsContent {
14884            words: Some(WordsCompletionMode::Enabled),
14885            words_min_length: Some(0),
14886            lsp_insert_mode: Some(LspInsertMode::Insert),
14887            ..Default::default()
14888        });
14889    });
14890
14891    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14892    cx.update_editor(|editor, _, _| {
14893        editor.disable_word_completions();
14894    });
14895    cx.set_state(indoc! {"ˇ
14896        wow
14897        wowen
14898        wowser
14899    "});
14900    cx.simulate_keystroke("w");
14901    cx.executor().run_until_parked();
14902    cx.update_editor(|editor, _, _| {
14903        if editor.context_menu.borrow_mut().is_some() {
14904            panic!(
14905                "expected completion menu to be hidden, as words completion are disabled for this editor"
14906            );
14907        }
14908    });
14909
14910    cx.update_editor(|editor, window, cx| {
14911        editor.show_word_completions(&ShowWordCompletions, window, cx);
14912    });
14913    cx.executor().run_until_parked();
14914    cx.update_editor(|editor, _, _| {
14915        if editor.context_menu.borrow_mut().is_some() {
14916            panic!(
14917                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14918            );
14919        }
14920    });
14921}
14922
14923fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14924    let position = || lsp::Position {
14925        line: params.text_document_position.position.line,
14926        character: params.text_document_position.position.character,
14927    };
14928    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14929        range: lsp::Range {
14930            start: position(),
14931            end: position(),
14932        },
14933        new_text: text.to_string(),
14934    }))
14935}
14936
14937#[gpui::test]
14938async fn test_multiline_completion(cx: &mut TestAppContext) {
14939    init_test(cx, |_| {});
14940
14941    let fs = FakeFs::new(cx.executor());
14942    fs.insert_tree(
14943        path!("/a"),
14944        json!({
14945            "main.ts": "a",
14946        }),
14947    )
14948    .await;
14949
14950    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14951    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14952    let typescript_language = Arc::new(Language::new(
14953        LanguageConfig {
14954            name: "TypeScript".into(),
14955            matcher: LanguageMatcher {
14956                path_suffixes: vec!["ts".to_string()],
14957                ..LanguageMatcher::default()
14958            },
14959            line_comments: vec!["// ".into()],
14960            ..LanguageConfig::default()
14961        },
14962        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14963    ));
14964    language_registry.add(typescript_language.clone());
14965    let mut fake_servers = language_registry.register_fake_lsp(
14966        "TypeScript",
14967        FakeLspAdapter {
14968            capabilities: lsp::ServerCapabilities {
14969                completion_provider: Some(lsp::CompletionOptions {
14970                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14971                    ..lsp::CompletionOptions::default()
14972                }),
14973                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14974                ..lsp::ServerCapabilities::default()
14975            },
14976            // Emulate vtsls label generation
14977            label_for_completion: Some(Box::new(|item, _| {
14978                let text = if let Some(description) = item
14979                    .label_details
14980                    .as_ref()
14981                    .and_then(|label_details| label_details.description.as_ref())
14982                {
14983                    format!("{} {}", item.label, description)
14984                } else if let Some(detail) = &item.detail {
14985                    format!("{} {}", item.label, detail)
14986                } else {
14987                    item.label.clone()
14988                };
14989                Some(language::CodeLabel::plain(text, None))
14990            })),
14991            ..FakeLspAdapter::default()
14992        },
14993    );
14994    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14995    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14996    let worktree_id = workspace
14997        .update(cx, |workspace, _window, cx| {
14998            workspace.project().update(cx, |project, cx| {
14999                project.worktrees(cx).next().unwrap().read(cx).id()
15000            })
15001        })
15002        .unwrap();
15003    let _buffer = project
15004        .update(cx, |project, cx| {
15005            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15006        })
15007        .await
15008        .unwrap();
15009    let editor = workspace
15010        .update(cx, |workspace, window, cx| {
15011            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15012        })
15013        .unwrap()
15014        .await
15015        .unwrap()
15016        .downcast::<Editor>()
15017        .unwrap();
15018    let fake_server = fake_servers.next().await.unwrap();
15019
15020    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15021    let multiline_label_2 = "a\nb\nc\n";
15022    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15023    let multiline_description = "d\ne\nf\n";
15024    let multiline_detail_2 = "g\nh\ni\n";
15025
15026    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15027        move |params, _| async move {
15028            Ok(Some(lsp::CompletionResponse::Array(vec![
15029                lsp::CompletionItem {
15030                    label: multiline_label.to_string(),
15031                    text_edit: gen_text_edit(&params, "new_text_1"),
15032                    ..lsp::CompletionItem::default()
15033                },
15034                lsp::CompletionItem {
15035                    label: "single line label 1".to_string(),
15036                    detail: Some(multiline_detail.to_string()),
15037                    text_edit: gen_text_edit(&params, "new_text_2"),
15038                    ..lsp::CompletionItem::default()
15039                },
15040                lsp::CompletionItem {
15041                    label: "single line label 2".to_string(),
15042                    label_details: Some(lsp::CompletionItemLabelDetails {
15043                        description: Some(multiline_description.to_string()),
15044                        detail: None,
15045                    }),
15046                    text_edit: gen_text_edit(&params, "new_text_2"),
15047                    ..lsp::CompletionItem::default()
15048                },
15049                lsp::CompletionItem {
15050                    label: multiline_label_2.to_string(),
15051                    detail: Some(multiline_detail_2.to_string()),
15052                    text_edit: gen_text_edit(&params, "new_text_3"),
15053                    ..lsp::CompletionItem::default()
15054                },
15055                lsp::CompletionItem {
15056                    label: "Label with many     spaces and \t but without newlines".to_string(),
15057                    detail: Some(
15058                        "Details with many     spaces and \t but without newlines".to_string(),
15059                    ),
15060                    text_edit: gen_text_edit(&params, "new_text_4"),
15061                    ..lsp::CompletionItem::default()
15062                },
15063            ])))
15064        },
15065    );
15066
15067    editor.update_in(cx, |editor, window, cx| {
15068        cx.focus_self(window);
15069        editor.move_to_end(&MoveToEnd, window, cx);
15070        editor.handle_input(".", window, cx);
15071    });
15072    cx.run_until_parked();
15073    completion_handle.next().await.unwrap();
15074
15075    editor.update(cx, |editor, _| {
15076        assert!(editor.context_menu_visible());
15077        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15078        {
15079            let completion_labels = menu
15080                .completions
15081                .borrow()
15082                .iter()
15083                .map(|c| c.label.text.clone())
15084                .collect::<Vec<_>>();
15085            assert_eq!(
15086                completion_labels,
15087                &[
15088                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15089                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15090                    "single line label 2 d e f ",
15091                    "a b c g h i ",
15092                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15093                ],
15094                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15095            );
15096
15097            for completion in menu
15098                .completions
15099                .borrow()
15100                .iter() {
15101                    assert_eq!(
15102                        completion.label.filter_range,
15103                        0..completion.label.text.len(),
15104                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15105                    );
15106                }
15107        } else {
15108            panic!("expected completion menu to be open");
15109        }
15110    });
15111}
15112
15113#[gpui::test]
15114async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15115    init_test(cx, |_| {});
15116    let mut cx = EditorLspTestContext::new_rust(
15117        lsp::ServerCapabilities {
15118            completion_provider: Some(lsp::CompletionOptions {
15119                trigger_characters: Some(vec![".".to_string()]),
15120                ..Default::default()
15121            }),
15122            ..Default::default()
15123        },
15124        cx,
15125    )
15126    .await;
15127    cx.lsp
15128        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15129            Ok(Some(lsp::CompletionResponse::Array(vec![
15130                lsp::CompletionItem {
15131                    label: "first".into(),
15132                    ..Default::default()
15133                },
15134                lsp::CompletionItem {
15135                    label: "last".into(),
15136                    ..Default::default()
15137                },
15138            ])))
15139        });
15140    cx.set_state("variableˇ");
15141    cx.simulate_keystroke(".");
15142    cx.executor().run_until_parked();
15143
15144    cx.update_editor(|editor, _, _| {
15145        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15146        {
15147            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15148        } else {
15149            panic!("expected completion menu to be open");
15150        }
15151    });
15152
15153    cx.update_editor(|editor, window, cx| {
15154        editor.move_page_down(&MovePageDown::default(), window, cx);
15155        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15156        {
15157            assert!(
15158                menu.selected_item == 1,
15159                "expected PageDown to select the last item from the context menu"
15160            );
15161        } else {
15162            panic!("expected completion menu to stay open after PageDown");
15163        }
15164    });
15165
15166    cx.update_editor(|editor, window, cx| {
15167        editor.move_page_up(&MovePageUp::default(), window, cx);
15168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15169        {
15170            assert!(
15171                menu.selected_item == 0,
15172                "expected PageUp to select the first item from the context menu"
15173            );
15174        } else {
15175            panic!("expected completion menu to stay open after PageUp");
15176        }
15177    });
15178}
15179
15180#[gpui::test]
15181async fn test_as_is_completions(cx: &mut TestAppContext) {
15182    init_test(cx, |_| {});
15183    let mut cx = EditorLspTestContext::new_rust(
15184        lsp::ServerCapabilities {
15185            completion_provider: Some(lsp::CompletionOptions {
15186                ..Default::default()
15187            }),
15188            ..Default::default()
15189        },
15190        cx,
15191    )
15192    .await;
15193    cx.lsp
15194        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15195            Ok(Some(lsp::CompletionResponse::Array(vec![
15196                lsp::CompletionItem {
15197                    label: "unsafe".into(),
15198                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15199                        range: lsp::Range {
15200                            start: lsp::Position {
15201                                line: 1,
15202                                character: 2,
15203                            },
15204                            end: lsp::Position {
15205                                line: 1,
15206                                character: 3,
15207                            },
15208                        },
15209                        new_text: "unsafe".to_string(),
15210                    })),
15211                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15212                    ..Default::default()
15213                },
15214            ])))
15215        });
15216    cx.set_state("fn a() {}\n");
15217    cx.executor().run_until_parked();
15218    cx.update_editor(|editor, window, cx| {
15219        editor.show_completions(
15220            &ShowCompletions {
15221                trigger: Some("\n".into()),
15222            },
15223            window,
15224            cx,
15225        );
15226    });
15227    cx.executor().run_until_parked();
15228
15229    cx.update_editor(|editor, window, cx| {
15230        editor.confirm_completion(&Default::default(), window, cx)
15231    });
15232    cx.executor().run_until_parked();
15233    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15234}
15235
15236#[gpui::test]
15237async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15238    init_test(cx, |_| {});
15239    let language =
15240        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15241    let mut cx = EditorLspTestContext::new(
15242        language,
15243        lsp::ServerCapabilities {
15244            completion_provider: Some(lsp::CompletionOptions {
15245                ..lsp::CompletionOptions::default()
15246            }),
15247            ..lsp::ServerCapabilities::default()
15248        },
15249        cx,
15250    )
15251    .await;
15252
15253    cx.set_state(
15254        "#ifndef BAR_H
15255#define BAR_H
15256
15257#include <stdbool.h>
15258
15259int fn_branch(bool do_branch1, bool do_branch2);
15260
15261#endif // BAR_H
15262ˇ",
15263    );
15264    cx.executor().run_until_parked();
15265    cx.update_editor(|editor, window, cx| {
15266        editor.handle_input("#", window, cx);
15267    });
15268    cx.executor().run_until_parked();
15269    cx.update_editor(|editor, window, cx| {
15270        editor.handle_input("i", window, cx);
15271    });
15272    cx.executor().run_until_parked();
15273    cx.update_editor(|editor, window, cx| {
15274        editor.handle_input("n", window, cx);
15275    });
15276    cx.executor().run_until_parked();
15277    cx.assert_editor_state(
15278        "#ifndef BAR_H
15279#define BAR_H
15280
15281#include <stdbool.h>
15282
15283int fn_branch(bool do_branch1, bool do_branch2);
15284
15285#endif // BAR_H
15286#inˇ",
15287    );
15288
15289    cx.lsp
15290        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15291            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15292                is_incomplete: false,
15293                item_defaults: None,
15294                items: vec![lsp::CompletionItem {
15295                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15296                    label_details: Some(lsp::CompletionItemLabelDetails {
15297                        detail: Some("header".to_string()),
15298                        description: None,
15299                    }),
15300                    label: " include".to_string(),
15301                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15302                        range: lsp::Range {
15303                            start: lsp::Position {
15304                                line: 8,
15305                                character: 1,
15306                            },
15307                            end: lsp::Position {
15308                                line: 8,
15309                                character: 1,
15310                            },
15311                        },
15312                        new_text: "include \"$0\"".to_string(),
15313                    })),
15314                    sort_text: Some("40b67681include".to_string()),
15315                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15316                    filter_text: Some("include".to_string()),
15317                    insert_text: Some("include \"$0\"".to_string()),
15318                    ..lsp::CompletionItem::default()
15319                }],
15320            })))
15321        });
15322    cx.update_editor(|editor, window, cx| {
15323        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15324    });
15325    cx.executor().run_until_parked();
15326    cx.update_editor(|editor, window, cx| {
15327        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15328    });
15329    cx.executor().run_until_parked();
15330    cx.assert_editor_state(
15331        "#ifndef BAR_H
15332#define BAR_H
15333
15334#include <stdbool.h>
15335
15336int fn_branch(bool do_branch1, bool do_branch2);
15337
15338#endif // BAR_H
15339#include \"ˇ\"",
15340    );
15341
15342    cx.lsp
15343        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15344            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15345                is_incomplete: true,
15346                item_defaults: None,
15347                items: vec![lsp::CompletionItem {
15348                    kind: Some(lsp::CompletionItemKind::FILE),
15349                    label: "AGL/".to_string(),
15350                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15351                        range: lsp::Range {
15352                            start: lsp::Position {
15353                                line: 8,
15354                                character: 10,
15355                            },
15356                            end: lsp::Position {
15357                                line: 8,
15358                                character: 11,
15359                            },
15360                        },
15361                        new_text: "AGL/".to_string(),
15362                    })),
15363                    sort_text: Some("40b67681AGL/".to_string()),
15364                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15365                    filter_text: Some("AGL/".to_string()),
15366                    insert_text: Some("AGL/".to_string()),
15367                    ..lsp::CompletionItem::default()
15368                }],
15369            })))
15370        });
15371    cx.update_editor(|editor, window, cx| {
15372        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15373    });
15374    cx.executor().run_until_parked();
15375    cx.update_editor(|editor, window, cx| {
15376        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15377    });
15378    cx.executor().run_until_parked();
15379    cx.assert_editor_state(
15380        r##"#ifndef BAR_H
15381#define BAR_H
15382
15383#include <stdbool.h>
15384
15385int fn_branch(bool do_branch1, bool do_branch2);
15386
15387#endif // BAR_H
15388#include "AGL/ˇ"##,
15389    );
15390
15391    cx.update_editor(|editor, window, cx| {
15392        editor.handle_input("\"", window, cx);
15393    });
15394    cx.executor().run_until_parked();
15395    cx.assert_editor_state(
15396        r##"#ifndef BAR_H
15397#define BAR_H
15398
15399#include <stdbool.h>
15400
15401int fn_branch(bool do_branch1, bool do_branch2);
15402
15403#endif // BAR_H
15404#include "AGL/"ˇ"##,
15405    );
15406}
15407
15408#[gpui::test]
15409async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15410    init_test(cx, |_| {});
15411
15412    let mut cx = EditorLspTestContext::new_rust(
15413        lsp::ServerCapabilities {
15414            completion_provider: Some(lsp::CompletionOptions {
15415                trigger_characters: Some(vec![".".to_string()]),
15416                resolve_provider: Some(true),
15417                ..Default::default()
15418            }),
15419            ..Default::default()
15420        },
15421        cx,
15422    )
15423    .await;
15424
15425    cx.set_state("fn main() { let a = 2ˇ; }");
15426    cx.simulate_keystroke(".");
15427    let completion_item = lsp::CompletionItem {
15428        label: "Some".into(),
15429        kind: Some(lsp::CompletionItemKind::SNIPPET),
15430        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15431        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15432            kind: lsp::MarkupKind::Markdown,
15433            value: "```rust\nSome(2)\n```".to_string(),
15434        })),
15435        deprecated: Some(false),
15436        sort_text: Some("Some".to_string()),
15437        filter_text: Some("Some".to_string()),
15438        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15439        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15440            range: lsp::Range {
15441                start: lsp::Position {
15442                    line: 0,
15443                    character: 22,
15444                },
15445                end: lsp::Position {
15446                    line: 0,
15447                    character: 22,
15448                },
15449            },
15450            new_text: "Some(2)".to_string(),
15451        })),
15452        additional_text_edits: Some(vec![lsp::TextEdit {
15453            range: lsp::Range {
15454                start: lsp::Position {
15455                    line: 0,
15456                    character: 20,
15457                },
15458                end: lsp::Position {
15459                    line: 0,
15460                    character: 22,
15461                },
15462            },
15463            new_text: "".to_string(),
15464        }]),
15465        ..Default::default()
15466    };
15467
15468    let closure_completion_item = completion_item.clone();
15469    let counter = Arc::new(AtomicUsize::new(0));
15470    let counter_clone = counter.clone();
15471    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15472        let task_completion_item = closure_completion_item.clone();
15473        counter_clone.fetch_add(1, atomic::Ordering::Release);
15474        async move {
15475            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15476                is_incomplete: true,
15477                item_defaults: None,
15478                items: vec![task_completion_item],
15479            })))
15480        }
15481    });
15482
15483    cx.condition(|editor, _| editor.context_menu_visible())
15484        .await;
15485    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15486    assert!(request.next().await.is_some());
15487    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15488
15489    cx.simulate_keystrokes("S o m");
15490    cx.condition(|editor, _| editor.context_menu_visible())
15491        .await;
15492    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15493    assert!(request.next().await.is_some());
15494    assert!(request.next().await.is_some());
15495    assert!(request.next().await.is_some());
15496    request.close();
15497    assert!(request.next().await.is_none());
15498    assert_eq!(
15499        counter.load(atomic::Ordering::Acquire),
15500        4,
15501        "With the completions menu open, only one LSP request should happen per input"
15502    );
15503}
15504
15505#[gpui::test]
15506async fn test_toggle_comment(cx: &mut TestAppContext) {
15507    init_test(cx, |_| {});
15508    let mut cx = EditorTestContext::new(cx).await;
15509    let language = Arc::new(Language::new(
15510        LanguageConfig {
15511            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15512            ..Default::default()
15513        },
15514        Some(tree_sitter_rust::LANGUAGE.into()),
15515    ));
15516    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15517
15518    // If multiple selections intersect a line, the line is only toggled once.
15519    cx.set_state(indoc! {"
15520        fn a() {
15521            «//b();
15522            ˇ»// «c();
15523            //ˇ»  d();
15524        }
15525    "});
15526
15527    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15528
15529    cx.assert_editor_state(indoc! {"
15530        fn a() {
15531            «b();
15532            c();
15533            ˇ» d();
15534        }
15535    "});
15536
15537    // The comment prefix is inserted at the same column for every line in a
15538    // selection.
15539    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15540
15541    cx.assert_editor_state(indoc! {"
15542        fn a() {
15543            // «b();
15544            // c();
15545            ˇ»//  d();
15546        }
15547    "});
15548
15549    // If a selection ends at the beginning of a line, that line is not toggled.
15550    cx.set_selections_state(indoc! {"
15551        fn a() {
15552            // b();
15553            «// c();
15554        ˇ»    //  d();
15555        }
15556    "});
15557
15558    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15559
15560    cx.assert_editor_state(indoc! {"
15561        fn a() {
15562            // b();
15563            «c();
15564        ˇ»    //  d();
15565        }
15566    "});
15567
15568    // If a selection span a single line and is empty, the line is toggled.
15569    cx.set_state(indoc! {"
15570        fn a() {
15571            a();
15572            b();
15573        ˇ
15574        }
15575    "});
15576
15577    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15578
15579    cx.assert_editor_state(indoc! {"
15580        fn a() {
15581            a();
15582            b();
15583        //•ˇ
15584        }
15585    "});
15586
15587    // If a selection span multiple lines, empty lines are not toggled.
15588    cx.set_state(indoc! {"
15589        fn a() {
15590            «a();
15591
15592            c();ˇ»
15593        }
15594    "});
15595
15596    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15597
15598    cx.assert_editor_state(indoc! {"
15599        fn a() {
15600            // «a();
15601
15602            // c();ˇ»
15603        }
15604    "});
15605
15606    // If a selection includes multiple comment prefixes, all lines are uncommented.
15607    cx.set_state(indoc! {"
15608        fn a() {
15609            «// a();
15610            /// b();
15611            //! c();ˇ»
15612        }
15613    "});
15614
15615    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15616
15617    cx.assert_editor_state(indoc! {"
15618        fn a() {
15619            «a();
15620            b();
15621            c();ˇ»
15622        }
15623    "});
15624}
15625
15626#[gpui::test]
15627async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15628    init_test(cx, |_| {});
15629    let mut cx = EditorTestContext::new(cx).await;
15630    let language = Arc::new(Language::new(
15631        LanguageConfig {
15632            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15633            ..Default::default()
15634        },
15635        Some(tree_sitter_rust::LANGUAGE.into()),
15636    ));
15637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15638
15639    let toggle_comments = &ToggleComments {
15640        advance_downwards: false,
15641        ignore_indent: true,
15642    };
15643
15644    // If multiple selections intersect a line, the line is only toggled once.
15645    cx.set_state(indoc! {"
15646        fn a() {
15647        //    «b();
15648        //    c();
15649        //    ˇ» d();
15650        }
15651    "});
15652
15653    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15654
15655    cx.assert_editor_state(indoc! {"
15656        fn a() {
15657            «b();
15658            c();
15659            ˇ» d();
15660        }
15661    "});
15662
15663    // The comment prefix is inserted at the beginning of each line
15664    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15665
15666    cx.assert_editor_state(indoc! {"
15667        fn a() {
15668        //    «b();
15669        //    c();
15670        //    ˇ» d();
15671        }
15672    "});
15673
15674    // If a selection ends at the beginning of a line, that line is not toggled.
15675    cx.set_selections_state(indoc! {"
15676        fn a() {
15677        //    b();
15678        //    «c();
15679        ˇ»//     d();
15680        }
15681    "});
15682
15683    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15684
15685    cx.assert_editor_state(indoc! {"
15686        fn a() {
15687        //    b();
15688            «c();
15689        ˇ»//     d();
15690        }
15691    "});
15692
15693    // If a selection span a single line and is empty, the line is toggled.
15694    cx.set_state(indoc! {"
15695        fn a() {
15696            a();
15697            b();
15698        ˇ
15699        }
15700    "});
15701
15702    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15703
15704    cx.assert_editor_state(indoc! {"
15705        fn a() {
15706            a();
15707            b();
15708        //ˇ
15709        }
15710    "});
15711
15712    // If a selection span multiple lines, empty lines are not toggled.
15713    cx.set_state(indoc! {"
15714        fn a() {
15715            «a();
15716
15717            c();ˇ»
15718        }
15719    "});
15720
15721    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15722
15723    cx.assert_editor_state(indoc! {"
15724        fn a() {
15725        //    «a();
15726
15727        //    c();ˇ»
15728        }
15729    "});
15730
15731    // If a selection includes multiple comment prefixes, all lines are uncommented.
15732    cx.set_state(indoc! {"
15733        fn a() {
15734        //    «a();
15735        ///    b();
15736        //!    c();ˇ»
15737        }
15738    "});
15739
15740    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15741
15742    cx.assert_editor_state(indoc! {"
15743        fn a() {
15744            «a();
15745            b();
15746            c();ˇ»
15747        }
15748    "});
15749}
15750
15751#[gpui::test]
15752async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15753    init_test(cx, |_| {});
15754
15755    let language = Arc::new(Language::new(
15756        LanguageConfig {
15757            line_comments: vec!["// ".into()],
15758            ..Default::default()
15759        },
15760        Some(tree_sitter_rust::LANGUAGE.into()),
15761    ));
15762
15763    let mut cx = EditorTestContext::new(cx).await;
15764
15765    cx.language_registry().add(language.clone());
15766    cx.update_buffer(|buffer, cx| {
15767        buffer.set_language(Some(language), cx);
15768    });
15769
15770    let toggle_comments = &ToggleComments {
15771        advance_downwards: true,
15772        ignore_indent: false,
15773    };
15774
15775    // Single cursor on one line -> advance
15776    // Cursor moves horizontally 3 characters as well on non-blank line
15777    cx.set_state(indoc!(
15778        "fn a() {
15779             ˇdog();
15780             cat();
15781        }"
15782    ));
15783    cx.update_editor(|editor, window, cx| {
15784        editor.toggle_comments(toggle_comments, window, cx);
15785    });
15786    cx.assert_editor_state(indoc!(
15787        "fn a() {
15788             // dog();
15789             catˇ();
15790        }"
15791    ));
15792
15793    // Single selection on one line -> don't advance
15794    cx.set_state(indoc!(
15795        "fn a() {
15796             «dog()ˇ»;
15797             cat();
15798        }"
15799    ));
15800    cx.update_editor(|editor, window, cx| {
15801        editor.toggle_comments(toggle_comments, window, cx);
15802    });
15803    cx.assert_editor_state(indoc!(
15804        "fn a() {
15805             // «dog()ˇ»;
15806             cat();
15807        }"
15808    ));
15809
15810    // Multiple cursors on one line -> advance
15811    cx.set_state(indoc!(
15812        "fn a() {
15813             ˇdˇog();
15814             cat();
15815        }"
15816    ));
15817    cx.update_editor(|editor, window, cx| {
15818        editor.toggle_comments(toggle_comments, window, cx);
15819    });
15820    cx.assert_editor_state(indoc!(
15821        "fn a() {
15822             // dog();
15823             catˇ(ˇ);
15824        }"
15825    ));
15826
15827    // Multiple cursors on one line, with selection -> don't advance
15828    cx.set_state(indoc!(
15829        "fn a() {
15830             ˇdˇog«()ˇ»;
15831             cat();
15832        }"
15833    ));
15834    cx.update_editor(|editor, window, cx| {
15835        editor.toggle_comments(toggle_comments, window, cx);
15836    });
15837    cx.assert_editor_state(indoc!(
15838        "fn a() {
15839             // ˇdˇog«()ˇ»;
15840             cat();
15841        }"
15842    ));
15843
15844    // Single cursor on one line -> advance
15845    // Cursor moves to column 0 on blank line
15846    cx.set_state(indoc!(
15847        "fn a() {
15848             ˇdog();
15849
15850             cat();
15851        }"
15852    ));
15853    cx.update_editor(|editor, window, cx| {
15854        editor.toggle_comments(toggle_comments, window, cx);
15855    });
15856    cx.assert_editor_state(indoc!(
15857        "fn a() {
15858             // dog();
15859        ˇ
15860             cat();
15861        }"
15862    ));
15863
15864    // Single cursor on one line -> advance
15865    // Cursor starts and ends at column 0
15866    cx.set_state(indoc!(
15867        "fn a() {
15868         ˇ    dog();
15869             cat();
15870        }"
15871    ));
15872    cx.update_editor(|editor, window, cx| {
15873        editor.toggle_comments(toggle_comments, window, cx);
15874    });
15875    cx.assert_editor_state(indoc!(
15876        "fn a() {
15877             // dog();
15878         ˇ    cat();
15879        }"
15880    ));
15881}
15882
15883#[gpui::test]
15884async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15885    init_test(cx, |_| {});
15886
15887    let mut cx = EditorTestContext::new(cx).await;
15888
15889    let html_language = Arc::new(
15890        Language::new(
15891            LanguageConfig {
15892                name: "HTML".into(),
15893                block_comment: Some(BlockCommentConfig {
15894                    start: "<!-- ".into(),
15895                    prefix: "".into(),
15896                    end: " -->".into(),
15897                    tab_size: 0,
15898                }),
15899                ..Default::default()
15900            },
15901            Some(tree_sitter_html::LANGUAGE.into()),
15902        )
15903        .with_injection_query(
15904            r#"
15905            (script_element
15906                (raw_text) @injection.content
15907                (#set! injection.language "javascript"))
15908            "#,
15909        )
15910        .unwrap(),
15911    );
15912
15913    let javascript_language = Arc::new(Language::new(
15914        LanguageConfig {
15915            name: "JavaScript".into(),
15916            line_comments: vec!["// ".into()],
15917            ..Default::default()
15918        },
15919        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15920    ));
15921
15922    cx.language_registry().add(html_language.clone());
15923    cx.language_registry().add(javascript_language);
15924    cx.update_buffer(|buffer, cx| {
15925        buffer.set_language(Some(html_language), cx);
15926    });
15927
15928    // Toggle comments for empty selections
15929    cx.set_state(
15930        &r#"
15931            <p>A</p>ˇ
15932            <p>B</p>ˇ
15933            <p>C</p>ˇ
15934        "#
15935        .unindent(),
15936    );
15937    cx.update_editor(|editor, window, cx| {
15938        editor.toggle_comments(&ToggleComments::default(), window, cx)
15939    });
15940    cx.assert_editor_state(
15941        &r#"
15942            <!-- <p>A</p>ˇ -->
15943            <!-- <p>B</p>ˇ -->
15944            <!-- <p>C</p>ˇ -->
15945        "#
15946        .unindent(),
15947    );
15948    cx.update_editor(|editor, window, cx| {
15949        editor.toggle_comments(&ToggleComments::default(), window, cx)
15950    });
15951    cx.assert_editor_state(
15952        &r#"
15953            <p>A</p>ˇ
15954            <p>B</p>ˇ
15955            <p>C</p>ˇ
15956        "#
15957        .unindent(),
15958    );
15959
15960    // Toggle comments for mixture of empty and non-empty selections, where
15961    // multiple selections occupy a given line.
15962    cx.set_state(
15963        &r#"
15964            <p>A«</p>
15965            <p>ˇ»B</p>ˇ
15966            <p>C«</p>
15967            <p>ˇ»D</p>ˇ
15968        "#
15969        .unindent(),
15970    );
15971
15972    cx.update_editor(|editor, window, cx| {
15973        editor.toggle_comments(&ToggleComments::default(), window, cx)
15974    });
15975    cx.assert_editor_state(
15976        &r#"
15977            <!-- <p>A«</p>
15978            <p>ˇ»B</p>ˇ -->
15979            <!-- <p>C«</p>
15980            <p>ˇ»D</p>ˇ -->
15981        "#
15982        .unindent(),
15983    );
15984    cx.update_editor(|editor, window, cx| {
15985        editor.toggle_comments(&ToggleComments::default(), window, cx)
15986    });
15987    cx.assert_editor_state(
15988        &r#"
15989            <p>A«</p>
15990            <p>ˇ»B</p>ˇ
15991            <p>C«</p>
15992            <p>ˇ»D</p>ˇ
15993        "#
15994        .unindent(),
15995    );
15996
15997    // Toggle comments when different languages are active for different
15998    // selections.
15999    cx.set_state(
16000        &r#"
16001            ˇ<script>
16002                ˇvar x = new Y();
16003            ˇ</script>
16004        "#
16005        .unindent(),
16006    );
16007    cx.executor().run_until_parked();
16008    cx.update_editor(|editor, window, cx| {
16009        editor.toggle_comments(&ToggleComments::default(), window, cx)
16010    });
16011    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16012    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16013    cx.assert_editor_state(
16014        &r#"
16015            <!-- ˇ<script> -->
16016                // ˇvar x = new Y();
16017            <!-- ˇ</script> -->
16018        "#
16019        .unindent(),
16020    );
16021}
16022
16023#[gpui::test]
16024fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16025    init_test(cx, |_| {});
16026
16027    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16028    let multibuffer = cx.new(|cx| {
16029        let mut multibuffer = MultiBuffer::new(ReadWrite);
16030        multibuffer.push_excerpts(
16031            buffer.clone(),
16032            [
16033                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16034                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16035            ],
16036            cx,
16037        );
16038        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16039        multibuffer
16040    });
16041
16042    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16043    editor.update_in(cx, |editor, window, cx| {
16044        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16046            s.select_ranges([
16047                Point::new(0, 0)..Point::new(0, 0),
16048                Point::new(1, 0)..Point::new(1, 0),
16049            ])
16050        });
16051
16052        editor.handle_input("X", window, cx);
16053        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16054        assert_eq!(
16055            editor.selections.ranges(&editor.display_snapshot(cx)),
16056            [
16057                Point::new(0, 1)..Point::new(0, 1),
16058                Point::new(1, 1)..Point::new(1, 1),
16059            ]
16060        );
16061
16062        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16064            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16065        });
16066        editor.backspace(&Default::default(), window, cx);
16067        assert_eq!(editor.text(cx), "Xa\nbbb");
16068        assert_eq!(
16069            editor.selections.ranges(&editor.display_snapshot(cx)),
16070            [Point::new(1, 0)..Point::new(1, 0)]
16071        );
16072
16073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16074            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16075        });
16076        editor.backspace(&Default::default(), window, cx);
16077        assert_eq!(editor.text(cx), "X\nbb");
16078        assert_eq!(
16079            editor.selections.ranges(&editor.display_snapshot(cx)),
16080            [Point::new(0, 1)..Point::new(0, 1)]
16081        );
16082    });
16083}
16084
16085#[gpui::test]
16086fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16087    init_test(cx, |_| {});
16088
16089    let markers = vec![('[', ']').into(), ('(', ')').into()];
16090    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16091        indoc! {"
16092            [aaaa
16093            (bbbb]
16094            cccc)",
16095        },
16096        markers.clone(),
16097    );
16098    let excerpt_ranges = markers.into_iter().map(|marker| {
16099        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16100        ExcerptRange::new(context)
16101    });
16102    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16103    let multibuffer = cx.new(|cx| {
16104        let mut multibuffer = MultiBuffer::new(ReadWrite);
16105        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16106        multibuffer
16107    });
16108
16109    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16110    editor.update_in(cx, |editor, window, cx| {
16111        let (expected_text, selection_ranges) = marked_text_ranges(
16112            indoc! {"
16113                aaaa
16114                bˇbbb
16115                bˇbbˇb
16116                cccc"
16117            },
16118            true,
16119        );
16120        assert_eq!(editor.text(cx), expected_text);
16121        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16122            s.select_ranges(selection_ranges)
16123        });
16124
16125        editor.handle_input("X", window, cx);
16126
16127        let (expected_text, expected_selections) = marked_text_ranges(
16128            indoc! {"
16129                aaaa
16130                bXˇbbXb
16131                bXˇbbXˇb
16132                cccc"
16133            },
16134            false,
16135        );
16136        assert_eq!(editor.text(cx), expected_text);
16137        assert_eq!(
16138            editor.selections.ranges(&editor.display_snapshot(cx)),
16139            expected_selections
16140        );
16141
16142        editor.newline(&Newline, window, cx);
16143        let (expected_text, expected_selections) = marked_text_ranges(
16144            indoc! {"
16145                aaaa
16146                bX
16147                ˇbbX
16148                b
16149                bX
16150                ˇbbX
16151                ˇb
16152                cccc"
16153            },
16154            false,
16155        );
16156        assert_eq!(editor.text(cx), expected_text);
16157        assert_eq!(
16158            editor.selections.ranges(&editor.display_snapshot(cx)),
16159            expected_selections
16160        );
16161    });
16162}
16163
16164#[gpui::test]
16165fn test_refresh_selections(cx: &mut TestAppContext) {
16166    init_test(cx, |_| {});
16167
16168    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16169    let mut excerpt1_id = None;
16170    let multibuffer = cx.new(|cx| {
16171        let mut multibuffer = MultiBuffer::new(ReadWrite);
16172        excerpt1_id = multibuffer
16173            .push_excerpts(
16174                buffer.clone(),
16175                [
16176                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16177                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16178                ],
16179                cx,
16180            )
16181            .into_iter()
16182            .next();
16183        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16184        multibuffer
16185    });
16186
16187    let editor = cx.add_window(|window, cx| {
16188        let mut editor = build_editor(multibuffer.clone(), window, cx);
16189        let snapshot = editor.snapshot(window, cx);
16190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16191            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16192        });
16193        editor.begin_selection(
16194            Point::new(2, 1).to_display_point(&snapshot),
16195            true,
16196            1,
16197            window,
16198            cx,
16199        );
16200        assert_eq!(
16201            editor.selections.ranges(&editor.display_snapshot(cx)),
16202            [
16203                Point::new(1, 3)..Point::new(1, 3),
16204                Point::new(2, 1)..Point::new(2, 1),
16205            ]
16206        );
16207        editor
16208    });
16209
16210    // Refreshing selections is a no-op when excerpts haven't changed.
16211    _ = editor.update(cx, |editor, window, cx| {
16212        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16213        assert_eq!(
16214            editor.selections.ranges(&editor.display_snapshot(cx)),
16215            [
16216                Point::new(1, 3)..Point::new(1, 3),
16217                Point::new(2, 1)..Point::new(2, 1),
16218            ]
16219        );
16220    });
16221
16222    multibuffer.update(cx, |multibuffer, cx| {
16223        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16224    });
16225    _ = editor.update(cx, |editor, window, cx| {
16226        // Removing an excerpt causes the first selection to become degenerate.
16227        assert_eq!(
16228            editor.selections.ranges(&editor.display_snapshot(cx)),
16229            [
16230                Point::new(0, 0)..Point::new(0, 0),
16231                Point::new(0, 1)..Point::new(0, 1)
16232            ]
16233        );
16234
16235        // Refreshing selections will relocate the first selection to the original buffer
16236        // location.
16237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16238        assert_eq!(
16239            editor.selections.ranges(&editor.display_snapshot(cx)),
16240            [
16241                Point::new(0, 1)..Point::new(0, 1),
16242                Point::new(0, 3)..Point::new(0, 3)
16243            ]
16244        );
16245        assert!(editor.selections.pending_anchor().is_some());
16246    });
16247}
16248
16249#[gpui::test]
16250fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16251    init_test(cx, |_| {});
16252
16253    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16254    let mut excerpt1_id = None;
16255    let multibuffer = cx.new(|cx| {
16256        let mut multibuffer = MultiBuffer::new(ReadWrite);
16257        excerpt1_id = multibuffer
16258            .push_excerpts(
16259                buffer.clone(),
16260                [
16261                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16262                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16263                ],
16264                cx,
16265            )
16266            .into_iter()
16267            .next();
16268        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16269        multibuffer
16270    });
16271
16272    let editor = cx.add_window(|window, cx| {
16273        let mut editor = build_editor(multibuffer.clone(), window, cx);
16274        let snapshot = editor.snapshot(window, cx);
16275        editor.begin_selection(
16276            Point::new(1, 3).to_display_point(&snapshot),
16277            false,
16278            1,
16279            window,
16280            cx,
16281        );
16282        assert_eq!(
16283            editor.selections.ranges(&editor.display_snapshot(cx)),
16284            [Point::new(1, 3)..Point::new(1, 3)]
16285        );
16286        editor
16287    });
16288
16289    multibuffer.update(cx, |multibuffer, cx| {
16290        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16291    });
16292    _ = editor.update(cx, |editor, window, cx| {
16293        assert_eq!(
16294            editor.selections.ranges(&editor.display_snapshot(cx)),
16295            [Point::new(0, 0)..Point::new(0, 0)]
16296        );
16297
16298        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16300        assert_eq!(
16301            editor.selections.ranges(&editor.display_snapshot(cx)),
16302            [Point::new(0, 3)..Point::new(0, 3)]
16303        );
16304        assert!(editor.selections.pending_anchor().is_some());
16305    });
16306}
16307
16308#[gpui::test]
16309async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16310    init_test(cx, |_| {});
16311
16312    let language = Arc::new(
16313        Language::new(
16314            LanguageConfig {
16315                brackets: BracketPairConfig {
16316                    pairs: vec![
16317                        BracketPair {
16318                            start: "{".to_string(),
16319                            end: "}".to_string(),
16320                            close: true,
16321                            surround: true,
16322                            newline: true,
16323                        },
16324                        BracketPair {
16325                            start: "/* ".to_string(),
16326                            end: " */".to_string(),
16327                            close: true,
16328                            surround: true,
16329                            newline: true,
16330                        },
16331                    ],
16332                    ..Default::default()
16333                },
16334                ..Default::default()
16335            },
16336            Some(tree_sitter_rust::LANGUAGE.into()),
16337        )
16338        .with_indents_query("")
16339        .unwrap(),
16340    );
16341
16342    let text = concat!(
16343        "{   }\n",     //
16344        "  x\n",       //
16345        "  /*   */\n", //
16346        "x\n",         //
16347        "{{} }\n",     //
16348    );
16349
16350    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16351    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16352    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16353    editor
16354        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16355        .await;
16356
16357    editor.update_in(cx, |editor, window, cx| {
16358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16359            s.select_display_ranges([
16360                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16361                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16362                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16363            ])
16364        });
16365        editor.newline(&Newline, window, cx);
16366
16367        assert_eq!(
16368            editor.buffer().read(cx).read(cx).text(),
16369            concat!(
16370                "{ \n",    // Suppress rustfmt
16371                "\n",      //
16372                "}\n",     //
16373                "  x\n",   //
16374                "  /* \n", //
16375                "  \n",    //
16376                "  */\n",  //
16377                "x\n",     //
16378                "{{} \n",  //
16379                "}\n",     //
16380            )
16381        );
16382    });
16383}
16384
16385#[gpui::test]
16386fn test_highlighted_ranges(cx: &mut TestAppContext) {
16387    init_test(cx, |_| {});
16388
16389    let editor = cx.add_window(|window, cx| {
16390        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16391        build_editor(buffer, window, cx)
16392    });
16393
16394    _ = editor.update(cx, |editor, window, cx| {
16395        struct Type1;
16396        struct Type2;
16397
16398        let buffer = editor.buffer.read(cx).snapshot(cx);
16399
16400        let anchor_range =
16401            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16402
16403        editor.highlight_background::<Type1>(
16404            &[
16405                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16406                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16407                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16408                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16409            ],
16410            |_| Hsla::red(),
16411            cx,
16412        );
16413        editor.highlight_background::<Type2>(
16414            &[
16415                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16416                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16417                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16418                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16419            ],
16420            |_| Hsla::green(),
16421            cx,
16422        );
16423
16424        let snapshot = editor.snapshot(window, cx);
16425        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16426            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16427            &snapshot,
16428            cx.theme(),
16429        );
16430        assert_eq!(
16431            highlighted_ranges,
16432            &[
16433                (
16434                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16435                    Hsla::green(),
16436                ),
16437                (
16438                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16439                    Hsla::red(),
16440                ),
16441                (
16442                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16443                    Hsla::green(),
16444                ),
16445                (
16446                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16447                    Hsla::red(),
16448                ),
16449            ]
16450        );
16451        assert_eq!(
16452            editor.sorted_background_highlights_in_range(
16453                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16454                &snapshot,
16455                cx.theme(),
16456            ),
16457            &[(
16458                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16459                Hsla::red(),
16460            )]
16461        );
16462    });
16463}
16464
16465#[gpui::test]
16466async fn test_following(cx: &mut TestAppContext) {
16467    init_test(cx, |_| {});
16468
16469    let fs = FakeFs::new(cx.executor());
16470    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16471
16472    let buffer = project.update(cx, |project, cx| {
16473        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16474        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16475    });
16476    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16477    let follower = cx.update(|cx| {
16478        cx.open_window(
16479            WindowOptions {
16480                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16481                    gpui::Point::new(px(0.), px(0.)),
16482                    gpui::Point::new(px(10.), px(80.)),
16483                ))),
16484                ..Default::default()
16485            },
16486            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16487        )
16488        .unwrap()
16489    });
16490
16491    let is_still_following = Rc::new(RefCell::new(true));
16492    let follower_edit_event_count = Rc::new(RefCell::new(0));
16493    let pending_update = Rc::new(RefCell::new(None));
16494    let leader_entity = leader.root(cx).unwrap();
16495    let follower_entity = follower.root(cx).unwrap();
16496    _ = follower.update(cx, {
16497        let update = pending_update.clone();
16498        let is_still_following = is_still_following.clone();
16499        let follower_edit_event_count = follower_edit_event_count.clone();
16500        |_, window, cx| {
16501            cx.subscribe_in(
16502                &leader_entity,
16503                window,
16504                move |_, leader, event, window, cx| {
16505                    leader.read(cx).add_event_to_update_proto(
16506                        event,
16507                        &mut update.borrow_mut(),
16508                        window,
16509                        cx,
16510                    );
16511                },
16512            )
16513            .detach();
16514
16515            cx.subscribe_in(
16516                &follower_entity,
16517                window,
16518                move |_, _, event: &EditorEvent, _window, _cx| {
16519                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16520                        *is_still_following.borrow_mut() = false;
16521                    }
16522
16523                    if let EditorEvent::BufferEdited = event {
16524                        *follower_edit_event_count.borrow_mut() += 1;
16525                    }
16526                },
16527            )
16528            .detach();
16529        }
16530    });
16531
16532    // Update the selections only
16533    _ = leader.update(cx, |leader, window, cx| {
16534        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16535            s.select_ranges([1..1])
16536        });
16537    });
16538    follower
16539        .update(cx, |follower, window, cx| {
16540            follower.apply_update_proto(
16541                &project,
16542                pending_update.borrow_mut().take().unwrap(),
16543                window,
16544                cx,
16545            )
16546        })
16547        .unwrap()
16548        .await
16549        .unwrap();
16550    _ = follower.update(cx, |follower, _, cx| {
16551        assert_eq!(
16552            follower.selections.ranges(&follower.display_snapshot(cx)),
16553            vec![1..1]
16554        );
16555    });
16556    assert!(*is_still_following.borrow());
16557    assert_eq!(*follower_edit_event_count.borrow(), 0);
16558
16559    // Update the scroll position only
16560    _ = leader.update(cx, |leader, window, cx| {
16561        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16562    });
16563    follower
16564        .update(cx, |follower, window, cx| {
16565            follower.apply_update_proto(
16566                &project,
16567                pending_update.borrow_mut().take().unwrap(),
16568                window,
16569                cx,
16570            )
16571        })
16572        .unwrap()
16573        .await
16574        .unwrap();
16575    assert_eq!(
16576        follower
16577            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16578            .unwrap(),
16579        gpui::Point::new(1.5, 3.5)
16580    );
16581    assert!(*is_still_following.borrow());
16582    assert_eq!(*follower_edit_event_count.borrow(), 0);
16583
16584    // Update the selections and scroll position. The follower's scroll position is updated
16585    // via autoscroll, not via the leader's exact scroll position.
16586    _ = leader.update(cx, |leader, window, cx| {
16587        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16588            s.select_ranges([0..0])
16589        });
16590        leader.request_autoscroll(Autoscroll::newest(), cx);
16591        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16592    });
16593    follower
16594        .update(cx, |follower, window, cx| {
16595            follower.apply_update_proto(
16596                &project,
16597                pending_update.borrow_mut().take().unwrap(),
16598                window,
16599                cx,
16600            )
16601        })
16602        .unwrap()
16603        .await
16604        .unwrap();
16605    _ = follower.update(cx, |follower, _, cx| {
16606        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16607        assert_eq!(
16608            follower.selections.ranges(&follower.display_snapshot(cx)),
16609            vec![0..0]
16610        );
16611    });
16612    assert!(*is_still_following.borrow());
16613
16614    // Creating a pending selection that precedes another selection
16615    _ = leader.update(cx, |leader, window, cx| {
16616        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16617            s.select_ranges([1..1])
16618        });
16619        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16620    });
16621    follower
16622        .update(cx, |follower, window, cx| {
16623            follower.apply_update_proto(
16624                &project,
16625                pending_update.borrow_mut().take().unwrap(),
16626                window,
16627                cx,
16628            )
16629        })
16630        .unwrap()
16631        .await
16632        .unwrap();
16633    _ = follower.update(cx, |follower, _, cx| {
16634        assert_eq!(
16635            follower.selections.ranges(&follower.display_snapshot(cx)),
16636            vec![0..0, 1..1]
16637        );
16638    });
16639    assert!(*is_still_following.borrow());
16640
16641    // Extend the pending selection so that it surrounds another selection
16642    _ = leader.update(cx, |leader, window, cx| {
16643        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16644    });
16645    follower
16646        .update(cx, |follower, window, cx| {
16647            follower.apply_update_proto(
16648                &project,
16649                pending_update.borrow_mut().take().unwrap(),
16650                window,
16651                cx,
16652            )
16653        })
16654        .unwrap()
16655        .await
16656        .unwrap();
16657    _ = follower.update(cx, |follower, _, cx| {
16658        assert_eq!(
16659            follower.selections.ranges(&follower.display_snapshot(cx)),
16660            vec![0..2]
16661        );
16662    });
16663
16664    // Scrolling locally breaks the follow
16665    _ = follower.update(cx, |follower, window, cx| {
16666        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16667        follower.set_scroll_anchor(
16668            ScrollAnchor {
16669                anchor: top_anchor,
16670                offset: gpui::Point::new(0.0, 0.5),
16671            },
16672            window,
16673            cx,
16674        );
16675    });
16676    assert!(!(*is_still_following.borrow()));
16677}
16678
16679#[gpui::test]
16680async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16681    init_test(cx, |_| {});
16682
16683    let fs = FakeFs::new(cx.executor());
16684    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16685    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16686    let pane = workspace
16687        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16688        .unwrap();
16689
16690    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16691
16692    let leader = pane.update_in(cx, |_, window, cx| {
16693        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16694        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16695    });
16696
16697    // Start following the editor when it has no excerpts.
16698    let mut state_message =
16699        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16700    let workspace_entity = workspace.root(cx).unwrap();
16701    let follower_1 = cx
16702        .update_window(*workspace.deref(), |_, window, cx| {
16703            Editor::from_state_proto(
16704                workspace_entity,
16705                ViewId {
16706                    creator: CollaboratorId::PeerId(PeerId::default()),
16707                    id: 0,
16708                },
16709                &mut state_message,
16710                window,
16711                cx,
16712            )
16713        })
16714        .unwrap()
16715        .unwrap()
16716        .await
16717        .unwrap();
16718
16719    let update_message = Rc::new(RefCell::new(None));
16720    follower_1.update_in(cx, {
16721        let update = update_message.clone();
16722        |_, window, cx| {
16723            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16724                leader.read(cx).add_event_to_update_proto(
16725                    event,
16726                    &mut update.borrow_mut(),
16727                    window,
16728                    cx,
16729                );
16730            })
16731            .detach();
16732        }
16733    });
16734
16735    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16736        (
16737            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16738            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16739        )
16740    });
16741
16742    // Insert some excerpts.
16743    leader.update(cx, |leader, cx| {
16744        leader.buffer.update(cx, |multibuffer, cx| {
16745            multibuffer.set_excerpts_for_path(
16746                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16747                buffer_1.clone(),
16748                vec![
16749                    Point::row_range(0..3),
16750                    Point::row_range(1..6),
16751                    Point::row_range(12..15),
16752                ],
16753                0,
16754                cx,
16755            );
16756            multibuffer.set_excerpts_for_path(
16757                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16758                buffer_2.clone(),
16759                vec![Point::row_range(0..6), Point::row_range(8..12)],
16760                0,
16761                cx,
16762            );
16763        });
16764    });
16765
16766    // Apply the update of adding the excerpts.
16767    follower_1
16768        .update_in(cx, |follower, window, cx| {
16769            follower.apply_update_proto(
16770                &project,
16771                update_message.borrow().clone().unwrap(),
16772                window,
16773                cx,
16774            )
16775        })
16776        .await
16777        .unwrap();
16778    assert_eq!(
16779        follower_1.update(cx, |editor, cx| editor.text(cx)),
16780        leader.update(cx, |editor, cx| editor.text(cx))
16781    );
16782    update_message.borrow_mut().take();
16783
16784    // Start following separately after it already has excerpts.
16785    let mut state_message =
16786        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16787    let workspace_entity = workspace.root(cx).unwrap();
16788    let follower_2 = cx
16789        .update_window(*workspace.deref(), |_, window, cx| {
16790            Editor::from_state_proto(
16791                workspace_entity,
16792                ViewId {
16793                    creator: CollaboratorId::PeerId(PeerId::default()),
16794                    id: 0,
16795                },
16796                &mut state_message,
16797                window,
16798                cx,
16799            )
16800        })
16801        .unwrap()
16802        .unwrap()
16803        .await
16804        .unwrap();
16805    assert_eq!(
16806        follower_2.update(cx, |editor, cx| editor.text(cx)),
16807        leader.update(cx, |editor, cx| editor.text(cx))
16808    );
16809
16810    // Remove some excerpts.
16811    leader.update(cx, |leader, cx| {
16812        leader.buffer.update(cx, |multibuffer, cx| {
16813            let excerpt_ids = multibuffer.excerpt_ids();
16814            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16815            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16816        });
16817    });
16818
16819    // Apply the update of removing the excerpts.
16820    follower_1
16821        .update_in(cx, |follower, window, cx| {
16822            follower.apply_update_proto(
16823                &project,
16824                update_message.borrow().clone().unwrap(),
16825                window,
16826                cx,
16827            )
16828        })
16829        .await
16830        .unwrap();
16831    follower_2
16832        .update_in(cx, |follower, window, cx| {
16833            follower.apply_update_proto(
16834                &project,
16835                update_message.borrow().clone().unwrap(),
16836                window,
16837                cx,
16838            )
16839        })
16840        .await
16841        .unwrap();
16842    update_message.borrow_mut().take();
16843    assert_eq!(
16844        follower_1.update(cx, |editor, cx| editor.text(cx)),
16845        leader.update(cx, |editor, cx| editor.text(cx))
16846    );
16847}
16848
16849#[gpui::test]
16850async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16851    init_test(cx, |_| {});
16852
16853    let mut cx = EditorTestContext::new(cx).await;
16854    let lsp_store =
16855        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16856
16857    cx.set_state(indoc! {"
16858        ˇfn func(abc def: i32) -> u32 {
16859        }
16860    "});
16861
16862    cx.update(|_, cx| {
16863        lsp_store.update(cx, |lsp_store, cx| {
16864            lsp_store
16865                .update_diagnostics(
16866                    LanguageServerId(0),
16867                    lsp::PublishDiagnosticsParams {
16868                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16869                        version: None,
16870                        diagnostics: vec![
16871                            lsp::Diagnostic {
16872                                range: lsp::Range::new(
16873                                    lsp::Position::new(0, 11),
16874                                    lsp::Position::new(0, 12),
16875                                ),
16876                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16877                                ..Default::default()
16878                            },
16879                            lsp::Diagnostic {
16880                                range: lsp::Range::new(
16881                                    lsp::Position::new(0, 12),
16882                                    lsp::Position::new(0, 15),
16883                                ),
16884                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16885                                ..Default::default()
16886                            },
16887                            lsp::Diagnostic {
16888                                range: lsp::Range::new(
16889                                    lsp::Position::new(0, 25),
16890                                    lsp::Position::new(0, 28),
16891                                ),
16892                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16893                                ..Default::default()
16894                            },
16895                        ],
16896                    },
16897                    None,
16898                    DiagnosticSourceKind::Pushed,
16899                    &[],
16900                    cx,
16901                )
16902                .unwrap()
16903        });
16904    });
16905
16906    executor.run_until_parked();
16907
16908    cx.update_editor(|editor, window, cx| {
16909        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16910    });
16911
16912    cx.assert_editor_state(indoc! {"
16913        fn func(abc def: i32) -> ˇu32 {
16914        }
16915    "});
16916
16917    cx.update_editor(|editor, window, cx| {
16918        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16919    });
16920
16921    cx.assert_editor_state(indoc! {"
16922        fn func(abc ˇdef: i32) -> u32 {
16923        }
16924    "});
16925
16926    cx.update_editor(|editor, window, cx| {
16927        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16928    });
16929
16930    cx.assert_editor_state(indoc! {"
16931        fn func(abcˇ def: i32) -> u32 {
16932        }
16933    "});
16934
16935    cx.update_editor(|editor, window, cx| {
16936        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16937    });
16938
16939    cx.assert_editor_state(indoc! {"
16940        fn func(abc def: i32) -> ˇu32 {
16941        }
16942    "});
16943}
16944
16945#[gpui::test]
16946async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16947    init_test(cx, |_| {});
16948
16949    let mut cx = EditorTestContext::new(cx).await;
16950
16951    let diff_base = r#"
16952        use some::mod;
16953
16954        const A: u32 = 42;
16955
16956        fn main() {
16957            println!("hello");
16958
16959            println!("world");
16960        }
16961        "#
16962    .unindent();
16963
16964    // Edits are modified, removed, modified, added
16965    cx.set_state(
16966        &r#"
16967        use some::modified;
16968
16969        ˇ
16970        fn main() {
16971            println!("hello there");
16972
16973            println!("around the");
16974            println!("world");
16975        }
16976        "#
16977        .unindent(),
16978    );
16979
16980    cx.set_head_text(&diff_base);
16981    executor.run_until_parked();
16982
16983    cx.update_editor(|editor, window, cx| {
16984        //Wrap around the bottom of the buffer
16985        for _ in 0..3 {
16986            editor.go_to_next_hunk(&GoToHunk, window, cx);
16987        }
16988    });
16989
16990    cx.assert_editor_state(
16991        &r#"
16992        ˇuse some::modified;
16993
16994
16995        fn main() {
16996            println!("hello there");
16997
16998            println!("around the");
16999            println!("world");
17000        }
17001        "#
17002        .unindent(),
17003    );
17004
17005    cx.update_editor(|editor, window, cx| {
17006        //Wrap around the top of the buffer
17007        for _ in 0..2 {
17008            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17009        }
17010    });
17011
17012    cx.assert_editor_state(
17013        &r#"
17014        use some::modified;
17015
17016
17017        fn main() {
17018        ˇ    println!("hello there");
17019
17020            println!("around the");
17021            println!("world");
17022        }
17023        "#
17024        .unindent(),
17025    );
17026
17027    cx.update_editor(|editor, window, cx| {
17028        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17029    });
17030
17031    cx.assert_editor_state(
17032        &r#"
17033        use some::modified;
17034
17035        ˇ
17036        fn main() {
17037            println!("hello there");
17038
17039            println!("around the");
17040            println!("world");
17041        }
17042        "#
17043        .unindent(),
17044    );
17045
17046    cx.update_editor(|editor, window, cx| {
17047        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17048    });
17049
17050    cx.assert_editor_state(
17051        &r#"
17052        ˇuse some::modified;
17053
17054
17055        fn main() {
17056            println!("hello there");
17057
17058            println!("around the");
17059            println!("world");
17060        }
17061        "#
17062        .unindent(),
17063    );
17064
17065    cx.update_editor(|editor, window, cx| {
17066        for _ in 0..2 {
17067            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17068        }
17069    });
17070
17071    cx.assert_editor_state(
17072        &r#"
17073        use some::modified;
17074
17075
17076        fn main() {
17077        ˇ    println!("hello there");
17078
17079            println!("around the");
17080            println!("world");
17081        }
17082        "#
17083        .unindent(),
17084    );
17085
17086    cx.update_editor(|editor, window, cx| {
17087        editor.fold(&Fold, window, cx);
17088    });
17089
17090    cx.update_editor(|editor, window, cx| {
17091        editor.go_to_next_hunk(&GoToHunk, window, cx);
17092    });
17093
17094    cx.assert_editor_state(
17095        &r#"
17096        ˇuse some::modified;
17097
17098
17099        fn main() {
17100            println!("hello there");
17101
17102            println!("around the");
17103            println!("world");
17104        }
17105        "#
17106        .unindent(),
17107    );
17108}
17109
17110#[test]
17111fn test_split_words() {
17112    fn split(text: &str) -> Vec<&str> {
17113        split_words(text).collect()
17114    }
17115
17116    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17117    assert_eq!(split("hello_world"), &["hello_", "world"]);
17118    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17119    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17120    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17121    assert_eq!(split("helloworld"), &["helloworld"]);
17122
17123    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17124}
17125
17126#[gpui::test]
17127async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17128    init_test(cx, |_| {});
17129
17130    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17131    let mut assert = |before, after| {
17132        let _state_context = cx.set_state(before);
17133        cx.run_until_parked();
17134        cx.update_editor(|editor, window, cx| {
17135            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17136        });
17137        cx.run_until_parked();
17138        cx.assert_editor_state(after);
17139    };
17140
17141    // Outside bracket jumps to outside of matching bracket
17142    assert("console.logˇ(var);", "console.log(var)ˇ;");
17143    assert("console.log(var)ˇ;", "console.logˇ(var);");
17144
17145    // Inside bracket jumps to inside of matching bracket
17146    assert("console.log(ˇvar);", "console.log(varˇ);");
17147    assert("console.log(varˇ);", "console.log(ˇvar);");
17148
17149    // When outside a bracket and inside, favor jumping to the inside bracket
17150    assert(
17151        "console.log('foo', [1, 2, 3]ˇ);",
17152        "console.log(ˇ'foo', [1, 2, 3]);",
17153    );
17154    assert(
17155        "console.log(ˇ'foo', [1, 2, 3]);",
17156        "console.log('foo', [1, 2, 3]ˇ);",
17157    );
17158
17159    // Bias forward if two options are equally likely
17160    assert(
17161        "let result = curried_fun()ˇ();",
17162        "let result = curried_fun()()ˇ;",
17163    );
17164
17165    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17166    assert(
17167        indoc! {"
17168            function test() {
17169                console.log('test')ˇ
17170            }"},
17171        indoc! {"
17172            function test() {
17173                console.logˇ('test')
17174            }"},
17175    );
17176}
17177
17178#[gpui::test]
17179async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17180    init_test(cx, |_| {});
17181
17182    let fs = FakeFs::new(cx.executor());
17183    fs.insert_tree(
17184        path!("/a"),
17185        json!({
17186            "main.rs": "fn main() { let a = 5; }",
17187            "other.rs": "// Test file",
17188        }),
17189    )
17190    .await;
17191    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17192
17193    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17194    language_registry.add(Arc::new(Language::new(
17195        LanguageConfig {
17196            name: "Rust".into(),
17197            matcher: LanguageMatcher {
17198                path_suffixes: vec!["rs".to_string()],
17199                ..Default::default()
17200            },
17201            brackets: BracketPairConfig {
17202                pairs: vec![BracketPair {
17203                    start: "{".to_string(),
17204                    end: "}".to_string(),
17205                    close: true,
17206                    surround: true,
17207                    newline: true,
17208                }],
17209                disabled_scopes_by_bracket_ix: Vec::new(),
17210            },
17211            ..Default::default()
17212        },
17213        Some(tree_sitter_rust::LANGUAGE.into()),
17214    )));
17215    let mut fake_servers = language_registry.register_fake_lsp(
17216        "Rust",
17217        FakeLspAdapter {
17218            capabilities: lsp::ServerCapabilities {
17219                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17220                    first_trigger_character: "{".to_string(),
17221                    more_trigger_character: None,
17222                }),
17223                ..Default::default()
17224            },
17225            ..Default::default()
17226        },
17227    );
17228
17229    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17230
17231    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17232
17233    let worktree_id = workspace
17234        .update(cx, |workspace, _, cx| {
17235            workspace.project().update(cx, |project, cx| {
17236                project.worktrees(cx).next().unwrap().read(cx).id()
17237            })
17238        })
17239        .unwrap();
17240
17241    let buffer = project
17242        .update(cx, |project, cx| {
17243            project.open_local_buffer(path!("/a/main.rs"), cx)
17244        })
17245        .await
17246        .unwrap();
17247    let editor_handle = workspace
17248        .update(cx, |workspace, window, cx| {
17249            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17250        })
17251        .unwrap()
17252        .await
17253        .unwrap()
17254        .downcast::<Editor>()
17255        .unwrap();
17256
17257    cx.executor().start_waiting();
17258    let fake_server = fake_servers.next().await.unwrap();
17259
17260    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17261        |params, _| async move {
17262            assert_eq!(
17263                params.text_document_position.text_document.uri,
17264                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17265            );
17266            assert_eq!(
17267                params.text_document_position.position,
17268                lsp::Position::new(0, 21),
17269            );
17270
17271            Ok(Some(vec![lsp::TextEdit {
17272                new_text: "]".to_string(),
17273                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17274            }]))
17275        },
17276    );
17277
17278    editor_handle.update_in(cx, |editor, window, cx| {
17279        window.focus(&editor.focus_handle(cx));
17280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17281            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17282        });
17283        editor.handle_input("{", window, cx);
17284    });
17285
17286    cx.executor().run_until_parked();
17287
17288    buffer.update(cx, |buffer, _| {
17289        assert_eq!(
17290            buffer.text(),
17291            "fn main() { let a = {5}; }",
17292            "No extra braces from on type formatting should appear in the buffer"
17293        )
17294    });
17295}
17296
17297#[gpui::test(iterations = 20, seeds(31))]
17298async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17299    init_test(cx, |_| {});
17300
17301    let mut cx = EditorLspTestContext::new_rust(
17302        lsp::ServerCapabilities {
17303            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17304                first_trigger_character: ".".to_string(),
17305                more_trigger_character: None,
17306            }),
17307            ..Default::default()
17308        },
17309        cx,
17310    )
17311    .await;
17312
17313    cx.update_buffer(|buffer, _| {
17314        // This causes autoindent to be async.
17315        buffer.set_sync_parse_timeout(Duration::ZERO)
17316    });
17317
17318    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17319    cx.simulate_keystroke("\n");
17320    cx.run_until_parked();
17321
17322    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17323    let mut request =
17324        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17325            let buffer_cloned = buffer_cloned.clone();
17326            async move {
17327                buffer_cloned.update(&mut cx, |buffer, _| {
17328                    assert_eq!(
17329                        buffer.text(),
17330                        "fn c() {\n    d()\n        .\n}\n",
17331                        "OnTypeFormatting should triggered after autoindent applied"
17332                    )
17333                })?;
17334
17335                Ok(Some(vec![]))
17336            }
17337        });
17338
17339    cx.simulate_keystroke(".");
17340    cx.run_until_parked();
17341
17342    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17343    assert!(request.next().await.is_some());
17344    request.close();
17345    assert!(request.next().await.is_none());
17346}
17347
17348#[gpui::test]
17349async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17350    init_test(cx, |_| {});
17351
17352    let fs = FakeFs::new(cx.executor());
17353    fs.insert_tree(
17354        path!("/a"),
17355        json!({
17356            "main.rs": "fn main() { let a = 5; }",
17357            "other.rs": "// Test file",
17358        }),
17359    )
17360    .await;
17361
17362    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17363
17364    let server_restarts = Arc::new(AtomicUsize::new(0));
17365    let closure_restarts = Arc::clone(&server_restarts);
17366    let language_server_name = "test language server";
17367    let language_name: LanguageName = "Rust".into();
17368
17369    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17370    language_registry.add(Arc::new(Language::new(
17371        LanguageConfig {
17372            name: language_name.clone(),
17373            matcher: LanguageMatcher {
17374                path_suffixes: vec!["rs".to_string()],
17375                ..Default::default()
17376            },
17377            ..Default::default()
17378        },
17379        Some(tree_sitter_rust::LANGUAGE.into()),
17380    )));
17381    let mut fake_servers = language_registry.register_fake_lsp(
17382        "Rust",
17383        FakeLspAdapter {
17384            name: language_server_name,
17385            initialization_options: Some(json!({
17386                "testOptionValue": true
17387            })),
17388            initializer: Some(Box::new(move |fake_server| {
17389                let task_restarts = Arc::clone(&closure_restarts);
17390                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17391                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17392                    futures::future::ready(Ok(()))
17393                });
17394            })),
17395            ..Default::default()
17396        },
17397    );
17398
17399    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17400    let _buffer = project
17401        .update(cx, |project, cx| {
17402            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17403        })
17404        .await
17405        .unwrap();
17406    let _fake_server = fake_servers.next().await.unwrap();
17407    update_test_language_settings(cx, |language_settings| {
17408        language_settings.languages.0.insert(
17409            language_name.clone().0,
17410            LanguageSettingsContent {
17411                tab_size: NonZeroU32::new(8),
17412                ..Default::default()
17413            },
17414        );
17415    });
17416    cx.executor().run_until_parked();
17417    assert_eq!(
17418        server_restarts.load(atomic::Ordering::Acquire),
17419        0,
17420        "Should not restart LSP server on an unrelated change"
17421    );
17422
17423    update_test_project_settings(cx, |project_settings| {
17424        project_settings.lsp.insert(
17425            "Some other server name".into(),
17426            LspSettings {
17427                binary: None,
17428                settings: None,
17429                initialization_options: Some(json!({
17430                    "some other init value": false
17431                })),
17432                enable_lsp_tasks: false,
17433                fetch: None,
17434            },
17435        );
17436    });
17437    cx.executor().run_until_parked();
17438    assert_eq!(
17439        server_restarts.load(atomic::Ordering::Acquire),
17440        0,
17441        "Should not restart LSP server on an unrelated LSP settings change"
17442    );
17443
17444    update_test_project_settings(cx, |project_settings| {
17445        project_settings.lsp.insert(
17446            language_server_name.into(),
17447            LspSettings {
17448                binary: None,
17449                settings: None,
17450                initialization_options: Some(json!({
17451                    "anotherInitValue": false
17452                })),
17453                enable_lsp_tasks: false,
17454                fetch: None,
17455            },
17456        );
17457    });
17458    cx.executor().run_until_parked();
17459    assert_eq!(
17460        server_restarts.load(atomic::Ordering::Acquire),
17461        1,
17462        "Should restart LSP server on a related LSP settings change"
17463    );
17464
17465    update_test_project_settings(cx, |project_settings| {
17466        project_settings.lsp.insert(
17467            language_server_name.into(),
17468            LspSettings {
17469                binary: None,
17470                settings: None,
17471                initialization_options: Some(json!({
17472                    "anotherInitValue": false
17473                })),
17474                enable_lsp_tasks: false,
17475                fetch: None,
17476            },
17477        );
17478    });
17479    cx.executor().run_until_parked();
17480    assert_eq!(
17481        server_restarts.load(atomic::Ordering::Acquire),
17482        1,
17483        "Should not restart LSP server on a related LSP settings change that is the same"
17484    );
17485
17486    update_test_project_settings(cx, |project_settings| {
17487        project_settings.lsp.insert(
17488            language_server_name.into(),
17489            LspSettings {
17490                binary: None,
17491                settings: None,
17492                initialization_options: None,
17493                enable_lsp_tasks: false,
17494                fetch: None,
17495            },
17496        );
17497    });
17498    cx.executor().run_until_parked();
17499    assert_eq!(
17500        server_restarts.load(atomic::Ordering::Acquire),
17501        2,
17502        "Should restart LSP server on another related LSP settings change"
17503    );
17504}
17505
17506#[gpui::test]
17507async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17508    init_test(cx, |_| {});
17509
17510    let mut cx = EditorLspTestContext::new_rust(
17511        lsp::ServerCapabilities {
17512            completion_provider: Some(lsp::CompletionOptions {
17513                trigger_characters: Some(vec![".".to_string()]),
17514                resolve_provider: Some(true),
17515                ..Default::default()
17516            }),
17517            ..Default::default()
17518        },
17519        cx,
17520    )
17521    .await;
17522
17523    cx.set_state("fn main() { let a = 2ˇ; }");
17524    cx.simulate_keystroke(".");
17525    let completion_item = lsp::CompletionItem {
17526        label: "some".into(),
17527        kind: Some(lsp::CompletionItemKind::SNIPPET),
17528        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17529        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17530            kind: lsp::MarkupKind::Markdown,
17531            value: "```rust\nSome(2)\n```".to_string(),
17532        })),
17533        deprecated: Some(false),
17534        sort_text: Some("fffffff2".to_string()),
17535        filter_text: Some("some".to_string()),
17536        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17537        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17538            range: lsp::Range {
17539                start: lsp::Position {
17540                    line: 0,
17541                    character: 22,
17542                },
17543                end: lsp::Position {
17544                    line: 0,
17545                    character: 22,
17546                },
17547            },
17548            new_text: "Some(2)".to_string(),
17549        })),
17550        additional_text_edits: Some(vec![lsp::TextEdit {
17551            range: lsp::Range {
17552                start: lsp::Position {
17553                    line: 0,
17554                    character: 20,
17555                },
17556                end: lsp::Position {
17557                    line: 0,
17558                    character: 22,
17559                },
17560            },
17561            new_text: "".to_string(),
17562        }]),
17563        ..Default::default()
17564    };
17565
17566    let closure_completion_item = completion_item.clone();
17567    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17568        let task_completion_item = closure_completion_item.clone();
17569        async move {
17570            Ok(Some(lsp::CompletionResponse::Array(vec![
17571                task_completion_item,
17572            ])))
17573        }
17574    });
17575
17576    request.next().await;
17577
17578    cx.condition(|editor, _| editor.context_menu_visible())
17579        .await;
17580    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17581        editor
17582            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17583            .unwrap()
17584    });
17585    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17586
17587    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17588        let task_completion_item = completion_item.clone();
17589        async move { Ok(task_completion_item) }
17590    })
17591    .next()
17592    .await
17593    .unwrap();
17594    apply_additional_edits.await.unwrap();
17595    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17596}
17597
17598#[gpui::test]
17599async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17600    init_test(cx, |_| {});
17601
17602    let mut cx = EditorLspTestContext::new_rust(
17603        lsp::ServerCapabilities {
17604            completion_provider: Some(lsp::CompletionOptions {
17605                trigger_characters: Some(vec![".".to_string()]),
17606                resolve_provider: Some(true),
17607                ..Default::default()
17608            }),
17609            ..Default::default()
17610        },
17611        cx,
17612    )
17613    .await;
17614
17615    cx.set_state("fn main() { let a = 2ˇ; }");
17616    cx.simulate_keystroke(".");
17617
17618    let item1 = lsp::CompletionItem {
17619        label: "method id()".to_string(),
17620        filter_text: Some("id".to_string()),
17621        detail: None,
17622        documentation: None,
17623        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17624            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17625            new_text: ".id".to_string(),
17626        })),
17627        ..lsp::CompletionItem::default()
17628    };
17629
17630    let item2 = lsp::CompletionItem {
17631        label: "other".to_string(),
17632        filter_text: Some("other".to_string()),
17633        detail: None,
17634        documentation: None,
17635        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17636            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17637            new_text: ".other".to_string(),
17638        })),
17639        ..lsp::CompletionItem::default()
17640    };
17641
17642    let item1 = item1.clone();
17643    cx.set_request_handler::<lsp::request::Completion, _, _>({
17644        let item1 = item1.clone();
17645        move |_, _, _| {
17646            let item1 = item1.clone();
17647            let item2 = item2.clone();
17648            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17649        }
17650    })
17651    .next()
17652    .await;
17653
17654    cx.condition(|editor, _| editor.context_menu_visible())
17655        .await;
17656    cx.update_editor(|editor, _, _| {
17657        let context_menu = editor.context_menu.borrow_mut();
17658        let context_menu = context_menu
17659            .as_ref()
17660            .expect("Should have the context menu deployed");
17661        match context_menu {
17662            CodeContextMenu::Completions(completions_menu) => {
17663                let completions = completions_menu.completions.borrow_mut();
17664                assert_eq!(
17665                    completions
17666                        .iter()
17667                        .map(|completion| &completion.label.text)
17668                        .collect::<Vec<_>>(),
17669                    vec!["method id()", "other"]
17670                )
17671            }
17672            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17673        }
17674    });
17675
17676    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17677        let item1 = item1.clone();
17678        move |_, item_to_resolve, _| {
17679            let item1 = item1.clone();
17680            async move {
17681                if item1 == item_to_resolve {
17682                    Ok(lsp::CompletionItem {
17683                        label: "method id()".to_string(),
17684                        filter_text: Some("id".to_string()),
17685                        detail: Some("Now resolved!".to_string()),
17686                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17687                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17688                            range: lsp::Range::new(
17689                                lsp::Position::new(0, 22),
17690                                lsp::Position::new(0, 22),
17691                            ),
17692                            new_text: ".id".to_string(),
17693                        })),
17694                        ..lsp::CompletionItem::default()
17695                    })
17696                } else {
17697                    Ok(item_to_resolve)
17698                }
17699            }
17700        }
17701    })
17702    .next()
17703    .await
17704    .unwrap();
17705    cx.run_until_parked();
17706
17707    cx.update_editor(|editor, window, cx| {
17708        editor.context_menu_next(&Default::default(), window, cx);
17709    });
17710
17711    cx.update_editor(|editor, _, _| {
17712        let context_menu = editor.context_menu.borrow_mut();
17713        let context_menu = context_menu
17714            .as_ref()
17715            .expect("Should have the context menu deployed");
17716        match context_menu {
17717            CodeContextMenu::Completions(completions_menu) => {
17718                let completions = completions_menu.completions.borrow_mut();
17719                assert_eq!(
17720                    completions
17721                        .iter()
17722                        .map(|completion| &completion.label.text)
17723                        .collect::<Vec<_>>(),
17724                    vec!["method id() Now resolved!", "other"],
17725                    "Should update first completion label, but not second as the filter text did not match."
17726                );
17727            }
17728            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17729        }
17730    });
17731}
17732
17733#[gpui::test]
17734async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17735    init_test(cx, |_| {});
17736    let mut cx = EditorLspTestContext::new_rust(
17737        lsp::ServerCapabilities {
17738            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17739            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17740            completion_provider: Some(lsp::CompletionOptions {
17741                resolve_provider: Some(true),
17742                ..Default::default()
17743            }),
17744            ..Default::default()
17745        },
17746        cx,
17747    )
17748    .await;
17749    cx.set_state(indoc! {"
17750        struct TestStruct {
17751            field: i32
17752        }
17753
17754        fn mainˇ() {
17755            let unused_var = 42;
17756            let test_struct = TestStruct { field: 42 };
17757        }
17758    "});
17759    let symbol_range = cx.lsp_range(indoc! {"
17760        struct TestStruct {
17761            field: i32
17762        }
17763
17764        «fn main»() {
17765            let unused_var = 42;
17766            let test_struct = TestStruct { field: 42 };
17767        }
17768    "});
17769    let mut hover_requests =
17770        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17771            Ok(Some(lsp::Hover {
17772                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17773                    kind: lsp::MarkupKind::Markdown,
17774                    value: "Function documentation".to_string(),
17775                }),
17776                range: Some(symbol_range),
17777            }))
17778        });
17779
17780    // Case 1: Test that code action menu hide hover popover
17781    cx.dispatch_action(Hover);
17782    hover_requests.next().await;
17783    cx.condition(|editor, _| editor.hover_state.visible()).await;
17784    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17785        move |_, _, _| async move {
17786            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17787                lsp::CodeAction {
17788                    title: "Remove unused variable".to_string(),
17789                    kind: Some(CodeActionKind::QUICKFIX),
17790                    edit: Some(lsp::WorkspaceEdit {
17791                        changes: Some(
17792                            [(
17793                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17794                                vec![lsp::TextEdit {
17795                                    range: lsp::Range::new(
17796                                        lsp::Position::new(5, 4),
17797                                        lsp::Position::new(5, 27),
17798                                    ),
17799                                    new_text: "".to_string(),
17800                                }],
17801                            )]
17802                            .into_iter()
17803                            .collect(),
17804                        ),
17805                        ..Default::default()
17806                    }),
17807                    ..Default::default()
17808                },
17809            )]))
17810        },
17811    );
17812    cx.update_editor(|editor, window, cx| {
17813        editor.toggle_code_actions(
17814            &ToggleCodeActions {
17815                deployed_from: None,
17816                quick_launch: false,
17817            },
17818            window,
17819            cx,
17820        );
17821    });
17822    code_action_requests.next().await;
17823    cx.run_until_parked();
17824    cx.condition(|editor, _| editor.context_menu_visible())
17825        .await;
17826    cx.update_editor(|editor, _, _| {
17827        assert!(
17828            !editor.hover_state.visible(),
17829            "Hover popover should be hidden when code action menu is shown"
17830        );
17831        // Hide code actions
17832        editor.context_menu.take();
17833    });
17834
17835    // Case 2: Test that code completions hide hover popover
17836    cx.dispatch_action(Hover);
17837    hover_requests.next().await;
17838    cx.condition(|editor, _| editor.hover_state.visible()).await;
17839    let counter = Arc::new(AtomicUsize::new(0));
17840    let mut completion_requests =
17841        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17842            let counter = counter.clone();
17843            async move {
17844                counter.fetch_add(1, atomic::Ordering::Release);
17845                Ok(Some(lsp::CompletionResponse::Array(vec![
17846                    lsp::CompletionItem {
17847                        label: "main".into(),
17848                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17849                        detail: Some("() -> ()".to_string()),
17850                        ..Default::default()
17851                    },
17852                    lsp::CompletionItem {
17853                        label: "TestStruct".into(),
17854                        kind: Some(lsp::CompletionItemKind::STRUCT),
17855                        detail: Some("struct TestStruct".to_string()),
17856                        ..Default::default()
17857                    },
17858                ])))
17859            }
17860        });
17861    cx.update_editor(|editor, window, cx| {
17862        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17863    });
17864    completion_requests.next().await;
17865    cx.condition(|editor, _| editor.context_menu_visible())
17866        .await;
17867    cx.update_editor(|editor, _, _| {
17868        assert!(
17869            !editor.hover_state.visible(),
17870            "Hover popover should be hidden when completion menu is shown"
17871        );
17872    });
17873}
17874
17875#[gpui::test]
17876async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17877    init_test(cx, |_| {});
17878
17879    let mut cx = EditorLspTestContext::new_rust(
17880        lsp::ServerCapabilities {
17881            completion_provider: Some(lsp::CompletionOptions {
17882                trigger_characters: Some(vec![".".to_string()]),
17883                resolve_provider: Some(true),
17884                ..Default::default()
17885            }),
17886            ..Default::default()
17887        },
17888        cx,
17889    )
17890    .await;
17891
17892    cx.set_state("fn main() { let a = 2ˇ; }");
17893    cx.simulate_keystroke(".");
17894
17895    let unresolved_item_1 = lsp::CompletionItem {
17896        label: "id".to_string(),
17897        filter_text: Some("id".to_string()),
17898        detail: None,
17899        documentation: None,
17900        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17901            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17902            new_text: ".id".to_string(),
17903        })),
17904        ..lsp::CompletionItem::default()
17905    };
17906    let resolved_item_1 = lsp::CompletionItem {
17907        additional_text_edits: Some(vec![lsp::TextEdit {
17908            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17909            new_text: "!!".to_string(),
17910        }]),
17911        ..unresolved_item_1.clone()
17912    };
17913    let unresolved_item_2 = lsp::CompletionItem {
17914        label: "other".to_string(),
17915        filter_text: Some("other".to_string()),
17916        detail: None,
17917        documentation: None,
17918        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17919            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17920            new_text: ".other".to_string(),
17921        })),
17922        ..lsp::CompletionItem::default()
17923    };
17924    let resolved_item_2 = lsp::CompletionItem {
17925        additional_text_edits: Some(vec![lsp::TextEdit {
17926            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17927            new_text: "??".to_string(),
17928        }]),
17929        ..unresolved_item_2.clone()
17930    };
17931
17932    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17933    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17934    cx.lsp
17935        .server
17936        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17937            let unresolved_item_1 = unresolved_item_1.clone();
17938            let resolved_item_1 = resolved_item_1.clone();
17939            let unresolved_item_2 = unresolved_item_2.clone();
17940            let resolved_item_2 = resolved_item_2.clone();
17941            let resolve_requests_1 = resolve_requests_1.clone();
17942            let resolve_requests_2 = resolve_requests_2.clone();
17943            move |unresolved_request, _| {
17944                let unresolved_item_1 = unresolved_item_1.clone();
17945                let resolved_item_1 = resolved_item_1.clone();
17946                let unresolved_item_2 = unresolved_item_2.clone();
17947                let resolved_item_2 = resolved_item_2.clone();
17948                let resolve_requests_1 = resolve_requests_1.clone();
17949                let resolve_requests_2 = resolve_requests_2.clone();
17950                async move {
17951                    if unresolved_request == unresolved_item_1 {
17952                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17953                        Ok(resolved_item_1.clone())
17954                    } else if unresolved_request == unresolved_item_2 {
17955                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17956                        Ok(resolved_item_2.clone())
17957                    } else {
17958                        panic!("Unexpected completion item {unresolved_request:?}")
17959                    }
17960                }
17961            }
17962        })
17963        .detach();
17964
17965    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17966        let unresolved_item_1 = unresolved_item_1.clone();
17967        let unresolved_item_2 = unresolved_item_2.clone();
17968        async move {
17969            Ok(Some(lsp::CompletionResponse::Array(vec![
17970                unresolved_item_1,
17971                unresolved_item_2,
17972            ])))
17973        }
17974    })
17975    .next()
17976    .await;
17977
17978    cx.condition(|editor, _| editor.context_menu_visible())
17979        .await;
17980    cx.update_editor(|editor, _, _| {
17981        let context_menu = editor.context_menu.borrow_mut();
17982        let context_menu = context_menu
17983            .as_ref()
17984            .expect("Should have the context menu deployed");
17985        match context_menu {
17986            CodeContextMenu::Completions(completions_menu) => {
17987                let completions = completions_menu.completions.borrow_mut();
17988                assert_eq!(
17989                    completions
17990                        .iter()
17991                        .map(|completion| &completion.label.text)
17992                        .collect::<Vec<_>>(),
17993                    vec!["id", "other"]
17994                )
17995            }
17996            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17997        }
17998    });
17999    cx.run_until_parked();
18000
18001    cx.update_editor(|editor, window, cx| {
18002        editor.context_menu_next(&ContextMenuNext, window, cx);
18003    });
18004    cx.run_until_parked();
18005    cx.update_editor(|editor, window, cx| {
18006        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18007    });
18008    cx.run_until_parked();
18009    cx.update_editor(|editor, window, cx| {
18010        editor.context_menu_next(&ContextMenuNext, window, cx);
18011    });
18012    cx.run_until_parked();
18013    cx.update_editor(|editor, window, cx| {
18014        editor
18015            .compose_completion(&ComposeCompletion::default(), window, cx)
18016            .expect("No task returned")
18017    })
18018    .await
18019    .expect("Completion failed");
18020    cx.run_until_parked();
18021
18022    cx.update_editor(|editor, _, cx| {
18023        assert_eq!(
18024            resolve_requests_1.load(atomic::Ordering::Acquire),
18025            1,
18026            "Should always resolve once despite multiple selections"
18027        );
18028        assert_eq!(
18029            resolve_requests_2.load(atomic::Ordering::Acquire),
18030            1,
18031            "Should always resolve once after multiple selections and applying the completion"
18032        );
18033        assert_eq!(
18034            editor.text(cx),
18035            "fn main() { let a = ??.other; }",
18036            "Should use resolved data when applying the completion"
18037        );
18038    });
18039}
18040
18041#[gpui::test]
18042async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18043    init_test(cx, |_| {});
18044
18045    let item_0 = lsp::CompletionItem {
18046        label: "abs".into(),
18047        insert_text: Some("abs".into()),
18048        data: Some(json!({ "very": "special"})),
18049        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18050        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18051            lsp::InsertReplaceEdit {
18052                new_text: "abs".to_string(),
18053                insert: lsp::Range::default(),
18054                replace: lsp::Range::default(),
18055            },
18056        )),
18057        ..lsp::CompletionItem::default()
18058    };
18059    let items = iter::once(item_0.clone())
18060        .chain((11..51).map(|i| lsp::CompletionItem {
18061            label: format!("item_{}", i),
18062            insert_text: Some(format!("item_{}", i)),
18063            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18064            ..lsp::CompletionItem::default()
18065        }))
18066        .collect::<Vec<_>>();
18067
18068    let default_commit_characters = vec!["?".to_string()];
18069    let default_data = json!({ "default": "data"});
18070    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18071    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18072    let default_edit_range = lsp::Range {
18073        start: lsp::Position {
18074            line: 0,
18075            character: 5,
18076        },
18077        end: lsp::Position {
18078            line: 0,
18079            character: 5,
18080        },
18081    };
18082
18083    let mut cx = EditorLspTestContext::new_rust(
18084        lsp::ServerCapabilities {
18085            completion_provider: Some(lsp::CompletionOptions {
18086                trigger_characters: Some(vec![".".to_string()]),
18087                resolve_provider: Some(true),
18088                ..Default::default()
18089            }),
18090            ..Default::default()
18091        },
18092        cx,
18093    )
18094    .await;
18095
18096    cx.set_state("fn main() { let a = 2ˇ; }");
18097    cx.simulate_keystroke(".");
18098
18099    let completion_data = default_data.clone();
18100    let completion_characters = default_commit_characters.clone();
18101    let completion_items = items.clone();
18102    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18103        let default_data = completion_data.clone();
18104        let default_commit_characters = completion_characters.clone();
18105        let items = completion_items.clone();
18106        async move {
18107            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18108                items,
18109                item_defaults: Some(lsp::CompletionListItemDefaults {
18110                    data: Some(default_data.clone()),
18111                    commit_characters: Some(default_commit_characters.clone()),
18112                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18113                        default_edit_range,
18114                    )),
18115                    insert_text_format: Some(default_insert_text_format),
18116                    insert_text_mode: Some(default_insert_text_mode),
18117                }),
18118                ..lsp::CompletionList::default()
18119            })))
18120        }
18121    })
18122    .next()
18123    .await;
18124
18125    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18126    cx.lsp
18127        .server
18128        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18129            let closure_resolved_items = resolved_items.clone();
18130            move |item_to_resolve, _| {
18131                let closure_resolved_items = closure_resolved_items.clone();
18132                async move {
18133                    closure_resolved_items.lock().push(item_to_resolve.clone());
18134                    Ok(item_to_resolve)
18135                }
18136            }
18137        })
18138        .detach();
18139
18140    cx.condition(|editor, _| editor.context_menu_visible())
18141        .await;
18142    cx.run_until_parked();
18143    cx.update_editor(|editor, _, _| {
18144        let menu = editor.context_menu.borrow_mut();
18145        match menu.as_ref().expect("should have the completions menu") {
18146            CodeContextMenu::Completions(completions_menu) => {
18147                assert_eq!(
18148                    completions_menu
18149                        .entries
18150                        .borrow()
18151                        .iter()
18152                        .map(|mat| mat.string.clone())
18153                        .collect::<Vec<String>>(),
18154                    items
18155                        .iter()
18156                        .map(|completion| completion.label.clone())
18157                        .collect::<Vec<String>>()
18158                );
18159            }
18160            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18161        }
18162    });
18163    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18164    // with 4 from the end.
18165    assert_eq!(
18166        *resolved_items.lock(),
18167        [&items[0..16], &items[items.len() - 4..items.len()]]
18168            .concat()
18169            .iter()
18170            .cloned()
18171            .map(|mut item| {
18172                if item.data.is_none() {
18173                    item.data = Some(default_data.clone());
18174                }
18175                item
18176            })
18177            .collect::<Vec<lsp::CompletionItem>>(),
18178        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18179    );
18180    resolved_items.lock().clear();
18181
18182    cx.update_editor(|editor, window, cx| {
18183        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18184    });
18185    cx.run_until_parked();
18186    // Completions that have already been resolved are skipped.
18187    assert_eq!(
18188        *resolved_items.lock(),
18189        items[items.len() - 17..items.len() - 4]
18190            .iter()
18191            .cloned()
18192            .map(|mut item| {
18193                if item.data.is_none() {
18194                    item.data = Some(default_data.clone());
18195                }
18196                item
18197            })
18198            .collect::<Vec<lsp::CompletionItem>>()
18199    );
18200    resolved_items.lock().clear();
18201}
18202
18203#[gpui::test]
18204async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18205    init_test(cx, |_| {});
18206
18207    let mut cx = EditorLspTestContext::new(
18208        Language::new(
18209            LanguageConfig {
18210                matcher: LanguageMatcher {
18211                    path_suffixes: vec!["jsx".into()],
18212                    ..Default::default()
18213                },
18214                overrides: [(
18215                    "element".into(),
18216                    LanguageConfigOverride {
18217                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18218                        ..Default::default()
18219                    },
18220                )]
18221                .into_iter()
18222                .collect(),
18223                ..Default::default()
18224            },
18225            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18226        )
18227        .with_override_query("(jsx_self_closing_element) @element")
18228        .unwrap(),
18229        lsp::ServerCapabilities {
18230            completion_provider: Some(lsp::CompletionOptions {
18231                trigger_characters: Some(vec![":".to_string()]),
18232                ..Default::default()
18233            }),
18234            ..Default::default()
18235        },
18236        cx,
18237    )
18238    .await;
18239
18240    cx.lsp
18241        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18242            Ok(Some(lsp::CompletionResponse::Array(vec![
18243                lsp::CompletionItem {
18244                    label: "bg-blue".into(),
18245                    ..Default::default()
18246                },
18247                lsp::CompletionItem {
18248                    label: "bg-red".into(),
18249                    ..Default::default()
18250                },
18251                lsp::CompletionItem {
18252                    label: "bg-yellow".into(),
18253                    ..Default::default()
18254                },
18255            ])))
18256        });
18257
18258    cx.set_state(r#"<p class="bgˇ" />"#);
18259
18260    // Trigger completion when typing a dash, because the dash is an extra
18261    // word character in the 'element' scope, which contains the cursor.
18262    cx.simulate_keystroke("-");
18263    cx.executor().run_until_parked();
18264    cx.update_editor(|editor, _, _| {
18265        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18266        {
18267            assert_eq!(
18268                completion_menu_entries(menu),
18269                &["bg-blue", "bg-red", "bg-yellow"]
18270            );
18271        } else {
18272            panic!("expected completion menu to be open");
18273        }
18274    });
18275
18276    cx.simulate_keystroke("l");
18277    cx.executor().run_until_parked();
18278    cx.update_editor(|editor, _, _| {
18279        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18280        {
18281            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18282        } else {
18283            panic!("expected completion menu to be open");
18284        }
18285    });
18286
18287    // When filtering completions, consider the character after the '-' to
18288    // be the start of a subword.
18289    cx.set_state(r#"<p class="yelˇ" />"#);
18290    cx.simulate_keystroke("l");
18291    cx.executor().run_until_parked();
18292    cx.update_editor(|editor, _, _| {
18293        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18294        {
18295            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18296        } else {
18297            panic!("expected completion menu to be open");
18298        }
18299    });
18300}
18301
18302fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18303    let entries = menu.entries.borrow();
18304    entries.iter().map(|mat| mat.string.clone()).collect()
18305}
18306
18307#[gpui::test]
18308async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18309    init_test(cx, |settings| {
18310        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18311    });
18312
18313    let fs = FakeFs::new(cx.executor());
18314    fs.insert_file(path!("/file.ts"), Default::default()).await;
18315
18316    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18317    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18318
18319    language_registry.add(Arc::new(Language::new(
18320        LanguageConfig {
18321            name: "TypeScript".into(),
18322            matcher: LanguageMatcher {
18323                path_suffixes: vec!["ts".to_string()],
18324                ..Default::default()
18325            },
18326            ..Default::default()
18327        },
18328        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18329    )));
18330    update_test_language_settings(cx, |settings| {
18331        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18332    });
18333
18334    let test_plugin = "test_plugin";
18335    let _ = language_registry.register_fake_lsp(
18336        "TypeScript",
18337        FakeLspAdapter {
18338            prettier_plugins: vec![test_plugin],
18339            ..Default::default()
18340        },
18341    );
18342
18343    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18344    let buffer = project
18345        .update(cx, |project, cx| {
18346            project.open_local_buffer(path!("/file.ts"), cx)
18347        })
18348        .await
18349        .unwrap();
18350
18351    let buffer_text = "one\ntwo\nthree\n";
18352    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18353    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18354    editor.update_in(cx, |editor, window, cx| {
18355        editor.set_text(buffer_text, window, cx)
18356    });
18357
18358    editor
18359        .update_in(cx, |editor, window, cx| {
18360            editor.perform_format(
18361                project.clone(),
18362                FormatTrigger::Manual,
18363                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18364                window,
18365                cx,
18366            )
18367        })
18368        .unwrap()
18369        .await;
18370    assert_eq!(
18371        editor.update(cx, |editor, cx| editor.text(cx)),
18372        buffer_text.to_string() + prettier_format_suffix,
18373        "Test prettier formatting was not applied to the original buffer text",
18374    );
18375
18376    update_test_language_settings(cx, |settings| {
18377        settings.defaults.formatter = Some(FormatterList::default())
18378    });
18379    let format = editor.update_in(cx, |editor, window, cx| {
18380        editor.perform_format(
18381            project.clone(),
18382            FormatTrigger::Manual,
18383            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18384            window,
18385            cx,
18386        )
18387    });
18388    format.await.unwrap();
18389    assert_eq!(
18390        editor.update(cx, |editor, cx| editor.text(cx)),
18391        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18392        "Autoformatting (via test prettier) was not applied to the original buffer text",
18393    );
18394}
18395
18396#[gpui::test]
18397async fn test_addition_reverts(cx: &mut TestAppContext) {
18398    init_test(cx, |_| {});
18399    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18400    let base_text = indoc! {r#"
18401        struct Row;
18402        struct Row1;
18403        struct Row2;
18404
18405        struct Row4;
18406        struct Row5;
18407        struct Row6;
18408
18409        struct Row8;
18410        struct Row9;
18411        struct Row10;"#};
18412
18413    // When addition hunks are not adjacent to carets, no hunk revert is performed
18414    assert_hunk_revert(
18415        indoc! {r#"struct Row;
18416                   struct Row1;
18417                   struct Row1.1;
18418                   struct Row1.2;
18419                   struct Row2;ˇ
18420
18421                   struct Row4;
18422                   struct Row5;
18423                   struct Row6;
18424
18425                   struct Row8;
18426                   ˇstruct Row9;
18427                   struct Row9.1;
18428                   struct Row9.2;
18429                   struct Row9.3;
18430                   struct Row10;"#},
18431        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18432        indoc! {r#"struct Row;
18433                   struct Row1;
18434                   struct Row1.1;
18435                   struct Row1.2;
18436                   struct Row2;ˇ
18437
18438                   struct Row4;
18439                   struct Row5;
18440                   struct Row6;
18441
18442                   struct Row8;
18443                   ˇstruct Row9;
18444                   struct Row9.1;
18445                   struct Row9.2;
18446                   struct Row9.3;
18447                   struct Row10;"#},
18448        base_text,
18449        &mut cx,
18450    );
18451    // Same for selections
18452    assert_hunk_revert(
18453        indoc! {r#"struct Row;
18454                   struct Row1;
18455                   struct Row2;
18456                   struct Row2.1;
18457                   struct Row2.2;
18458                   «ˇ
18459                   struct Row4;
18460                   struct» Row5;
18461                   «struct Row6;
18462                   ˇ»
18463                   struct Row9.1;
18464                   struct Row9.2;
18465                   struct Row9.3;
18466                   struct Row8;
18467                   struct Row9;
18468                   struct Row10;"#},
18469        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18470        indoc! {r#"struct Row;
18471                   struct Row1;
18472                   struct Row2;
18473                   struct Row2.1;
18474                   struct Row2.2;
18475                   «ˇ
18476                   struct Row4;
18477                   struct» Row5;
18478                   «struct Row6;
18479                   ˇ»
18480                   struct Row9.1;
18481                   struct Row9.2;
18482                   struct Row9.3;
18483                   struct Row8;
18484                   struct Row9;
18485                   struct Row10;"#},
18486        base_text,
18487        &mut cx,
18488    );
18489
18490    // When carets and selections intersect the addition hunks, those are reverted.
18491    // Adjacent carets got merged.
18492    assert_hunk_revert(
18493        indoc! {r#"struct Row;
18494                   ˇ// something on the top
18495                   struct Row1;
18496                   struct Row2;
18497                   struct Roˇw3.1;
18498                   struct Row2.2;
18499                   struct Row2.3;ˇ
18500
18501                   struct Row4;
18502                   struct ˇRow5.1;
18503                   struct Row5.2;
18504                   struct «Rowˇ»5.3;
18505                   struct Row5;
18506                   struct Row6;
18507                   ˇ
18508                   struct Row9.1;
18509                   struct «Rowˇ»9.2;
18510                   struct «ˇRow»9.3;
18511                   struct Row8;
18512                   struct Row9;
18513                   «ˇ// something on bottom»
18514                   struct Row10;"#},
18515        vec![
18516            DiffHunkStatusKind::Added,
18517            DiffHunkStatusKind::Added,
18518            DiffHunkStatusKind::Added,
18519            DiffHunkStatusKind::Added,
18520            DiffHunkStatusKind::Added,
18521        ],
18522        indoc! {r#"struct Row;
18523                   ˇstruct Row1;
18524                   struct Row2;
18525                   ˇ
18526                   struct Row4;
18527                   ˇstruct Row5;
18528                   struct Row6;
18529                   ˇ
18530                   ˇstruct Row8;
18531                   struct Row9;
18532                   ˇstruct Row10;"#},
18533        base_text,
18534        &mut cx,
18535    );
18536}
18537
18538#[gpui::test]
18539async fn test_modification_reverts(cx: &mut TestAppContext) {
18540    init_test(cx, |_| {});
18541    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18542    let base_text = indoc! {r#"
18543        struct Row;
18544        struct Row1;
18545        struct Row2;
18546
18547        struct Row4;
18548        struct Row5;
18549        struct Row6;
18550
18551        struct Row8;
18552        struct Row9;
18553        struct Row10;"#};
18554
18555    // Modification hunks behave the same as the addition ones.
18556    assert_hunk_revert(
18557        indoc! {r#"struct Row;
18558                   struct Row1;
18559                   struct Row33;
18560                   ˇ
18561                   struct Row4;
18562                   struct Row5;
18563                   struct Row6;
18564                   ˇ
18565                   struct Row99;
18566                   struct Row9;
18567                   struct Row10;"#},
18568        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18569        indoc! {r#"struct Row;
18570                   struct Row1;
18571                   struct Row33;
18572                   ˇ
18573                   struct Row4;
18574                   struct Row5;
18575                   struct Row6;
18576                   ˇ
18577                   struct Row99;
18578                   struct Row9;
18579                   struct Row10;"#},
18580        base_text,
18581        &mut cx,
18582    );
18583    assert_hunk_revert(
18584        indoc! {r#"struct Row;
18585                   struct Row1;
18586                   struct Row33;
18587                   «ˇ
18588                   struct Row4;
18589                   struct» Row5;
18590                   «struct Row6;
18591                   ˇ»
18592                   struct Row99;
18593                   struct Row9;
18594                   struct Row10;"#},
18595        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18596        indoc! {r#"struct Row;
18597                   struct Row1;
18598                   struct Row33;
18599                   «ˇ
18600                   struct Row4;
18601                   struct» Row5;
18602                   «struct Row6;
18603                   ˇ»
18604                   struct Row99;
18605                   struct Row9;
18606                   struct Row10;"#},
18607        base_text,
18608        &mut cx,
18609    );
18610
18611    assert_hunk_revert(
18612        indoc! {r#"ˇstruct Row1.1;
18613                   struct Row1;
18614                   «ˇstr»uct Row22;
18615
18616                   struct ˇRow44;
18617                   struct Row5;
18618                   struct «Rˇ»ow66;ˇ
18619
18620                   «struˇ»ct Row88;
18621                   struct Row9;
18622                   struct Row1011;ˇ"#},
18623        vec![
18624            DiffHunkStatusKind::Modified,
18625            DiffHunkStatusKind::Modified,
18626            DiffHunkStatusKind::Modified,
18627            DiffHunkStatusKind::Modified,
18628            DiffHunkStatusKind::Modified,
18629            DiffHunkStatusKind::Modified,
18630        ],
18631        indoc! {r#"struct Row;
18632                   ˇstruct Row1;
18633                   struct Row2;
18634                   ˇ
18635                   struct Row4;
18636                   ˇstruct Row5;
18637                   struct Row6;
18638                   ˇ
18639                   struct Row8;
18640                   ˇstruct Row9;
18641                   struct Row10;ˇ"#},
18642        base_text,
18643        &mut cx,
18644    );
18645}
18646
18647#[gpui::test]
18648async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18649    init_test(cx, |_| {});
18650    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18651    let base_text = indoc! {r#"
18652        one
18653
18654        two
18655        three
18656        "#};
18657
18658    cx.set_head_text(base_text);
18659    cx.set_state("\nˇ\n");
18660    cx.executor().run_until_parked();
18661    cx.update_editor(|editor, _window, cx| {
18662        editor.expand_selected_diff_hunks(cx);
18663    });
18664    cx.executor().run_until_parked();
18665    cx.update_editor(|editor, window, cx| {
18666        editor.backspace(&Default::default(), window, cx);
18667    });
18668    cx.run_until_parked();
18669    cx.assert_state_with_diff(
18670        indoc! {r#"
18671
18672        - two
18673        - threeˇ
18674        +
18675        "#}
18676        .to_string(),
18677    );
18678}
18679
18680#[gpui::test]
18681async fn test_deletion_reverts(cx: &mut TestAppContext) {
18682    init_test(cx, |_| {});
18683    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18684    let base_text = indoc! {r#"struct Row;
18685struct Row1;
18686struct Row2;
18687
18688struct Row4;
18689struct Row5;
18690struct Row6;
18691
18692struct Row8;
18693struct Row9;
18694struct Row10;"#};
18695
18696    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18697    assert_hunk_revert(
18698        indoc! {r#"struct Row;
18699                   struct Row2;
18700
18701                   ˇstruct Row4;
18702                   struct Row5;
18703                   struct Row6;
18704                   ˇ
18705                   struct Row8;
18706                   struct Row10;"#},
18707        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18708        indoc! {r#"struct Row;
18709                   struct Row2;
18710
18711                   ˇstruct Row4;
18712                   struct Row5;
18713                   struct Row6;
18714                   ˇ
18715                   struct Row8;
18716                   struct Row10;"#},
18717        base_text,
18718        &mut cx,
18719    );
18720    assert_hunk_revert(
18721        indoc! {r#"struct Row;
18722                   struct Row2;
18723
18724                   «ˇstruct Row4;
18725                   struct» Row5;
18726                   «struct Row6;
18727                   ˇ»
18728                   struct Row8;
18729                   struct Row10;"#},
18730        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18731        indoc! {r#"struct Row;
18732                   struct Row2;
18733
18734                   «ˇstruct Row4;
18735                   struct» Row5;
18736                   «struct Row6;
18737                   ˇ»
18738                   struct Row8;
18739                   struct Row10;"#},
18740        base_text,
18741        &mut cx,
18742    );
18743
18744    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18745    assert_hunk_revert(
18746        indoc! {r#"struct Row;
18747                   ˇstruct Row2;
18748
18749                   struct Row4;
18750                   struct Row5;
18751                   struct Row6;
18752
18753                   struct Row8;ˇ
18754                   struct Row10;"#},
18755        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18756        indoc! {r#"struct Row;
18757                   struct Row1;
18758                   ˇstruct Row2;
18759
18760                   struct Row4;
18761                   struct Row5;
18762                   struct Row6;
18763
18764                   struct Row8;ˇ
18765                   struct Row9;
18766                   struct Row10;"#},
18767        base_text,
18768        &mut cx,
18769    );
18770    assert_hunk_revert(
18771        indoc! {r#"struct Row;
18772                   struct Row2«ˇ;
18773                   struct Row4;
18774                   struct» Row5;
18775                   «struct Row6;
18776
18777                   struct Row8;ˇ»
18778                   struct Row10;"#},
18779        vec![
18780            DiffHunkStatusKind::Deleted,
18781            DiffHunkStatusKind::Deleted,
18782            DiffHunkStatusKind::Deleted,
18783        ],
18784        indoc! {r#"struct Row;
18785                   struct Row1;
18786                   struct Row2«ˇ;
18787
18788                   struct Row4;
18789                   struct» Row5;
18790                   «struct Row6;
18791
18792                   struct Row8;ˇ»
18793                   struct Row9;
18794                   struct Row10;"#},
18795        base_text,
18796        &mut cx,
18797    );
18798}
18799
18800#[gpui::test]
18801async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18802    init_test(cx, |_| {});
18803
18804    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18805    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18806    let base_text_3 =
18807        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18808
18809    let text_1 = edit_first_char_of_every_line(base_text_1);
18810    let text_2 = edit_first_char_of_every_line(base_text_2);
18811    let text_3 = edit_first_char_of_every_line(base_text_3);
18812
18813    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18814    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18815    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18816
18817    let multibuffer = cx.new(|cx| {
18818        let mut multibuffer = MultiBuffer::new(ReadWrite);
18819        multibuffer.push_excerpts(
18820            buffer_1.clone(),
18821            [
18822                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18823                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18824                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18825            ],
18826            cx,
18827        );
18828        multibuffer.push_excerpts(
18829            buffer_2.clone(),
18830            [
18831                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18832                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18833                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18834            ],
18835            cx,
18836        );
18837        multibuffer.push_excerpts(
18838            buffer_3.clone(),
18839            [
18840                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18841                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18842                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18843            ],
18844            cx,
18845        );
18846        multibuffer
18847    });
18848
18849    let fs = FakeFs::new(cx.executor());
18850    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18851    let (editor, cx) = cx
18852        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18853    editor.update_in(cx, |editor, _window, cx| {
18854        for (buffer, diff_base) in [
18855            (buffer_1.clone(), base_text_1),
18856            (buffer_2.clone(), base_text_2),
18857            (buffer_3.clone(), base_text_3),
18858        ] {
18859            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18860            editor
18861                .buffer
18862                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18863        }
18864    });
18865    cx.executor().run_until_parked();
18866
18867    editor.update_in(cx, |editor, window, cx| {
18868        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}");
18869        editor.select_all(&SelectAll, window, cx);
18870        editor.git_restore(&Default::default(), window, cx);
18871    });
18872    cx.executor().run_until_parked();
18873
18874    // When all ranges are selected, all buffer hunks are reverted.
18875    editor.update(cx, |editor, cx| {
18876        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");
18877    });
18878    buffer_1.update(cx, |buffer, _| {
18879        assert_eq!(buffer.text(), base_text_1);
18880    });
18881    buffer_2.update(cx, |buffer, _| {
18882        assert_eq!(buffer.text(), base_text_2);
18883    });
18884    buffer_3.update(cx, |buffer, _| {
18885        assert_eq!(buffer.text(), base_text_3);
18886    });
18887
18888    editor.update_in(cx, |editor, window, cx| {
18889        editor.undo(&Default::default(), window, cx);
18890    });
18891
18892    editor.update_in(cx, |editor, window, cx| {
18893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18894            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18895        });
18896        editor.git_restore(&Default::default(), window, cx);
18897    });
18898
18899    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18900    // but not affect buffer_2 and its related excerpts.
18901    editor.update(cx, |editor, cx| {
18902        assert_eq!(
18903            editor.text(cx),
18904            "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}"
18905        );
18906    });
18907    buffer_1.update(cx, |buffer, _| {
18908        assert_eq!(buffer.text(), base_text_1);
18909    });
18910    buffer_2.update(cx, |buffer, _| {
18911        assert_eq!(
18912            buffer.text(),
18913            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18914        );
18915    });
18916    buffer_3.update(cx, |buffer, _| {
18917        assert_eq!(
18918            buffer.text(),
18919            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18920        );
18921    });
18922
18923    fn edit_first_char_of_every_line(text: &str) -> String {
18924        text.split('\n')
18925            .map(|line| format!("X{}", &line[1..]))
18926            .collect::<Vec<_>>()
18927            .join("\n")
18928    }
18929}
18930
18931#[gpui::test]
18932async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18933    init_test(cx, |_| {});
18934
18935    let cols = 4;
18936    let rows = 10;
18937    let sample_text_1 = sample_text(rows, cols, 'a');
18938    assert_eq!(
18939        sample_text_1,
18940        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18941    );
18942    let sample_text_2 = sample_text(rows, cols, 'l');
18943    assert_eq!(
18944        sample_text_2,
18945        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18946    );
18947    let sample_text_3 = sample_text(rows, cols, 'v');
18948    assert_eq!(
18949        sample_text_3,
18950        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18951    );
18952
18953    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18954    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18955    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18956
18957    let multi_buffer = cx.new(|cx| {
18958        let mut multibuffer = MultiBuffer::new(ReadWrite);
18959        multibuffer.push_excerpts(
18960            buffer_1.clone(),
18961            [
18962                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18963                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18964                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18965            ],
18966            cx,
18967        );
18968        multibuffer.push_excerpts(
18969            buffer_2.clone(),
18970            [
18971                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18972                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18973                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18974            ],
18975            cx,
18976        );
18977        multibuffer.push_excerpts(
18978            buffer_3.clone(),
18979            [
18980                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18981                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18982                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18983            ],
18984            cx,
18985        );
18986        multibuffer
18987    });
18988
18989    let fs = FakeFs::new(cx.executor());
18990    fs.insert_tree(
18991        "/a",
18992        json!({
18993            "main.rs": sample_text_1,
18994            "other.rs": sample_text_2,
18995            "lib.rs": sample_text_3,
18996        }),
18997    )
18998    .await;
18999    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19000    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19001    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19002    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19003        Editor::new(
19004            EditorMode::full(),
19005            multi_buffer,
19006            Some(project.clone()),
19007            window,
19008            cx,
19009        )
19010    });
19011    let multibuffer_item_id = workspace
19012        .update(cx, |workspace, window, cx| {
19013            assert!(
19014                workspace.active_item(cx).is_none(),
19015                "active item should be None before the first item is added"
19016            );
19017            workspace.add_item_to_active_pane(
19018                Box::new(multi_buffer_editor.clone()),
19019                None,
19020                true,
19021                window,
19022                cx,
19023            );
19024            let active_item = workspace
19025                .active_item(cx)
19026                .expect("should have an active item after adding the multi buffer");
19027            assert_eq!(
19028                active_item.buffer_kind(cx),
19029                ItemBufferKind::Multibuffer,
19030                "A multi buffer was expected to active after adding"
19031            );
19032            active_item.item_id()
19033        })
19034        .unwrap();
19035    cx.executor().run_until_parked();
19036
19037    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19038        editor.change_selections(
19039            SelectionEffects::scroll(Autoscroll::Next),
19040            window,
19041            cx,
19042            |s| s.select_ranges(Some(1..2)),
19043        );
19044        editor.open_excerpts(&OpenExcerpts, window, cx);
19045    });
19046    cx.executor().run_until_parked();
19047    let first_item_id = workspace
19048        .update(cx, |workspace, window, cx| {
19049            let active_item = workspace
19050                .active_item(cx)
19051                .expect("should have an active item after navigating into the 1st buffer");
19052            let first_item_id = active_item.item_id();
19053            assert_ne!(
19054                first_item_id, multibuffer_item_id,
19055                "Should navigate into the 1st buffer and activate it"
19056            );
19057            assert_eq!(
19058                active_item.buffer_kind(cx),
19059                ItemBufferKind::Singleton,
19060                "New active item should be a singleton buffer"
19061            );
19062            assert_eq!(
19063                active_item
19064                    .act_as::<Editor>(cx)
19065                    .expect("should have navigated into an editor for the 1st buffer")
19066                    .read(cx)
19067                    .text(cx),
19068                sample_text_1
19069            );
19070
19071            workspace
19072                .go_back(workspace.active_pane().downgrade(), window, cx)
19073                .detach_and_log_err(cx);
19074
19075            first_item_id
19076        })
19077        .unwrap();
19078    cx.executor().run_until_parked();
19079    workspace
19080        .update(cx, |workspace, _, cx| {
19081            let active_item = workspace
19082                .active_item(cx)
19083                .expect("should have an active item after navigating back");
19084            assert_eq!(
19085                active_item.item_id(),
19086                multibuffer_item_id,
19087                "Should navigate back to the multi buffer"
19088            );
19089            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19090        })
19091        .unwrap();
19092
19093    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19094        editor.change_selections(
19095            SelectionEffects::scroll(Autoscroll::Next),
19096            window,
19097            cx,
19098            |s| s.select_ranges(Some(39..40)),
19099        );
19100        editor.open_excerpts(&OpenExcerpts, window, cx);
19101    });
19102    cx.executor().run_until_parked();
19103    let second_item_id = workspace
19104        .update(cx, |workspace, window, cx| {
19105            let active_item = workspace
19106                .active_item(cx)
19107                .expect("should have an active item after navigating into the 2nd buffer");
19108            let second_item_id = active_item.item_id();
19109            assert_ne!(
19110                second_item_id, multibuffer_item_id,
19111                "Should navigate away from the multibuffer"
19112            );
19113            assert_ne!(
19114                second_item_id, first_item_id,
19115                "Should navigate into the 2nd buffer and activate it"
19116            );
19117            assert_eq!(
19118                active_item.buffer_kind(cx),
19119                ItemBufferKind::Singleton,
19120                "New active item should be a singleton buffer"
19121            );
19122            assert_eq!(
19123                active_item
19124                    .act_as::<Editor>(cx)
19125                    .expect("should have navigated into an editor")
19126                    .read(cx)
19127                    .text(cx),
19128                sample_text_2
19129            );
19130
19131            workspace
19132                .go_back(workspace.active_pane().downgrade(), window, cx)
19133                .detach_and_log_err(cx);
19134
19135            second_item_id
19136        })
19137        .unwrap();
19138    cx.executor().run_until_parked();
19139    workspace
19140        .update(cx, |workspace, _, cx| {
19141            let active_item = workspace
19142                .active_item(cx)
19143                .expect("should have an active item after navigating back from the 2nd buffer");
19144            assert_eq!(
19145                active_item.item_id(),
19146                multibuffer_item_id,
19147                "Should navigate back from the 2nd buffer to the multi buffer"
19148            );
19149            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19150        })
19151        .unwrap();
19152
19153    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19154        editor.change_selections(
19155            SelectionEffects::scroll(Autoscroll::Next),
19156            window,
19157            cx,
19158            |s| s.select_ranges(Some(70..70)),
19159        );
19160        editor.open_excerpts(&OpenExcerpts, window, cx);
19161    });
19162    cx.executor().run_until_parked();
19163    workspace
19164        .update(cx, |workspace, window, cx| {
19165            let active_item = workspace
19166                .active_item(cx)
19167                .expect("should have an active item after navigating into the 3rd buffer");
19168            let third_item_id = active_item.item_id();
19169            assert_ne!(
19170                third_item_id, multibuffer_item_id,
19171                "Should navigate into the 3rd buffer and activate it"
19172            );
19173            assert_ne!(third_item_id, first_item_id);
19174            assert_ne!(third_item_id, second_item_id);
19175            assert_eq!(
19176                active_item.buffer_kind(cx),
19177                ItemBufferKind::Singleton,
19178                "New active item should be a singleton buffer"
19179            );
19180            assert_eq!(
19181                active_item
19182                    .act_as::<Editor>(cx)
19183                    .expect("should have navigated into an editor")
19184                    .read(cx)
19185                    .text(cx),
19186                sample_text_3
19187            );
19188
19189            workspace
19190                .go_back(workspace.active_pane().downgrade(), window, cx)
19191                .detach_and_log_err(cx);
19192        })
19193        .unwrap();
19194    cx.executor().run_until_parked();
19195    workspace
19196        .update(cx, |workspace, _, cx| {
19197            let active_item = workspace
19198                .active_item(cx)
19199                .expect("should have an active item after navigating back from the 3rd buffer");
19200            assert_eq!(
19201                active_item.item_id(),
19202                multibuffer_item_id,
19203                "Should navigate back from the 3rd buffer to the multi buffer"
19204            );
19205            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19206        })
19207        .unwrap();
19208}
19209
19210#[gpui::test]
19211async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19212    init_test(cx, |_| {});
19213
19214    let mut cx = EditorTestContext::new(cx).await;
19215
19216    let diff_base = r#"
19217        use some::mod;
19218
19219        const A: u32 = 42;
19220
19221        fn main() {
19222            println!("hello");
19223
19224            println!("world");
19225        }
19226        "#
19227    .unindent();
19228
19229    cx.set_state(
19230        &r#"
19231        use some::modified;
19232
19233        ˇ
19234        fn main() {
19235            println!("hello there");
19236
19237            println!("around the");
19238            println!("world");
19239        }
19240        "#
19241        .unindent(),
19242    );
19243
19244    cx.set_head_text(&diff_base);
19245    executor.run_until_parked();
19246
19247    cx.update_editor(|editor, window, cx| {
19248        editor.go_to_next_hunk(&GoToHunk, window, cx);
19249        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19250    });
19251    executor.run_until_parked();
19252    cx.assert_state_with_diff(
19253        r#"
19254          use some::modified;
19255
19256
19257          fn main() {
19258        -     println!("hello");
19259        + ˇ    println!("hello there");
19260
19261              println!("around the");
19262              println!("world");
19263          }
19264        "#
19265        .unindent(),
19266    );
19267
19268    cx.update_editor(|editor, window, cx| {
19269        for _ in 0..2 {
19270            editor.go_to_next_hunk(&GoToHunk, window, cx);
19271            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19272        }
19273    });
19274    executor.run_until_parked();
19275    cx.assert_state_with_diff(
19276        r#"
19277        - use some::mod;
19278        + ˇuse some::modified;
19279
19280
19281          fn main() {
19282        -     println!("hello");
19283        +     println!("hello there");
19284
19285        +     println!("around the");
19286              println!("world");
19287          }
19288        "#
19289        .unindent(),
19290    );
19291
19292    cx.update_editor(|editor, window, cx| {
19293        editor.go_to_next_hunk(&GoToHunk, window, cx);
19294        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19295    });
19296    executor.run_until_parked();
19297    cx.assert_state_with_diff(
19298        r#"
19299        - use some::mod;
19300        + use some::modified;
19301
19302        - const A: u32 = 42;
19303          ˇ
19304          fn main() {
19305        -     println!("hello");
19306        +     println!("hello there");
19307
19308        +     println!("around the");
19309              println!("world");
19310          }
19311        "#
19312        .unindent(),
19313    );
19314
19315    cx.update_editor(|editor, window, cx| {
19316        editor.cancel(&Cancel, window, cx);
19317    });
19318
19319    cx.assert_state_with_diff(
19320        r#"
19321          use some::modified;
19322
19323          ˇ
19324          fn main() {
19325              println!("hello there");
19326
19327              println!("around the");
19328              println!("world");
19329          }
19330        "#
19331        .unindent(),
19332    );
19333}
19334
19335#[gpui::test]
19336async fn test_diff_base_change_with_expanded_diff_hunks(
19337    executor: BackgroundExecutor,
19338    cx: &mut TestAppContext,
19339) {
19340    init_test(cx, |_| {});
19341
19342    let mut cx = EditorTestContext::new(cx).await;
19343
19344    let diff_base = r#"
19345        use some::mod1;
19346        use some::mod2;
19347
19348        const A: u32 = 42;
19349        const B: u32 = 42;
19350        const C: u32 = 42;
19351
19352        fn main() {
19353            println!("hello");
19354
19355            println!("world");
19356        }
19357        "#
19358    .unindent();
19359
19360    cx.set_state(
19361        &r#"
19362        use some::mod2;
19363
19364        const A: u32 = 42;
19365        const C: u32 = 42;
19366
19367        fn main(ˇ) {
19368            //println!("hello");
19369
19370            println!("world");
19371            //
19372            //
19373        }
19374        "#
19375        .unindent(),
19376    );
19377
19378    cx.set_head_text(&diff_base);
19379    executor.run_until_parked();
19380
19381    cx.update_editor(|editor, window, cx| {
19382        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19383    });
19384    executor.run_until_parked();
19385    cx.assert_state_with_diff(
19386        r#"
19387        - use some::mod1;
19388          use some::mod2;
19389
19390          const A: u32 = 42;
19391        - const B: u32 = 42;
19392          const C: u32 = 42;
19393
19394          fn main(ˇ) {
19395        -     println!("hello");
19396        +     //println!("hello");
19397
19398              println!("world");
19399        +     //
19400        +     //
19401          }
19402        "#
19403        .unindent(),
19404    );
19405
19406    cx.set_head_text("new diff base!");
19407    executor.run_until_parked();
19408    cx.assert_state_with_diff(
19409        r#"
19410        - new diff base!
19411        + use some::mod2;
19412        +
19413        + const A: u32 = 42;
19414        + const C: u32 = 42;
19415        +
19416        + fn main(ˇ) {
19417        +     //println!("hello");
19418        +
19419        +     println!("world");
19420        +     //
19421        +     //
19422        + }
19423        "#
19424        .unindent(),
19425    );
19426}
19427
19428#[gpui::test]
19429async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19430    init_test(cx, |_| {});
19431
19432    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19433    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19434    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19435    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19436    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19437    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19438
19439    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19440    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19441    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19442
19443    let multi_buffer = cx.new(|cx| {
19444        let mut multibuffer = MultiBuffer::new(ReadWrite);
19445        multibuffer.push_excerpts(
19446            buffer_1.clone(),
19447            [
19448                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19449                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19450                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19451            ],
19452            cx,
19453        );
19454        multibuffer.push_excerpts(
19455            buffer_2.clone(),
19456            [
19457                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19458                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19459                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19460            ],
19461            cx,
19462        );
19463        multibuffer.push_excerpts(
19464            buffer_3.clone(),
19465            [
19466                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19467                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19468                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19469            ],
19470            cx,
19471        );
19472        multibuffer
19473    });
19474
19475    let editor =
19476        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19477    editor
19478        .update(cx, |editor, _window, cx| {
19479            for (buffer, diff_base) in [
19480                (buffer_1.clone(), file_1_old),
19481                (buffer_2.clone(), file_2_old),
19482                (buffer_3.clone(), file_3_old),
19483            ] {
19484                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19485                editor
19486                    .buffer
19487                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19488            }
19489        })
19490        .unwrap();
19491
19492    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19493    cx.run_until_parked();
19494
19495    cx.assert_editor_state(
19496        &"
19497            ˇaaa
19498            ccc
19499            ddd
19500
19501            ggg
19502            hhh
19503
19504
19505            lll
19506            mmm
19507            NNN
19508
19509            qqq
19510            rrr
19511
19512            uuu
19513            111
19514            222
19515            333
19516
19517            666
19518            777
19519
19520            000
19521            !!!"
19522        .unindent(),
19523    );
19524
19525    cx.update_editor(|editor, window, cx| {
19526        editor.select_all(&SelectAll, window, cx);
19527        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19528    });
19529    cx.executor().run_until_parked();
19530
19531    cx.assert_state_with_diff(
19532        "
19533            «aaa
19534          - bbb
19535            ccc
19536            ddd
19537
19538            ggg
19539            hhh
19540
19541
19542            lll
19543            mmm
19544          - nnn
19545          + NNN
19546
19547            qqq
19548            rrr
19549
19550            uuu
19551            111
19552            222
19553            333
19554
19555          + 666
19556            777
19557
19558            000
19559            !!!ˇ»"
19560            .unindent(),
19561    );
19562}
19563
19564#[gpui::test]
19565async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19566    init_test(cx, |_| {});
19567
19568    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19569    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19570
19571    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19572    let multi_buffer = cx.new(|cx| {
19573        let mut multibuffer = MultiBuffer::new(ReadWrite);
19574        multibuffer.push_excerpts(
19575            buffer.clone(),
19576            [
19577                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19578                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19579                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19580            ],
19581            cx,
19582        );
19583        multibuffer
19584    });
19585
19586    let editor =
19587        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19588    editor
19589        .update(cx, |editor, _window, cx| {
19590            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19591            editor
19592                .buffer
19593                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19594        })
19595        .unwrap();
19596
19597    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19598    cx.run_until_parked();
19599
19600    cx.update_editor(|editor, window, cx| {
19601        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19602    });
19603    cx.executor().run_until_parked();
19604
19605    // When the start of a hunk coincides with the start of its excerpt,
19606    // the hunk is expanded. When the start of a hunk is earlier than
19607    // the start of its excerpt, the hunk is not expanded.
19608    cx.assert_state_with_diff(
19609        "
19610            ˇaaa
19611          - bbb
19612          + BBB
19613
19614          - ddd
19615          - eee
19616          + DDD
19617          + EEE
19618            fff
19619
19620            iii
19621        "
19622        .unindent(),
19623    );
19624}
19625
19626#[gpui::test]
19627async fn test_edits_around_expanded_insertion_hunks(
19628    executor: BackgroundExecutor,
19629    cx: &mut TestAppContext,
19630) {
19631    init_test(cx, |_| {});
19632
19633    let mut cx = EditorTestContext::new(cx).await;
19634
19635    let diff_base = r#"
19636        use some::mod1;
19637        use some::mod2;
19638
19639        const A: u32 = 42;
19640
19641        fn main() {
19642            println!("hello");
19643
19644            println!("world");
19645        }
19646        "#
19647    .unindent();
19648    executor.run_until_parked();
19649    cx.set_state(
19650        &r#"
19651        use some::mod1;
19652        use some::mod2;
19653
19654        const A: u32 = 42;
19655        const B: u32 = 42;
19656        const C: u32 = 42;
19657        ˇ
19658
19659        fn main() {
19660            println!("hello");
19661
19662            println!("world");
19663        }
19664        "#
19665        .unindent(),
19666    );
19667
19668    cx.set_head_text(&diff_base);
19669    executor.run_until_parked();
19670
19671    cx.update_editor(|editor, window, cx| {
19672        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19673    });
19674    executor.run_until_parked();
19675
19676    cx.assert_state_with_diff(
19677        r#"
19678        use some::mod1;
19679        use some::mod2;
19680
19681        const A: u32 = 42;
19682      + const B: u32 = 42;
19683      + const C: u32 = 42;
19684      + ˇ
19685
19686        fn main() {
19687            println!("hello");
19688
19689            println!("world");
19690        }
19691      "#
19692        .unindent(),
19693    );
19694
19695    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19696    executor.run_until_parked();
19697
19698    cx.assert_state_with_diff(
19699        r#"
19700        use some::mod1;
19701        use some::mod2;
19702
19703        const A: u32 = 42;
19704      + const B: u32 = 42;
19705      + const C: u32 = 42;
19706      + const D: u32 = 42;
19707      + ˇ
19708
19709        fn main() {
19710            println!("hello");
19711
19712            println!("world");
19713        }
19714      "#
19715        .unindent(),
19716    );
19717
19718    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19719    executor.run_until_parked();
19720
19721    cx.assert_state_with_diff(
19722        r#"
19723        use some::mod1;
19724        use some::mod2;
19725
19726        const A: u32 = 42;
19727      + const B: u32 = 42;
19728      + const C: u32 = 42;
19729      + const D: u32 = 42;
19730      + const E: u32 = 42;
19731      + ˇ
19732
19733        fn main() {
19734            println!("hello");
19735
19736            println!("world");
19737        }
19738      "#
19739        .unindent(),
19740    );
19741
19742    cx.update_editor(|editor, window, cx| {
19743        editor.delete_line(&DeleteLine, window, cx);
19744    });
19745    executor.run_until_parked();
19746
19747    cx.assert_state_with_diff(
19748        r#"
19749        use some::mod1;
19750        use some::mod2;
19751
19752        const A: u32 = 42;
19753      + const B: u32 = 42;
19754      + const C: u32 = 42;
19755      + const D: u32 = 42;
19756      + const E: u32 = 42;
19757        ˇ
19758        fn main() {
19759            println!("hello");
19760
19761            println!("world");
19762        }
19763      "#
19764        .unindent(),
19765    );
19766
19767    cx.update_editor(|editor, window, cx| {
19768        editor.move_up(&MoveUp, window, cx);
19769        editor.delete_line(&DeleteLine, window, cx);
19770        editor.move_up(&MoveUp, window, cx);
19771        editor.delete_line(&DeleteLine, window, cx);
19772        editor.move_up(&MoveUp, window, cx);
19773        editor.delete_line(&DeleteLine, window, cx);
19774    });
19775    executor.run_until_parked();
19776    cx.assert_state_with_diff(
19777        r#"
19778        use some::mod1;
19779        use some::mod2;
19780
19781        const A: u32 = 42;
19782      + const B: u32 = 42;
19783        ˇ
19784        fn main() {
19785            println!("hello");
19786
19787            println!("world");
19788        }
19789      "#
19790        .unindent(),
19791    );
19792
19793    cx.update_editor(|editor, window, cx| {
19794        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19795        editor.delete_line(&DeleteLine, window, cx);
19796    });
19797    executor.run_until_parked();
19798    cx.assert_state_with_diff(
19799        r#"
19800        ˇ
19801        fn main() {
19802            println!("hello");
19803
19804            println!("world");
19805        }
19806      "#
19807        .unindent(),
19808    );
19809}
19810
19811#[gpui::test]
19812async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19813    init_test(cx, |_| {});
19814
19815    let mut cx = EditorTestContext::new(cx).await;
19816    cx.set_head_text(indoc! { "
19817        one
19818        two
19819        three
19820        four
19821        five
19822        "
19823    });
19824    cx.set_state(indoc! { "
19825        one
19826        ˇthree
19827        five
19828    "});
19829    cx.run_until_parked();
19830    cx.update_editor(|editor, window, cx| {
19831        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19832    });
19833    cx.assert_state_with_diff(
19834        indoc! { "
19835        one
19836      - two
19837        ˇthree
19838      - four
19839        five
19840    "}
19841        .to_string(),
19842    );
19843    cx.update_editor(|editor, window, cx| {
19844        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19845    });
19846
19847    cx.assert_state_with_diff(
19848        indoc! { "
19849        one
19850        ˇthree
19851        five
19852    "}
19853        .to_string(),
19854    );
19855
19856    cx.set_state(indoc! { "
19857        one
19858        ˇTWO
19859        three
19860        four
19861        five
19862    "});
19863    cx.run_until_parked();
19864    cx.update_editor(|editor, window, cx| {
19865        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19866    });
19867
19868    cx.assert_state_with_diff(
19869        indoc! { "
19870            one
19871          - two
19872          + ˇTWO
19873            three
19874            four
19875            five
19876        "}
19877        .to_string(),
19878    );
19879    cx.update_editor(|editor, window, cx| {
19880        editor.move_up(&Default::default(), window, cx);
19881        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19882    });
19883    cx.assert_state_with_diff(
19884        indoc! { "
19885            one
19886            ˇTWO
19887            three
19888            four
19889            five
19890        "}
19891        .to_string(),
19892    );
19893}
19894
19895#[gpui::test]
19896async fn test_edits_around_expanded_deletion_hunks(
19897    executor: BackgroundExecutor,
19898    cx: &mut TestAppContext,
19899) {
19900    init_test(cx, |_| {});
19901
19902    let mut cx = EditorTestContext::new(cx).await;
19903
19904    let diff_base = r#"
19905        use some::mod1;
19906        use some::mod2;
19907
19908        const A: u32 = 42;
19909        const B: u32 = 42;
19910        const C: u32 = 42;
19911
19912
19913        fn main() {
19914            println!("hello");
19915
19916            println!("world");
19917        }
19918    "#
19919    .unindent();
19920    executor.run_until_parked();
19921    cx.set_state(
19922        &r#"
19923        use some::mod1;
19924        use some::mod2;
19925
19926        ˇconst B: u32 = 42;
19927        const C: u32 = 42;
19928
19929
19930        fn main() {
19931            println!("hello");
19932
19933            println!("world");
19934        }
19935        "#
19936        .unindent(),
19937    );
19938
19939    cx.set_head_text(&diff_base);
19940    executor.run_until_parked();
19941
19942    cx.update_editor(|editor, window, cx| {
19943        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19944    });
19945    executor.run_until_parked();
19946
19947    cx.assert_state_with_diff(
19948        r#"
19949        use some::mod1;
19950        use some::mod2;
19951
19952      - const A: u32 = 42;
19953        ˇconst B: u32 = 42;
19954        const C: u32 = 42;
19955
19956
19957        fn main() {
19958            println!("hello");
19959
19960            println!("world");
19961        }
19962      "#
19963        .unindent(),
19964    );
19965
19966    cx.update_editor(|editor, window, cx| {
19967        editor.delete_line(&DeleteLine, window, cx);
19968    });
19969    executor.run_until_parked();
19970    cx.assert_state_with_diff(
19971        r#"
19972        use some::mod1;
19973        use some::mod2;
19974
19975      - const A: u32 = 42;
19976      - const B: u32 = 42;
19977        ˇconst C: u32 = 42;
19978
19979
19980        fn main() {
19981            println!("hello");
19982
19983            println!("world");
19984        }
19985      "#
19986        .unindent(),
19987    );
19988
19989    cx.update_editor(|editor, window, cx| {
19990        editor.delete_line(&DeleteLine, window, cx);
19991    });
19992    executor.run_until_parked();
19993    cx.assert_state_with_diff(
19994        r#"
19995        use some::mod1;
19996        use some::mod2;
19997
19998      - const A: u32 = 42;
19999      - const B: u32 = 42;
20000      - const C: u32 = 42;
20001        ˇ
20002
20003        fn main() {
20004            println!("hello");
20005
20006            println!("world");
20007        }
20008      "#
20009        .unindent(),
20010    );
20011
20012    cx.update_editor(|editor, window, cx| {
20013        editor.handle_input("replacement", window, cx);
20014    });
20015    executor.run_until_parked();
20016    cx.assert_state_with_diff(
20017        r#"
20018        use some::mod1;
20019        use some::mod2;
20020
20021      - const A: u32 = 42;
20022      - const B: u32 = 42;
20023      - const C: u32 = 42;
20024      -
20025      + replacementˇ
20026
20027        fn main() {
20028            println!("hello");
20029
20030            println!("world");
20031        }
20032      "#
20033        .unindent(),
20034    );
20035}
20036
20037#[gpui::test]
20038async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20039    init_test(cx, |_| {});
20040
20041    let mut cx = EditorTestContext::new(cx).await;
20042
20043    let base_text = r#"
20044        one
20045        two
20046        three
20047        four
20048        five
20049    "#
20050    .unindent();
20051    executor.run_until_parked();
20052    cx.set_state(
20053        &r#"
20054        one
20055        two
20056        fˇour
20057        five
20058        "#
20059        .unindent(),
20060    );
20061
20062    cx.set_head_text(&base_text);
20063    executor.run_until_parked();
20064
20065    cx.update_editor(|editor, window, cx| {
20066        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20067    });
20068    executor.run_until_parked();
20069
20070    cx.assert_state_with_diff(
20071        r#"
20072          one
20073          two
20074        - three
20075          fˇour
20076          five
20077        "#
20078        .unindent(),
20079    );
20080
20081    cx.update_editor(|editor, window, cx| {
20082        editor.backspace(&Backspace, window, cx);
20083        editor.backspace(&Backspace, window, cx);
20084    });
20085    executor.run_until_parked();
20086    cx.assert_state_with_diff(
20087        r#"
20088          one
20089          two
20090        - threeˇ
20091        - four
20092        + our
20093          five
20094        "#
20095        .unindent(),
20096    );
20097}
20098
20099#[gpui::test]
20100async fn test_edit_after_expanded_modification_hunk(
20101    executor: BackgroundExecutor,
20102    cx: &mut TestAppContext,
20103) {
20104    init_test(cx, |_| {});
20105
20106    let mut cx = EditorTestContext::new(cx).await;
20107
20108    let diff_base = r#"
20109        use some::mod1;
20110        use some::mod2;
20111
20112        const A: u32 = 42;
20113        const B: u32 = 42;
20114        const C: u32 = 42;
20115        const D: u32 = 42;
20116
20117
20118        fn main() {
20119            println!("hello");
20120
20121            println!("world");
20122        }"#
20123    .unindent();
20124
20125    cx.set_state(
20126        &r#"
20127        use some::mod1;
20128        use some::mod2;
20129
20130        const A: u32 = 42;
20131        const B: u32 = 42;
20132        const C: u32 = 43ˇ
20133        const D: u32 = 42;
20134
20135
20136        fn main() {
20137            println!("hello");
20138
20139            println!("world");
20140        }"#
20141        .unindent(),
20142    );
20143
20144    cx.set_head_text(&diff_base);
20145    executor.run_until_parked();
20146    cx.update_editor(|editor, window, cx| {
20147        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20148    });
20149    executor.run_until_parked();
20150
20151    cx.assert_state_with_diff(
20152        r#"
20153        use some::mod1;
20154        use some::mod2;
20155
20156        const A: u32 = 42;
20157        const B: u32 = 42;
20158      - const C: u32 = 42;
20159      + const C: u32 = 43ˇ
20160        const D: u32 = 42;
20161
20162
20163        fn main() {
20164            println!("hello");
20165
20166            println!("world");
20167        }"#
20168        .unindent(),
20169    );
20170
20171    cx.update_editor(|editor, window, cx| {
20172        editor.handle_input("\nnew_line\n", window, cx);
20173    });
20174    executor.run_until_parked();
20175
20176    cx.assert_state_with_diff(
20177        r#"
20178        use some::mod1;
20179        use some::mod2;
20180
20181        const A: u32 = 42;
20182        const B: u32 = 42;
20183      - const C: u32 = 42;
20184      + const C: u32 = 43
20185      + new_line
20186      + ˇ
20187        const D: u32 = 42;
20188
20189
20190        fn main() {
20191            println!("hello");
20192
20193            println!("world");
20194        }"#
20195        .unindent(),
20196    );
20197}
20198
20199#[gpui::test]
20200async fn test_stage_and_unstage_added_file_hunk(
20201    executor: BackgroundExecutor,
20202    cx: &mut TestAppContext,
20203) {
20204    init_test(cx, |_| {});
20205
20206    let mut cx = EditorTestContext::new(cx).await;
20207    cx.update_editor(|editor, _, cx| {
20208        editor.set_expand_all_diff_hunks(cx);
20209    });
20210
20211    let working_copy = r#"
20212            ˇfn main() {
20213                println!("hello, world!");
20214            }
20215        "#
20216    .unindent();
20217
20218    cx.set_state(&working_copy);
20219    executor.run_until_parked();
20220
20221    cx.assert_state_with_diff(
20222        r#"
20223            + ˇfn main() {
20224            +     println!("hello, world!");
20225            + }
20226        "#
20227        .unindent(),
20228    );
20229    cx.assert_index_text(None);
20230
20231    cx.update_editor(|editor, window, cx| {
20232        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20233    });
20234    executor.run_until_parked();
20235    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20236    cx.assert_state_with_diff(
20237        r#"
20238            + ˇfn main() {
20239            +     println!("hello, world!");
20240            + }
20241        "#
20242        .unindent(),
20243    );
20244
20245    cx.update_editor(|editor, window, cx| {
20246        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20247    });
20248    executor.run_until_parked();
20249    cx.assert_index_text(None);
20250}
20251
20252async fn setup_indent_guides_editor(
20253    text: &str,
20254    cx: &mut TestAppContext,
20255) -> (BufferId, EditorTestContext) {
20256    init_test(cx, |_| {});
20257
20258    let mut cx = EditorTestContext::new(cx).await;
20259
20260    let buffer_id = cx.update_editor(|editor, window, cx| {
20261        editor.set_text(text, window, cx);
20262        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20263
20264        buffer_ids[0]
20265    });
20266
20267    (buffer_id, cx)
20268}
20269
20270fn assert_indent_guides(
20271    range: Range<u32>,
20272    expected: Vec<IndentGuide>,
20273    active_indices: Option<Vec<usize>>,
20274    cx: &mut EditorTestContext,
20275) {
20276    let indent_guides = cx.update_editor(|editor, window, cx| {
20277        let snapshot = editor.snapshot(window, cx).display_snapshot;
20278        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20279            editor,
20280            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20281            true,
20282            &snapshot,
20283            cx,
20284        );
20285
20286        indent_guides.sort_by(|a, b| {
20287            a.depth.cmp(&b.depth).then(
20288                a.start_row
20289                    .cmp(&b.start_row)
20290                    .then(a.end_row.cmp(&b.end_row)),
20291            )
20292        });
20293        indent_guides
20294    });
20295
20296    if let Some(expected) = active_indices {
20297        let active_indices = cx.update_editor(|editor, window, cx| {
20298            let snapshot = editor.snapshot(window, cx).display_snapshot;
20299            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20300        });
20301
20302        assert_eq!(
20303            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20304            expected,
20305            "Active indent guide indices do not match"
20306        );
20307    }
20308
20309    assert_eq!(indent_guides, expected, "Indent guides do not match");
20310}
20311
20312fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20313    IndentGuide {
20314        buffer_id,
20315        start_row: MultiBufferRow(start_row),
20316        end_row: MultiBufferRow(end_row),
20317        depth,
20318        tab_size: 4,
20319        settings: IndentGuideSettings {
20320            enabled: true,
20321            line_width: 1,
20322            active_line_width: 1,
20323            coloring: IndentGuideColoring::default(),
20324            background_coloring: IndentGuideBackgroundColoring::default(),
20325        },
20326    }
20327}
20328
20329#[gpui::test]
20330async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20331    let (buffer_id, mut cx) = setup_indent_guides_editor(
20332        &"
20333        fn main() {
20334            let a = 1;
20335        }"
20336        .unindent(),
20337        cx,
20338    )
20339    .await;
20340
20341    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20342}
20343
20344#[gpui::test]
20345async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20346    let (buffer_id, mut cx) = setup_indent_guides_editor(
20347        &"
20348        fn main() {
20349            let a = 1;
20350            let b = 2;
20351        }"
20352        .unindent(),
20353        cx,
20354    )
20355    .await;
20356
20357    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20358}
20359
20360#[gpui::test]
20361async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20362    let (buffer_id, mut cx) = setup_indent_guides_editor(
20363        &"
20364        fn main() {
20365            let a = 1;
20366            if a == 3 {
20367                let b = 2;
20368            } else {
20369                let c = 3;
20370            }
20371        }"
20372        .unindent(),
20373        cx,
20374    )
20375    .await;
20376
20377    assert_indent_guides(
20378        0..8,
20379        vec![
20380            indent_guide(buffer_id, 1, 6, 0),
20381            indent_guide(buffer_id, 3, 3, 1),
20382            indent_guide(buffer_id, 5, 5, 1),
20383        ],
20384        None,
20385        &mut cx,
20386    );
20387}
20388
20389#[gpui::test]
20390async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20391    let (buffer_id, mut cx) = setup_indent_guides_editor(
20392        &"
20393        fn main() {
20394            let a = 1;
20395                let b = 2;
20396            let c = 3;
20397        }"
20398        .unindent(),
20399        cx,
20400    )
20401    .await;
20402
20403    assert_indent_guides(
20404        0..5,
20405        vec![
20406            indent_guide(buffer_id, 1, 3, 0),
20407            indent_guide(buffer_id, 2, 2, 1),
20408        ],
20409        None,
20410        &mut cx,
20411    );
20412}
20413
20414#[gpui::test]
20415async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20416    let (buffer_id, mut cx) = setup_indent_guides_editor(
20417        &"
20418        fn main() {
20419            let a = 1;
20420
20421            let c = 3;
20422        }"
20423        .unindent(),
20424        cx,
20425    )
20426    .await;
20427
20428    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20429}
20430
20431#[gpui::test]
20432async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20433    let (buffer_id, mut cx) = setup_indent_guides_editor(
20434        &"
20435        fn main() {
20436            let a = 1;
20437
20438            let c = 3;
20439
20440            if a == 3 {
20441                let b = 2;
20442            } else {
20443                let c = 3;
20444            }
20445        }"
20446        .unindent(),
20447        cx,
20448    )
20449    .await;
20450
20451    assert_indent_guides(
20452        0..11,
20453        vec![
20454            indent_guide(buffer_id, 1, 9, 0),
20455            indent_guide(buffer_id, 6, 6, 1),
20456            indent_guide(buffer_id, 8, 8, 1),
20457        ],
20458        None,
20459        &mut cx,
20460    );
20461}
20462
20463#[gpui::test]
20464async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20465    let (buffer_id, mut cx) = setup_indent_guides_editor(
20466        &"
20467        fn main() {
20468            let a = 1;
20469
20470            let c = 3;
20471
20472            if a == 3 {
20473                let b = 2;
20474            } else {
20475                let c = 3;
20476            }
20477        }"
20478        .unindent(),
20479        cx,
20480    )
20481    .await;
20482
20483    assert_indent_guides(
20484        1..11,
20485        vec![
20486            indent_guide(buffer_id, 1, 9, 0),
20487            indent_guide(buffer_id, 6, 6, 1),
20488            indent_guide(buffer_id, 8, 8, 1),
20489        ],
20490        None,
20491        &mut cx,
20492    );
20493}
20494
20495#[gpui::test]
20496async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499        fn main() {
20500            let a = 1;
20501
20502            let c = 3;
20503
20504            if a == 3 {
20505                let b = 2;
20506            } else {
20507                let c = 3;
20508            }
20509        }"
20510        .unindent(),
20511        cx,
20512    )
20513    .await;
20514
20515    assert_indent_guides(
20516        1..10,
20517        vec![
20518            indent_guide(buffer_id, 1, 9, 0),
20519            indent_guide(buffer_id, 6, 6, 1),
20520            indent_guide(buffer_id, 8, 8, 1),
20521        ],
20522        None,
20523        &mut cx,
20524    );
20525}
20526
20527#[gpui::test]
20528async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20529    let (buffer_id, mut cx) = setup_indent_guides_editor(
20530        &"
20531        fn main() {
20532            if a {
20533                b(
20534                    c,
20535                    d,
20536                )
20537            } else {
20538                e(
20539                    f
20540                )
20541            }
20542        }"
20543        .unindent(),
20544        cx,
20545    )
20546    .await;
20547
20548    assert_indent_guides(
20549        0..11,
20550        vec![
20551            indent_guide(buffer_id, 1, 10, 0),
20552            indent_guide(buffer_id, 2, 5, 1),
20553            indent_guide(buffer_id, 7, 9, 1),
20554            indent_guide(buffer_id, 3, 4, 2),
20555            indent_guide(buffer_id, 8, 8, 2),
20556        ],
20557        None,
20558        &mut cx,
20559    );
20560
20561    cx.update_editor(|editor, window, cx| {
20562        editor.fold_at(MultiBufferRow(2), window, cx);
20563        assert_eq!(
20564            editor.display_text(cx),
20565            "
20566            fn main() {
20567                if a {
20568                    b(⋯
20569                    )
20570                } else {
20571                    e(
20572                        f
20573                    )
20574                }
20575            }"
20576            .unindent()
20577        );
20578    });
20579
20580    assert_indent_guides(
20581        0..11,
20582        vec![
20583            indent_guide(buffer_id, 1, 10, 0),
20584            indent_guide(buffer_id, 2, 5, 1),
20585            indent_guide(buffer_id, 7, 9, 1),
20586            indent_guide(buffer_id, 8, 8, 2),
20587        ],
20588        None,
20589        &mut cx,
20590    );
20591}
20592
20593#[gpui::test]
20594async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20595    let (buffer_id, mut cx) = setup_indent_guides_editor(
20596        &"
20597        block1
20598            block2
20599                block3
20600                    block4
20601            block2
20602        block1
20603        block1"
20604            .unindent(),
20605        cx,
20606    )
20607    .await;
20608
20609    assert_indent_guides(
20610        1..10,
20611        vec![
20612            indent_guide(buffer_id, 1, 4, 0),
20613            indent_guide(buffer_id, 2, 3, 1),
20614            indent_guide(buffer_id, 3, 3, 2),
20615        ],
20616        None,
20617        &mut cx,
20618    );
20619}
20620
20621#[gpui::test]
20622async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20623    let (buffer_id, mut cx) = setup_indent_guides_editor(
20624        &"
20625        block1
20626            block2
20627                block3
20628
20629        block1
20630        block1"
20631            .unindent(),
20632        cx,
20633    )
20634    .await;
20635
20636    assert_indent_guides(
20637        0..6,
20638        vec![
20639            indent_guide(buffer_id, 1, 2, 0),
20640            indent_guide(buffer_id, 2, 2, 1),
20641        ],
20642        None,
20643        &mut cx,
20644    );
20645}
20646
20647#[gpui::test]
20648async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20649    let (buffer_id, mut cx) = setup_indent_guides_editor(
20650        &"
20651        function component() {
20652        \treturn (
20653        \t\t\t
20654        \t\t<div>
20655        \t\t\t<abc></abc>
20656        \t\t</div>
20657        \t)
20658        }"
20659        .unindent(),
20660        cx,
20661    )
20662    .await;
20663
20664    assert_indent_guides(
20665        0..8,
20666        vec![
20667            indent_guide(buffer_id, 1, 6, 0),
20668            indent_guide(buffer_id, 2, 5, 1),
20669            indent_guide(buffer_id, 4, 4, 2),
20670        ],
20671        None,
20672        &mut cx,
20673    );
20674}
20675
20676#[gpui::test]
20677async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20678    let (buffer_id, mut cx) = setup_indent_guides_editor(
20679        &"
20680        function component() {
20681        \treturn (
20682        \t
20683        \t\t<div>
20684        \t\t\t<abc></abc>
20685        \t\t</div>
20686        \t)
20687        }"
20688        .unindent(),
20689        cx,
20690    )
20691    .await;
20692
20693    assert_indent_guides(
20694        0..8,
20695        vec![
20696            indent_guide(buffer_id, 1, 6, 0),
20697            indent_guide(buffer_id, 2, 5, 1),
20698            indent_guide(buffer_id, 4, 4, 2),
20699        ],
20700        None,
20701        &mut cx,
20702    );
20703}
20704
20705#[gpui::test]
20706async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20707    let (buffer_id, mut cx) = setup_indent_guides_editor(
20708        &"
20709        block1
20710
20711
20712
20713            block2
20714        "
20715        .unindent(),
20716        cx,
20717    )
20718    .await;
20719
20720    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20721}
20722
20723#[gpui::test]
20724async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20725    let (buffer_id, mut cx) = setup_indent_guides_editor(
20726        &"
20727        def a:
20728        \tb = 3
20729        \tif True:
20730        \t\tc = 4
20731        \t\td = 5
20732        \tprint(b)
20733        "
20734        .unindent(),
20735        cx,
20736    )
20737    .await;
20738
20739    assert_indent_guides(
20740        0..6,
20741        vec![
20742            indent_guide(buffer_id, 1, 5, 0),
20743            indent_guide(buffer_id, 3, 4, 1),
20744        ],
20745        None,
20746        &mut cx,
20747    );
20748}
20749
20750#[gpui::test]
20751async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20752    let (buffer_id, mut cx) = setup_indent_guides_editor(
20753        &"
20754    fn main() {
20755        let a = 1;
20756    }"
20757        .unindent(),
20758        cx,
20759    )
20760    .await;
20761
20762    cx.update_editor(|editor, window, cx| {
20763        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20764            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20765        });
20766    });
20767
20768    assert_indent_guides(
20769        0..3,
20770        vec![indent_guide(buffer_id, 1, 1, 0)],
20771        Some(vec![0]),
20772        &mut cx,
20773    );
20774}
20775
20776#[gpui::test]
20777async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20778    let (buffer_id, mut cx) = setup_indent_guides_editor(
20779        &"
20780    fn main() {
20781        if 1 == 2 {
20782            let a = 1;
20783        }
20784    }"
20785        .unindent(),
20786        cx,
20787    )
20788    .await;
20789
20790    cx.update_editor(|editor, window, cx| {
20791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20792            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20793        });
20794    });
20795
20796    assert_indent_guides(
20797        0..4,
20798        vec![
20799            indent_guide(buffer_id, 1, 3, 0),
20800            indent_guide(buffer_id, 2, 2, 1),
20801        ],
20802        Some(vec![1]),
20803        &mut cx,
20804    );
20805
20806    cx.update_editor(|editor, window, cx| {
20807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20808            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20809        });
20810    });
20811
20812    assert_indent_guides(
20813        0..4,
20814        vec![
20815            indent_guide(buffer_id, 1, 3, 0),
20816            indent_guide(buffer_id, 2, 2, 1),
20817        ],
20818        Some(vec![1]),
20819        &mut cx,
20820    );
20821
20822    cx.update_editor(|editor, window, cx| {
20823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20824            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20825        });
20826    });
20827
20828    assert_indent_guides(
20829        0..4,
20830        vec![
20831            indent_guide(buffer_id, 1, 3, 0),
20832            indent_guide(buffer_id, 2, 2, 1),
20833        ],
20834        Some(vec![0]),
20835        &mut cx,
20836    );
20837}
20838
20839#[gpui::test]
20840async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20841    let (buffer_id, mut cx) = setup_indent_guides_editor(
20842        &"
20843    fn main() {
20844        let a = 1;
20845
20846        let b = 2;
20847    }"
20848        .unindent(),
20849        cx,
20850    )
20851    .await;
20852
20853    cx.update_editor(|editor, window, cx| {
20854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20855            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20856        });
20857    });
20858
20859    assert_indent_guides(
20860        0..5,
20861        vec![indent_guide(buffer_id, 1, 3, 0)],
20862        Some(vec![0]),
20863        &mut cx,
20864    );
20865}
20866
20867#[gpui::test]
20868async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20869    let (buffer_id, mut cx) = setup_indent_guides_editor(
20870        &"
20871    def m:
20872        a = 1
20873        pass"
20874            .unindent(),
20875        cx,
20876    )
20877    .await;
20878
20879    cx.update_editor(|editor, window, cx| {
20880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20881            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20882        });
20883    });
20884
20885    assert_indent_guides(
20886        0..3,
20887        vec![indent_guide(buffer_id, 1, 2, 0)],
20888        Some(vec![0]),
20889        &mut cx,
20890    );
20891}
20892
20893#[gpui::test]
20894async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20895    init_test(cx, |_| {});
20896    let mut cx = EditorTestContext::new(cx).await;
20897    let text = indoc! {
20898        "
20899        impl A {
20900            fn b() {
20901                0;
20902                3;
20903                5;
20904                6;
20905                7;
20906            }
20907        }
20908        "
20909    };
20910    let base_text = indoc! {
20911        "
20912        impl A {
20913            fn b() {
20914                0;
20915                1;
20916                2;
20917                3;
20918                4;
20919            }
20920            fn c() {
20921                5;
20922                6;
20923                7;
20924            }
20925        }
20926        "
20927    };
20928
20929    cx.update_editor(|editor, window, cx| {
20930        editor.set_text(text, window, cx);
20931
20932        editor.buffer().update(cx, |multibuffer, cx| {
20933            let buffer = multibuffer.as_singleton().unwrap();
20934            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20935
20936            multibuffer.set_all_diff_hunks_expanded(cx);
20937            multibuffer.add_diff(diff, cx);
20938
20939            buffer.read(cx).remote_id()
20940        })
20941    });
20942    cx.run_until_parked();
20943
20944    cx.assert_state_with_diff(
20945        indoc! { "
20946          impl A {
20947              fn b() {
20948                  0;
20949        -         1;
20950        -         2;
20951                  3;
20952        -         4;
20953        -     }
20954        -     fn c() {
20955                  5;
20956                  6;
20957                  7;
20958              }
20959          }
20960          ˇ"
20961        }
20962        .to_string(),
20963    );
20964
20965    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20966        editor
20967            .snapshot(window, cx)
20968            .buffer_snapshot()
20969            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20970            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20971            .collect::<Vec<_>>()
20972    });
20973    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20974    assert_eq!(
20975        actual_guides,
20976        vec![
20977            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20978            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20979            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20980        ]
20981    );
20982}
20983
20984#[gpui::test]
20985async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20986    init_test(cx, |_| {});
20987    let mut cx = EditorTestContext::new(cx).await;
20988
20989    let diff_base = r#"
20990        a
20991        b
20992        c
20993        "#
20994    .unindent();
20995
20996    cx.set_state(
20997        &r#"
20998        ˇA
20999        b
21000        C
21001        "#
21002        .unindent(),
21003    );
21004    cx.set_head_text(&diff_base);
21005    cx.update_editor(|editor, window, cx| {
21006        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21007    });
21008    executor.run_until_parked();
21009
21010    let both_hunks_expanded = r#"
21011        - a
21012        + ˇA
21013          b
21014        - c
21015        + C
21016        "#
21017    .unindent();
21018
21019    cx.assert_state_with_diff(both_hunks_expanded.clone());
21020
21021    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21022        let snapshot = editor.snapshot(window, cx);
21023        let hunks = editor
21024            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21025            .collect::<Vec<_>>();
21026        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21027        let buffer_id = hunks[0].buffer_id;
21028        hunks
21029            .into_iter()
21030            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21031            .collect::<Vec<_>>()
21032    });
21033    assert_eq!(hunk_ranges.len(), 2);
21034
21035    cx.update_editor(|editor, _, cx| {
21036        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21037    });
21038    executor.run_until_parked();
21039
21040    let second_hunk_expanded = r#"
21041          ˇA
21042          b
21043        - c
21044        + C
21045        "#
21046    .unindent();
21047
21048    cx.assert_state_with_diff(second_hunk_expanded);
21049
21050    cx.update_editor(|editor, _, cx| {
21051        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21052    });
21053    executor.run_until_parked();
21054
21055    cx.assert_state_with_diff(both_hunks_expanded.clone());
21056
21057    cx.update_editor(|editor, _, cx| {
21058        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21059    });
21060    executor.run_until_parked();
21061
21062    let first_hunk_expanded = r#"
21063        - a
21064        + ˇA
21065          b
21066          C
21067        "#
21068    .unindent();
21069
21070    cx.assert_state_with_diff(first_hunk_expanded);
21071
21072    cx.update_editor(|editor, _, cx| {
21073        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21074    });
21075    executor.run_until_parked();
21076
21077    cx.assert_state_with_diff(both_hunks_expanded);
21078
21079    cx.set_state(
21080        &r#"
21081        ˇA
21082        b
21083        "#
21084        .unindent(),
21085    );
21086    cx.run_until_parked();
21087
21088    // TODO this cursor position seems bad
21089    cx.assert_state_with_diff(
21090        r#"
21091        - ˇa
21092        + A
21093          b
21094        "#
21095        .unindent(),
21096    );
21097
21098    cx.update_editor(|editor, window, cx| {
21099        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21100    });
21101
21102    cx.assert_state_with_diff(
21103        r#"
21104            - ˇa
21105            + A
21106              b
21107            - c
21108            "#
21109        .unindent(),
21110    );
21111
21112    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21113        let snapshot = editor.snapshot(window, cx);
21114        let hunks = editor
21115            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21116            .collect::<Vec<_>>();
21117        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21118        let buffer_id = hunks[0].buffer_id;
21119        hunks
21120            .into_iter()
21121            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21122            .collect::<Vec<_>>()
21123    });
21124    assert_eq!(hunk_ranges.len(), 2);
21125
21126    cx.update_editor(|editor, _, cx| {
21127        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21128    });
21129    executor.run_until_parked();
21130
21131    cx.assert_state_with_diff(
21132        r#"
21133        - ˇa
21134        + A
21135          b
21136        "#
21137        .unindent(),
21138    );
21139}
21140
21141#[gpui::test]
21142async fn test_toggle_deletion_hunk_at_start_of_file(
21143    executor: BackgroundExecutor,
21144    cx: &mut TestAppContext,
21145) {
21146    init_test(cx, |_| {});
21147    let mut cx = EditorTestContext::new(cx).await;
21148
21149    let diff_base = r#"
21150        a
21151        b
21152        c
21153        "#
21154    .unindent();
21155
21156    cx.set_state(
21157        &r#"
21158        ˇb
21159        c
21160        "#
21161        .unindent(),
21162    );
21163    cx.set_head_text(&diff_base);
21164    cx.update_editor(|editor, window, cx| {
21165        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21166    });
21167    executor.run_until_parked();
21168
21169    let hunk_expanded = r#"
21170        - a
21171          ˇb
21172          c
21173        "#
21174    .unindent();
21175
21176    cx.assert_state_with_diff(hunk_expanded.clone());
21177
21178    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21179        let snapshot = editor.snapshot(window, cx);
21180        let hunks = editor
21181            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21182            .collect::<Vec<_>>();
21183        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21184        let buffer_id = hunks[0].buffer_id;
21185        hunks
21186            .into_iter()
21187            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21188            .collect::<Vec<_>>()
21189    });
21190    assert_eq!(hunk_ranges.len(), 1);
21191
21192    cx.update_editor(|editor, _, cx| {
21193        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21194    });
21195    executor.run_until_parked();
21196
21197    let hunk_collapsed = r#"
21198          ˇb
21199          c
21200        "#
21201    .unindent();
21202
21203    cx.assert_state_with_diff(hunk_collapsed);
21204
21205    cx.update_editor(|editor, _, cx| {
21206        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21207    });
21208    executor.run_until_parked();
21209
21210    cx.assert_state_with_diff(hunk_expanded);
21211}
21212
21213#[gpui::test]
21214async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21215    init_test(cx, |_| {});
21216
21217    let fs = FakeFs::new(cx.executor());
21218    fs.insert_tree(
21219        path!("/test"),
21220        json!({
21221            ".git": {},
21222            "file-1": "ONE\n",
21223            "file-2": "TWO\n",
21224            "file-3": "THREE\n",
21225        }),
21226    )
21227    .await;
21228
21229    fs.set_head_for_repo(
21230        path!("/test/.git").as_ref(),
21231        &[
21232            ("file-1", "one\n".into()),
21233            ("file-2", "two\n".into()),
21234            ("file-3", "three\n".into()),
21235        ],
21236        "deadbeef",
21237    );
21238
21239    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21240    let mut buffers = vec![];
21241    for i in 1..=3 {
21242        let buffer = project
21243            .update(cx, |project, cx| {
21244                let path = format!(path!("/test/file-{}"), i);
21245                project.open_local_buffer(path, cx)
21246            })
21247            .await
21248            .unwrap();
21249        buffers.push(buffer);
21250    }
21251
21252    let multibuffer = cx.new(|cx| {
21253        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21254        multibuffer.set_all_diff_hunks_expanded(cx);
21255        for buffer in &buffers {
21256            let snapshot = buffer.read(cx).snapshot();
21257            multibuffer.set_excerpts_for_path(
21258                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21259                buffer.clone(),
21260                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21261                2,
21262                cx,
21263            );
21264        }
21265        multibuffer
21266    });
21267
21268    let editor = cx.add_window(|window, cx| {
21269        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21270    });
21271    cx.run_until_parked();
21272
21273    let snapshot = editor
21274        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21275        .unwrap();
21276    let hunks = snapshot
21277        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21278        .map(|hunk| match hunk {
21279            DisplayDiffHunk::Unfolded {
21280                display_row_range, ..
21281            } => display_row_range,
21282            DisplayDiffHunk::Folded { .. } => unreachable!(),
21283        })
21284        .collect::<Vec<_>>();
21285    assert_eq!(
21286        hunks,
21287        [
21288            DisplayRow(2)..DisplayRow(4),
21289            DisplayRow(7)..DisplayRow(9),
21290            DisplayRow(12)..DisplayRow(14),
21291        ]
21292    );
21293}
21294
21295#[gpui::test]
21296async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21297    init_test(cx, |_| {});
21298
21299    let mut cx = EditorTestContext::new(cx).await;
21300    cx.set_head_text(indoc! { "
21301        one
21302        two
21303        three
21304        four
21305        five
21306        "
21307    });
21308    cx.set_index_text(indoc! { "
21309        one
21310        two
21311        three
21312        four
21313        five
21314        "
21315    });
21316    cx.set_state(indoc! {"
21317        one
21318        TWO
21319        ˇTHREE
21320        FOUR
21321        five
21322    "});
21323    cx.run_until_parked();
21324    cx.update_editor(|editor, window, cx| {
21325        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21326    });
21327    cx.run_until_parked();
21328    cx.assert_index_text(Some(indoc! {"
21329        one
21330        TWO
21331        THREE
21332        FOUR
21333        five
21334    "}));
21335    cx.set_state(indoc! { "
21336        one
21337        TWO
21338        ˇTHREE-HUNDRED
21339        FOUR
21340        five
21341    "});
21342    cx.run_until_parked();
21343    cx.update_editor(|editor, window, cx| {
21344        let snapshot = editor.snapshot(window, cx);
21345        let hunks = editor
21346            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21347            .collect::<Vec<_>>();
21348        assert_eq!(hunks.len(), 1);
21349        assert_eq!(
21350            hunks[0].status(),
21351            DiffHunkStatus {
21352                kind: DiffHunkStatusKind::Modified,
21353                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21354            }
21355        );
21356
21357        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21358    });
21359    cx.run_until_parked();
21360    cx.assert_index_text(Some(indoc! {"
21361        one
21362        TWO
21363        THREE-HUNDRED
21364        FOUR
21365        five
21366    "}));
21367}
21368
21369#[gpui::test]
21370fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21371    init_test(cx, |_| {});
21372
21373    let editor = cx.add_window(|window, cx| {
21374        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21375        build_editor(buffer, window, cx)
21376    });
21377
21378    let render_args = Arc::new(Mutex::new(None));
21379    let snapshot = editor
21380        .update(cx, |editor, window, cx| {
21381            let snapshot = editor.buffer().read(cx).snapshot(cx);
21382            let range =
21383                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21384
21385            struct RenderArgs {
21386                row: MultiBufferRow,
21387                folded: bool,
21388                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21389            }
21390
21391            let crease = Crease::inline(
21392                range,
21393                FoldPlaceholder::test(),
21394                {
21395                    let toggle_callback = render_args.clone();
21396                    move |row, folded, callback, _window, _cx| {
21397                        *toggle_callback.lock() = Some(RenderArgs {
21398                            row,
21399                            folded,
21400                            callback,
21401                        });
21402                        div()
21403                    }
21404                },
21405                |_row, _folded, _window, _cx| div(),
21406            );
21407
21408            editor.insert_creases(Some(crease), cx);
21409            let snapshot = editor.snapshot(window, cx);
21410            let _div =
21411                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21412            snapshot
21413        })
21414        .unwrap();
21415
21416    let render_args = render_args.lock().take().unwrap();
21417    assert_eq!(render_args.row, MultiBufferRow(1));
21418    assert!(!render_args.folded);
21419    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21420
21421    cx.update_window(*editor, |_, window, cx| {
21422        (render_args.callback)(true, window, cx)
21423    })
21424    .unwrap();
21425    let snapshot = editor
21426        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21427        .unwrap();
21428    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21429
21430    cx.update_window(*editor, |_, window, cx| {
21431        (render_args.callback)(false, window, cx)
21432    })
21433    .unwrap();
21434    let snapshot = editor
21435        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21436        .unwrap();
21437    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21438}
21439
21440#[gpui::test]
21441async fn test_input_text(cx: &mut TestAppContext) {
21442    init_test(cx, |_| {});
21443    let mut cx = EditorTestContext::new(cx).await;
21444
21445    cx.set_state(
21446        &r#"ˇone
21447        two
21448
21449        three
21450        fourˇ
21451        five
21452
21453        siˇx"#
21454            .unindent(),
21455    );
21456
21457    cx.dispatch_action(HandleInput(String::new()));
21458    cx.assert_editor_state(
21459        &r#"ˇone
21460        two
21461
21462        three
21463        fourˇ
21464        five
21465
21466        siˇx"#
21467            .unindent(),
21468    );
21469
21470    cx.dispatch_action(HandleInput("AAAA".to_string()));
21471    cx.assert_editor_state(
21472        &r#"AAAAˇone
21473        two
21474
21475        three
21476        fourAAAAˇ
21477        five
21478
21479        siAAAAˇx"#
21480            .unindent(),
21481    );
21482}
21483
21484#[gpui::test]
21485async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21486    init_test(cx, |_| {});
21487
21488    let mut cx = EditorTestContext::new(cx).await;
21489    cx.set_state(
21490        r#"let foo = 1;
21491let foo = 2;
21492let foo = 3;
21493let fooˇ = 4;
21494let foo = 5;
21495let foo = 6;
21496let foo = 7;
21497let foo = 8;
21498let foo = 9;
21499let foo = 10;
21500let foo = 11;
21501let foo = 12;
21502let foo = 13;
21503let foo = 14;
21504let foo = 15;"#,
21505    );
21506
21507    cx.update_editor(|e, window, cx| {
21508        assert_eq!(
21509            e.next_scroll_position,
21510            NextScrollCursorCenterTopBottom::Center,
21511            "Default next scroll direction is center",
21512        );
21513
21514        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21515        assert_eq!(
21516            e.next_scroll_position,
21517            NextScrollCursorCenterTopBottom::Top,
21518            "After center, next scroll direction should be top",
21519        );
21520
21521        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21522        assert_eq!(
21523            e.next_scroll_position,
21524            NextScrollCursorCenterTopBottom::Bottom,
21525            "After top, next scroll direction should be bottom",
21526        );
21527
21528        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21529        assert_eq!(
21530            e.next_scroll_position,
21531            NextScrollCursorCenterTopBottom::Center,
21532            "After bottom, scrolling should start over",
21533        );
21534
21535        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21536        assert_eq!(
21537            e.next_scroll_position,
21538            NextScrollCursorCenterTopBottom::Top,
21539            "Scrolling continues if retriggered fast enough"
21540        );
21541    });
21542
21543    cx.executor()
21544        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21545    cx.executor().run_until_parked();
21546    cx.update_editor(|e, _, _| {
21547        assert_eq!(
21548            e.next_scroll_position,
21549            NextScrollCursorCenterTopBottom::Center,
21550            "If scrolling is not triggered fast enough, it should reset"
21551        );
21552    });
21553}
21554
21555#[gpui::test]
21556async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21557    init_test(cx, |_| {});
21558    let mut cx = EditorLspTestContext::new_rust(
21559        lsp::ServerCapabilities {
21560            definition_provider: Some(lsp::OneOf::Left(true)),
21561            references_provider: Some(lsp::OneOf::Left(true)),
21562            ..lsp::ServerCapabilities::default()
21563        },
21564        cx,
21565    )
21566    .await;
21567
21568    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21569        let go_to_definition = cx
21570            .lsp
21571            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21572                move |params, _| async move {
21573                    if empty_go_to_definition {
21574                        Ok(None)
21575                    } else {
21576                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21577                            uri: params.text_document_position_params.text_document.uri,
21578                            range: lsp::Range::new(
21579                                lsp::Position::new(4, 3),
21580                                lsp::Position::new(4, 6),
21581                            ),
21582                        })))
21583                    }
21584                },
21585            );
21586        let references = cx
21587            .lsp
21588            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21589                Ok(Some(vec![lsp::Location {
21590                    uri: params.text_document_position.text_document.uri,
21591                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21592                }]))
21593            });
21594        (go_to_definition, references)
21595    };
21596
21597    cx.set_state(
21598        &r#"fn one() {
21599            let mut a = ˇtwo();
21600        }
21601
21602        fn two() {}"#
21603            .unindent(),
21604    );
21605    set_up_lsp_handlers(false, &mut cx);
21606    let navigated = cx
21607        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21608        .await
21609        .expect("Failed to navigate to definition");
21610    assert_eq!(
21611        navigated,
21612        Navigated::Yes,
21613        "Should have navigated to definition from the GetDefinition response"
21614    );
21615    cx.assert_editor_state(
21616        &r#"fn one() {
21617            let mut a = two();
21618        }
21619
21620        fn «twoˇ»() {}"#
21621            .unindent(),
21622    );
21623
21624    let editors = cx.update_workspace(|workspace, _, cx| {
21625        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21626    });
21627    cx.update_editor(|_, _, test_editor_cx| {
21628        assert_eq!(
21629            editors.len(),
21630            1,
21631            "Initially, only one, test, editor should be open in the workspace"
21632        );
21633        assert_eq!(
21634            test_editor_cx.entity(),
21635            editors.last().expect("Asserted len is 1").clone()
21636        );
21637    });
21638
21639    set_up_lsp_handlers(true, &mut cx);
21640    let navigated = cx
21641        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21642        .await
21643        .expect("Failed to navigate to lookup references");
21644    assert_eq!(
21645        navigated,
21646        Navigated::Yes,
21647        "Should have navigated to references as a fallback after empty GoToDefinition response"
21648    );
21649    // We should not change the selections in the existing file,
21650    // if opening another milti buffer with the references
21651    cx.assert_editor_state(
21652        &r#"fn one() {
21653            let mut a = two();
21654        }
21655
21656        fn «twoˇ»() {}"#
21657            .unindent(),
21658    );
21659    let editors = cx.update_workspace(|workspace, _, cx| {
21660        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21661    });
21662    cx.update_editor(|_, _, test_editor_cx| {
21663        assert_eq!(
21664            editors.len(),
21665            2,
21666            "After falling back to references search, we open a new editor with the results"
21667        );
21668        let references_fallback_text = editors
21669            .into_iter()
21670            .find(|new_editor| *new_editor != test_editor_cx.entity())
21671            .expect("Should have one non-test editor now")
21672            .read(test_editor_cx)
21673            .text(test_editor_cx);
21674        assert_eq!(
21675            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21676            "Should use the range from the references response and not the GoToDefinition one"
21677        );
21678    });
21679}
21680
21681#[gpui::test]
21682async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21683    init_test(cx, |_| {});
21684    cx.update(|cx| {
21685        let mut editor_settings = EditorSettings::get_global(cx).clone();
21686        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21687        EditorSettings::override_global(editor_settings, cx);
21688    });
21689    let mut cx = EditorLspTestContext::new_rust(
21690        lsp::ServerCapabilities {
21691            definition_provider: Some(lsp::OneOf::Left(true)),
21692            references_provider: Some(lsp::OneOf::Left(true)),
21693            ..lsp::ServerCapabilities::default()
21694        },
21695        cx,
21696    )
21697    .await;
21698    let original_state = r#"fn one() {
21699        let mut a = ˇtwo();
21700    }
21701
21702    fn two() {}"#
21703        .unindent();
21704    cx.set_state(&original_state);
21705
21706    let mut go_to_definition = cx
21707        .lsp
21708        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21709            move |_, _| async move { Ok(None) },
21710        );
21711    let _references = cx
21712        .lsp
21713        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21714            panic!("Should not call for references with no go to definition fallback")
21715        });
21716
21717    let navigated = cx
21718        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21719        .await
21720        .expect("Failed to navigate to lookup references");
21721    go_to_definition
21722        .next()
21723        .await
21724        .expect("Should have called the go_to_definition handler");
21725
21726    assert_eq!(
21727        navigated,
21728        Navigated::No,
21729        "Should have navigated to references as a fallback after empty GoToDefinition response"
21730    );
21731    cx.assert_editor_state(&original_state);
21732    let editors = cx.update_workspace(|workspace, _, cx| {
21733        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21734    });
21735    cx.update_editor(|_, _, _| {
21736        assert_eq!(
21737            editors.len(),
21738            1,
21739            "After unsuccessful fallback, no other editor should have been opened"
21740        );
21741    });
21742}
21743
21744#[gpui::test]
21745async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21746    init_test(cx, |_| {});
21747    let mut cx = EditorLspTestContext::new_rust(
21748        lsp::ServerCapabilities {
21749            references_provider: Some(lsp::OneOf::Left(true)),
21750            ..lsp::ServerCapabilities::default()
21751        },
21752        cx,
21753    )
21754    .await;
21755
21756    cx.set_state(
21757        &r#"
21758        fn one() {
21759            let mut a = two();
21760        }
21761
21762        fn ˇtwo() {}"#
21763            .unindent(),
21764    );
21765    cx.lsp
21766        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21767            Ok(Some(vec![
21768                lsp::Location {
21769                    uri: params.text_document_position.text_document.uri.clone(),
21770                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21771                },
21772                lsp::Location {
21773                    uri: params.text_document_position.text_document.uri,
21774                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21775                },
21776            ]))
21777        });
21778    let navigated = cx
21779        .update_editor(|editor, window, cx| {
21780            editor.find_all_references(&FindAllReferences, window, cx)
21781        })
21782        .unwrap()
21783        .await
21784        .expect("Failed to navigate to references");
21785    assert_eq!(
21786        navigated,
21787        Navigated::Yes,
21788        "Should have navigated to references from the FindAllReferences response"
21789    );
21790    cx.assert_editor_state(
21791        &r#"fn one() {
21792            let mut a = two();
21793        }
21794
21795        fn ˇtwo() {}"#
21796            .unindent(),
21797    );
21798
21799    let editors = cx.update_workspace(|workspace, _, cx| {
21800        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21801    });
21802    cx.update_editor(|_, _, _| {
21803        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21804    });
21805
21806    cx.set_state(
21807        &r#"fn one() {
21808            let mut a = ˇtwo();
21809        }
21810
21811        fn two() {}"#
21812            .unindent(),
21813    );
21814    let navigated = cx
21815        .update_editor(|editor, window, cx| {
21816            editor.find_all_references(&FindAllReferences, window, cx)
21817        })
21818        .unwrap()
21819        .await
21820        .expect("Failed to navigate to references");
21821    assert_eq!(
21822        navigated,
21823        Navigated::Yes,
21824        "Should have navigated to references from the FindAllReferences response"
21825    );
21826    cx.assert_editor_state(
21827        &r#"fn one() {
21828            let mut a = ˇtwo();
21829        }
21830
21831        fn two() {}"#
21832            .unindent(),
21833    );
21834    let editors = cx.update_workspace(|workspace, _, cx| {
21835        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21836    });
21837    cx.update_editor(|_, _, _| {
21838        assert_eq!(
21839            editors.len(),
21840            2,
21841            "should have re-used the previous multibuffer"
21842        );
21843    });
21844
21845    cx.set_state(
21846        &r#"fn one() {
21847            let mut a = ˇtwo();
21848        }
21849        fn three() {}
21850        fn two() {}"#
21851            .unindent(),
21852    );
21853    cx.lsp
21854        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21855            Ok(Some(vec![
21856                lsp::Location {
21857                    uri: params.text_document_position.text_document.uri.clone(),
21858                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21859                },
21860                lsp::Location {
21861                    uri: params.text_document_position.text_document.uri,
21862                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21863                },
21864            ]))
21865        });
21866    let navigated = cx
21867        .update_editor(|editor, window, cx| {
21868            editor.find_all_references(&FindAllReferences, window, cx)
21869        })
21870        .unwrap()
21871        .await
21872        .expect("Failed to navigate to references");
21873    assert_eq!(
21874        navigated,
21875        Navigated::Yes,
21876        "Should have navigated to references from the FindAllReferences response"
21877    );
21878    cx.assert_editor_state(
21879        &r#"fn one() {
21880                let mut a = ˇtwo();
21881            }
21882            fn three() {}
21883            fn two() {}"#
21884            .unindent(),
21885    );
21886    let editors = cx.update_workspace(|workspace, _, cx| {
21887        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21888    });
21889    cx.update_editor(|_, _, _| {
21890        assert_eq!(
21891            editors.len(),
21892            3,
21893            "should have used a new multibuffer as offsets changed"
21894        );
21895    });
21896}
21897#[gpui::test]
21898async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21899    init_test(cx, |_| {});
21900
21901    let language = Arc::new(Language::new(
21902        LanguageConfig::default(),
21903        Some(tree_sitter_rust::LANGUAGE.into()),
21904    ));
21905
21906    let text = r#"
21907        #[cfg(test)]
21908        mod tests() {
21909            #[test]
21910            fn runnable_1() {
21911                let a = 1;
21912            }
21913
21914            #[test]
21915            fn runnable_2() {
21916                let a = 1;
21917                let b = 2;
21918            }
21919        }
21920    "#
21921    .unindent();
21922
21923    let fs = FakeFs::new(cx.executor());
21924    fs.insert_file("/file.rs", Default::default()).await;
21925
21926    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21927    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21928    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21929    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21930    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21931
21932    let editor = cx.new_window_entity(|window, cx| {
21933        Editor::new(
21934            EditorMode::full(),
21935            multi_buffer,
21936            Some(project.clone()),
21937            window,
21938            cx,
21939        )
21940    });
21941
21942    editor.update_in(cx, |editor, window, cx| {
21943        let snapshot = editor.buffer().read(cx).snapshot(cx);
21944        editor.tasks.insert(
21945            (buffer.read(cx).remote_id(), 3),
21946            RunnableTasks {
21947                templates: vec![],
21948                offset: snapshot.anchor_before(43),
21949                column: 0,
21950                extra_variables: HashMap::default(),
21951                context_range: BufferOffset(43)..BufferOffset(85),
21952            },
21953        );
21954        editor.tasks.insert(
21955            (buffer.read(cx).remote_id(), 8),
21956            RunnableTasks {
21957                templates: vec![],
21958                offset: snapshot.anchor_before(86),
21959                column: 0,
21960                extra_variables: HashMap::default(),
21961                context_range: BufferOffset(86)..BufferOffset(191),
21962            },
21963        );
21964
21965        // Test finding task when cursor is inside function body
21966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21967            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21968        });
21969        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21970        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21971
21972        // Test finding task when cursor is on function name
21973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21974            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21975        });
21976        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21977        assert_eq!(row, 8, "Should find task when cursor is on function name");
21978    });
21979}
21980
21981#[gpui::test]
21982async fn test_folding_buffers(cx: &mut TestAppContext) {
21983    init_test(cx, |_| {});
21984
21985    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21986    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21987    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21988
21989    let fs = FakeFs::new(cx.executor());
21990    fs.insert_tree(
21991        path!("/a"),
21992        json!({
21993            "first.rs": sample_text_1,
21994            "second.rs": sample_text_2,
21995            "third.rs": sample_text_3,
21996        }),
21997    )
21998    .await;
21999    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22000    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22001    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22002    let worktree = project.update(cx, |project, cx| {
22003        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22004        assert_eq!(worktrees.len(), 1);
22005        worktrees.pop().unwrap()
22006    });
22007    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22008
22009    let buffer_1 = project
22010        .update(cx, |project, cx| {
22011            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22012        })
22013        .await
22014        .unwrap();
22015    let buffer_2 = project
22016        .update(cx, |project, cx| {
22017            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22018        })
22019        .await
22020        .unwrap();
22021    let buffer_3 = project
22022        .update(cx, |project, cx| {
22023            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22024        })
22025        .await
22026        .unwrap();
22027
22028    let multi_buffer = cx.new(|cx| {
22029        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22030        multi_buffer.push_excerpts(
22031            buffer_1.clone(),
22032            [
22033                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22034                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22035                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22036            ],
22037            cx,
22038        );
22039        multi_buffer.push_excerpts(
22040            buffer_2.clone(),
22041            [
22042                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22043                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22044                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22045            ],
22046            cx,
22047        );
22048        multi_buffer.push_excerpts(
22049            buffer_3.clone(),
22050            [
22051                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22052                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22053                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22054            ],
22055            cx,
22056        );
22057        multi_buffer
22058    });
22059    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22060        Editor::new(
22061            EditorMode::full(),
22062            multi_buffer.clone(),
22063            Some(project.clone()),
22064            window,
22065            cx,
22066        )
22067    });
22068
22069    assert_eq!(
22070        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22071        "\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",
22072    );
22073
22074    multi_buffer_editor.update(cx, |editor, cx| {
22075        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22076    });
22077    assert_eq!(
22078        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22079        "\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",
22080        "After folding the first buffer, its text should not be displayed"
22081    );
22082
22083    multi_buffer_editor.update(cx, |editor, cx| {
22084        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22085    });
22086    assert_eq!(
22087        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22088        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22089        "After folding the second buffer, its text should not be displayed"
22090    );
22091
22092    multi_buffer_editor.update(cx, |editor, cx| {
22093        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22094    });
22095    assert_eq!(
22096        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22097        "\n\n\n\n\n",
22098        "After folding the third buffer, its text should not be displayed"
22099    );
22100
22101    // Emulate selection inside the fold logic, that should work
22102    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22103        editor
22104            .snapshot(window, cx)
22105            .next_line_boundary(Point::new(0, 4));
22106    });
22107
22108    multi_buffer_editor.update(cx, |editor, cx| {
22109        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22110    });
22111    assert_eq!(
22112        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22113        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22114        "After unfolding the second buffer, its text should be displayed"
22115    );
22116
22117    // Typing inside of buffer 1 causes that buffer to be unfolded.
22118    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22119        assert_eq!(
22120            multi_buffer
22121                .read(cx)
22122                .snapshot(cx)
22123                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22124                .collect::<String>(),
22125            "bbbb"
22126        );
22127        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22128            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22129        });
22130        editor.handle_input("B", window, cx);
22131    });
22132
22133    assert_eq!(
22134        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22135        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22136        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22137    );
22138
22139    multi_buffer_editor.update(cx, |editor, cx| {
22140        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22141    });
22142    assert_eq!(
22143        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22144        "\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",
22145        "After unfolding the all buffers, all original text should be displayed"
22146    );
22147}
22148
22149#[gpui::test]
22150async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22151    init_test(cx, |_| {});
22152
22153    let sample_text_1 = "1111\n2222\n3333".to_string();
22154    let sample_text_2 = "4444\n5555\n6666".to_string();
22155    let sample_text_3 = "7777\n8888\n9999".to_string();
22156
22157    let fs = FakeFs::new(cx.executor());
22158    fs.insert_tree(
22159        path!("/a"),
22160        json!({
22161            "first.rs": sample_text_1,
22162            "second.rs": sample_text_2,
22163            "third.rs": sample_text_3,
22164        }),
22165    )
22166    .await;
22167    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22168    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22169    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22170    let worktree = project.update(cx, |project, cx| {
22171        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22172        assert_eq!(worktrees.len(), 1);
22173        worktrees.pop().unwrap()
22174    });
22175    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22176
22177    let buffer_1 = project
22178        .update(cx, |project, cx| {
22179            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22180        })
22181        .await
22182        .unwrap();
22183    let buffer_2 = project
22184        .update(cx, |project, cx| {
22185            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22186        })
22187        .await
22188        .unwrap();
22189    let buffer_3 = project
22190        .update(cx, |project, cx| {
22191            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22192        })
22193        .await
22194        .unwrap();
22195
22196    let multi_buffer = cx.new(|cx| {
22197        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22198        multi_buffer.push_excerpts(
22199            buffer_1.clone(),
22200            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22201            cx,
22202        );
22203        multi_buffer.push_excerpts(
22204            buffer_2.clone(),
22205            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22206            cx,
22207        );
22208        multi_buffer.push_excerpts(
22209            buffer_3.clone(),
22210            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22211            cx,
22212        );
22213        multi_buffer
22214    });
22215
22216    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22217        Editor::new(
22218            EditorMode::full(),
22219            multi_buffer,
22220            Some(project.clone()),
22221            window,
22222            cx,
22223        )
22224    });
22225
22226    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22227    assert_eq!(
22228        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22229        full_text,
22230    );
22231
22232    multi_buffer_editor.update(cx, |editor, cx| {
22233        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22234    });
22235    assert_eq!(
22236        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22237        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22238        "After folding the first buffer, its text should not be displayed"
22239    );
22240
22241    multi_buffer_editor.update(cx, |editor, cx| {
22242        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22243    });
22244
22245    assert_eq!(
22246        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22247        "\n\n\n\n\n\n7777\n8888\n9999",
22248        "After folding the second buffer, its text should not be displayed"
22249    );
22250
22251    multi_buffer_editor.update(cx, |editor, cx| {
22252        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22253    });
22254    assert_eq!(
22255        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22256        "\n\n\n\n\n",
22257        "After folding the third buffer, its text should not be displayed"
22258    );
22259
22260    multi_buffer_editor.update(cx, |editor, cx| {
22261        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22262    });
22263    assert_eq!(
22264        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22265        "\n\n\n\n4444\n5555\n6666\n\n",
22266        "After unfolding the second buffer, its text should be displayed"
22267    );
22268
22269    multi_buffer_editor.update(cx, |editor, cx| {
22270        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22271    });
22272    assert_eq!(
22273        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22274        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22275        "After unfolding the first buffer, its text should be displayed"
22276    );
22277
22278    multi_buffer_editor.update(cx, |editor, cx| {
22279        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22280    });
22281    assert_eq!(
22282        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22283        full_text,
22284        "After unfolding all buffers, all original text should be displayed"
22285    );
22286}
22287
22288#[gpui::test]
22289async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22290    init_test(cx, |_| {});
22291
22292    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22293
22294    let fs = FakeFs::new(cx.executor());
22295    fs.insert_tree(
22296        path!("/a"),
22297        json!({
22298            "main.rs": sample_text,
22299        }),
22300    )
22301    .await;
22302    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22303    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22304    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22305    let worktree = project.update(cx, |project, cx| {
22306        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22307        assert_eq!(worktrees.len(), 1);
22308        worktrees.pop().unwrap()
22309    });
22310    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22311
22312    let buffer_1 = project
22313        .update(cx, |project, cx| {
22314            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22315        })
22316        .await
22317        .unwrap();
22318
22319    let multi_buffer = cx.new(|cx| {
22320        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22321        multi_buffer.push_excerpts(
22322            buffer_1.clone(),
22323            [ExcerptRange::new(
22324                Point::new(0, 0)
22325                    ..Point::new(
22326                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22327                        0,
22328                    ),
22329            )],
22330            cx,
22331        );
22332        multi_buffer
22333    });
22334    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22335        Editor::new(
22336            EditorMode::full(),
22337            multi_buffer,
22338            Some(project.clone()),
22339            window,
22340            cx,
22341        )
22342    });
22343
22344    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22345    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22346        enum TestHighlight {}
22347        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22348        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22349        editor.highlight_text::<TestHighlight>(
22350            vec![highlight_range.clone()],
22351            HighlightStyle::color(Hsla::green()),
22352            cx,
22353        );
22354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22355            s.select_ranges(Some(highlight_range))
22356        });
22357    });
22358
22359    let full_text = format!("\n\n{sample_text}");
22360    assert_eq!(
22361        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22362        full_text,
22363    );
22364}
22365
22366#[gpui::test]
22367async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22368    init_test(cx, |_| {});
22369    cx.update(|cx| {
22370        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22371            "keymaps/default-linux.json",
22372            cx,
22373        )
22374        .unwrap();
22375        cx.bind_keys(default_key_bindings);
22376    });
22377
22378    let (editor, cx) = cx.add_window_view(|window, cx| {
22379        let multi_buffer = MultiBuffer::build_multi(
22380            [
22381                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22382                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22383                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22384                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22385            ],
22386            cx,
22387        );
22388        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22389
22390        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22391        // fold all but the second buffer, so that we test navigating between two
22392        // adjacent folded buffers, as well as folded buffers at the start and
22393        // end the multibuffer
22394        editor.fold_buffer(buffer_ids[0], cx);
22395        editor.fold_buffer(buffer_ids[2], cx);
22396        editor.fold_buffer(buffer_ids[3], cx);
22397
22398        editor
22399    });
22400    cx.simulate_resize(size(px(1000.), px(1000.)));
22401
22402    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22403    cx.assert_excerpts_with_selections(indoc! {"
22404        [EXCERPT]
22405        ˇ[FOLDED]
22406        [EXCERPT]
22407        a1
22408        b1
22409        [EXCERPT]
22410        [FOLDED]
22411        [EXCERPT]
22412        [FOLDED]
22413        "
22414    });
22415    cx.simulate_keystroke("down");
22416    cx.assert_excerpts_with_selections(indoc! {"
22417        [EXCERPT]
22418        [FOLDED]
22419        [EXCERPT]
22420        ˇa1
22421        b1
22422        [EXCERPT]
22423        [FOLDED]
22424        [EXCERPT]
22425        [FOLDED]
22426        "
22427    });
22428    cx.simulate_keystroke("down");
22429    cx.assert_excerpts_with_selections(indoc! {"
22430        [EXCERPT]
22431        [FOLDED]
22432        [EXCERPT]
22433        a1
22434        ˇb1
22435        [EXCERPT]
22436        [FOLDED]
22437        [EXCERPT]
22438        [FOLDED]
22439        "
22440    });
22441    cx.simulate_keystroke("down");
22442    cx.assert_excerpts_with_selections(indoc! {"
22443        [EXCERPT]
22444        [FOLDED]
22445        [EXCERPT]
22446        a1
22447        b1
22448        ˇ[EXCERPT]
22449        [FOLDED]
22450        [EXCERPT]
22451        [FOLDED]
22452        "
22453    });
22454    cx.simulate_keystroke("down");
22455    cx.assert_excerpts_with_selections(indoc! {"
22456        [EXCERPT]
22457        [FOLDED]
22458        [EXCERPT]
22459        a1
22460        b1
22461        [EXCERPT]
22462        ˇ[FOLDED]
22463        [EXCERPT]
22464        [FOLDED]
22465        "
22466    });
22467    for _ in 0..5 {
22468        cx.simulate_keystroke("down");
22469        cx.assert_excerpts_with_selections(indoc! {"
22470            [EXCERPT]
22471            [FOLDED]
22472            [EXCERPT]
22473            a1
22474            b1
22475            [EXCERPT]
22476            [FOLDED]
22477            [EXCERPT]
22478            ˇ[FOLDED]
22479            "
22480        });
22481    }
22482
22483    cx.simulate_keystroke("up");
22484    cx.assert_excerpts_with_selections(indoc! {"
22485        [EXCERPT]
22486        [FOLDED]
22487        [EXCERPT]
22488        a1
22489        b1
22490        [EXCERPT]
22491        ˇ[FOLDED]
22492        [EXCERPT]
22493        [FOLDED]
22494        "
22495    });
22496    cx.simulate_keystroke("up");
22497    cx.assert_excerpts_with_selections(indoc! {"
22498        [EXCERPT]
22499        [FOLDED]
22500        [EXCERPT]
22501        a1
22502        b1
22503        ˇ[EXCERPT]
22504        [FOLDED]
22505        [EXCERPT]
22506        [FOLDED]
22507        "
22508    });
22509    cx.simulate_keystroke("up");
22510    cx.assert_excerpts_with_selections(indoc! {"
22511        [EXCERPT]
22512        [FOLDED]
22513        [EXCERPT]
22514        a1
22515        ˇb1
22516        [EXCERPT]
22517        [FOLDED]
22518        [EXCERPT]
22519        [FOLDED]
22520        "
22521    });
22522    cx.simulate_keystroke("up");
22523    cx.assert_excerpts_with_selections(indoc! {"
22524        [EXCERPT]
22525        [FOLDED]
22526        [EXCERPT]
22527        ˇa1
22528        b1
22529        [EXCERPT]
22530        [FOLDED]
22531        [EXCERPT]
22532        [FOLDED]
22533        "
22534    });
22535    for _ in 0..5 {
22536        cx.simulate_keystroke("up");
22537        cx.assert_excerpts_with_selections(indoc! {"
22538            [EXCERPT]
22539            ˇ[FOLDED]
22540            [EXCERPT]
22541            a1
22542            b1
22543            [EXCERPT]
22544            [FOLDED]
22545            [EXCERPT]
22546            [FOLDED]
22547            "
22548        });
22549    }
22550}
22551
22552#[gpui::test]
22553async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22554    init_test(cx, |_| {});
22555
22556    // Simple insertion
22557    assert_highlighted_edits(
22558        "Hello, world!",
22559        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22560        true,
22561        cx,
22562        |highlighted_edits, cx| {
22563            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22564            assert_eq!(highlighted_edits.highlights.len(), 1);
22565            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22566            assert_eq!(
22567                highlighted_edits.highlights[0].1.background_color,
22568                Some(cx.theme().status().created_background)
22569            );
22570        },
22571    )
22572    .await;
22573
22574    // Replacement
22575    assert_highlighted_edits(
22576        "This is a test.",
22577        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22578        false,
22579        cx,
22580        |highlighted_edits, cx| {
22581            assert_eq!(highlighted_edits.text, "That is a test.");
22582            assert_eq!(highlighted_edits.highlights.len(), 1);
22583            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22584            assert_eq!(
22585                highlighted_edits.highlights[0].1.background_color,
22586                Some(cx.theme().status().created_background)
22587            );
22588        },
22589    )
22590    .await;
22591
22592    // Multiple edits
22593    assert_highlighted_edits(
22594        "Hello, world!",
22595        vec![
22596            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22597            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22598        ],
22599        false,
22600        cx,
22601        |highlighted_edits, cx| {
22602            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22603            assert_eq!(highlighted_edits.highlights.len(), 2);
22604            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22605            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22606            assert_eq!(
22607                highlighted_edits.highlights[0].1.background_color,
22608                Some(cx.theme().status().created_background)
22609            );
22610            assert_eq!(
22611                highlighted_edits.highlights[1].1.background_color,
22612                Some(cx.theme().status().created_background)
22613            );
22614        },
22615    )
22616    .await;
22617
22618    // Multiple lines with edits
22619    assert_highlighted_edits(
22620        "First line\nSecond line\nThird line\nFourth line",
22621        vec![
22622            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22623            (
22624                Point::new(2, 0)..Point::new(2, 10),
22625                "New third line".to_string(),
22626            ),
22627            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22628        ],
22629        false,
22630        cx,
22631        |highlighted_edits, cx| {
22632            assert_eq!(
22633                highlighted_edits.text,
22634                "Second modified\nNew third line\nFourth updated line"
22635            );
22636            assert_eq!(highlighted_edits.highlights.len(), 3);
22637            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22638            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22639            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22640            for highlight in &highlighted_edits.highlights {
22641                assert_eq!(
22642                    highlight.1.background_color,
22643                    Some(cx.theme().status().created_background)
22644                );
22645            }
22646        },
22647    )
22648    .await;
22649}
22650
22651#[gpui::test]
22652async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22653    init_test(cx, |_| {});
22654
22655    // Deletion
22656    assert_highlighted_edits(
22657        "Hello, world!",
22658        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22659        true,
22660        cx,
22661        |highlighted_edits, cx| {
22662            assert_eq!(highlighted_edits.text, "Hello, world!");
22663            assert_eq!(highlighted_edits.highlights.len(), 1);
22664            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22665            assert_eq!(
22666                highlighted_edits.highlights[0].1.background_color,
22667                Some(cx.theme().status().deleted_background)
22668            );
22669        },
22670    )
22671    .await;
22672
22673    // Insertion
22674    assert_highlighted_edits(
22675        "Hello, world!",
22676        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22677        true,
22678        cx,
22679        |highlighted_edits, cx| {
22680            assert_eq!(highlighted_edits.highlights.len(), 1);
22681            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22682            assert_eq!(
22683                highlighted_edits.highlights[0].1.background_color,
22684                Some(cx.theme().status().created_background)
22685            );
22686        },
22687    )
22688    .await;
22689}
22690
22691async fn assert_highlighted_edits(
22692    text: &str,
22693    edits: Vec<(Range<Point>, String)>,
22694    include_deletions: bool,
22695    cx: &mut TestAppContext,
22696    assertion_fn: impl Fn(HighlightedText, &App),
22697) {
22698    let window = cx.add_window(|window, cx| {
22699        let buffer = MultiBuffer::build_simple(text, cx);
22700        Editor::new(EditorMode::full(), buffer, None, window, cx)
22701    });
22702    let cx = &mut VisualTestContext::from_window(*window, cx);
22703
22704    let (buffer, snapshot) = window
22705        .update(cx, |editor, _window, cx| {
22706            (
22707                editor.buffer().clone(),
22708                editor.buffer().read(cx).snapshot(cx),
22709            )
22710        })
22711        .unwrap();
22712
22713    let edits = edits
22714        .into_iter()
22715        .map(|(range, edit)| {
22716            (
22717                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22718                edit,
22719            )
22720        })
22721        .collect::<Vec<_>>();
22722
22723    let text_anchor_edits = edits
22724        .clone()
22725        .into_iter()
22726        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22727        .collect::<Vec<_>>();
22728
22729    let edit_preview = window
22730        .update(cx, |_, _window, cx| {
22731            buffer
22732                .read(cx)
22733                .as_singleton()
22734                .unwrap()
22735                .read(cx)
22736                .preview_edits(text_anchor_edits.into(), cx)
22737        })
22738        .unwrap()
22739        .await;
22740
22741    cx.update(|_window, cx| {
22742        let highlighted_edits = edit_prediction_edit_text(
22743            snapshot.as_singleton().unwrap().2,
22744            &edits,
22745            &edit_preview,
22746            include_deletions,
22747            cx,
22748        );
22749        assertion_fn(highlighted_edits, cx)
22750    });
22751}
22752
22753#[track_caller]
22754fn assert_breakpoint(
22755    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22756    path: &Arc<Path>,
22757    expected: Vec<(u32, Breakpoint)>,
22758) {
22759    if expected.is_empty() {
22760        assert!(!breakpoints.contains_key(path), "{}", path.display());
22761    } else {
22762        let mut breakpoint = breakpoints
22763            .get(path)
22764            .unwrap()
22765            .iter()
22766            .map(|breakpoint| {
22767                (
22768                    breakpoint.row,
22769                    Breakpoint {
22770                        message: breakpoint.message.clone(),
22771                        state: breakpoint.state,
22772                        condition: breakpoint.condition.clone(),
22773                        hit_condition: breakpoint.hit_condition.clone(),
22774                    },
22775                )
22776            })
22777            .collect::<Vec<_>>();
22778
22779        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22780
22781        assert_eq!(expected, breakpoint);
22782    }
22783}
22784
22785fn add_log_breakpoint_at_cursor(
22786    editor: &mut Editor,
22787    log_message: &str,
22788    window: &mut Window,
22789    cx: &mut Context<Editor>,
22790) {
22791    let (anchor, bp) = editor
22792        .breakpoints_at_cursors(window, cx)
22793        .first()
22794        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22795        .unwrap_or_else(|| {
22796            let snapshot = editor.snapshot(window, cx);
22797            let cursor_position: Point =
22798                editor.selections.newest(&snapshot.display_snapshot).head();
22799
22800            let breakpoint_position = snapshot
22801                .buffer_snapshot()
22802                .anchor_before(Point::new(cursor_position.row, 0));
22803
22804            (breakpoint_position, Breakpoint::new_log(log_message))
22805        });
22806
22807    editor.edit_breakpoint_at_anchor(
22808        anchor,
22809        bp,
22810        BreakpointEditAction::EditLogMessage(log_message.into()),
22811        cx,
22812    );
22813}
22814
22815#[gpui::test]
22816async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22817    init_test(cx, |_| {});
22818
22819    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22820    let fs = FakeFs::new(cx.executor());
22821    fs.insert_tree(
22822        path!("/a"),
22823        json!({
22824            "main.rs": sample_text,
22825        }),
22826    )
22827    .await;
22828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22831
22832    let fs = FakeFs::new(cx.executor());
22833    fs.insert_tree(
22834        path!("/a"),
22835        json!({
22836            "main.rs": sample_text,
22837        }),
22838    )
22839    .await;
22840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22841    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22842    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22843    let worktree_id = workspace
22844        .update(cx, |workspace, _window, cx| {
22845            workspace.project().update(cx, |project, cx| {
22846                project.worktrees(cx).next().unwrap().read(cx).id()
22847            })
22848        })
22849        .unwrap();
22850
22851    let buffer = project
22852        .update(cx, |project, cx| {
22853            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22854        })
22855        .await
22856        .unwrap();
22857
22858    let (editor, cx) = cx.add_window_view(|window, cx| {
22859        Editor::new(
22860            EditorMode::full(),
22861            MultiBuffer::build_from_buffer(buffer, cx),
22862            Some(project.clone()),
22863            window,
22864            cx,
22865        )
22866    });
22867
22868    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22869    let abs_path = project.read_with(cx, |project, cx| {
22870        project
22871            .absolute_path(&project_path, cx)
22872            .map(Arc::from)
22873            .unwrap()
22874    });
22875
22876    // assert we can add breakpoint on the first line
22877    editor.update_in(cx, |editor, window, cx| {
22878        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22879        editor.move_to_end(&MoveToEnd, window, cx);
22880        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22881    });
22882
22883    let breakpoints = editor.update(cx, |editor, cx| {
22884        editor
22885            .breakpoint_store()
22886            .as_ref()
22887            .unwrap()
22888            .read(cx)
22889            .all_source_breakpoints(cx)
22890    });
22891
22892    assert_eq!(1, breakpoints.len());
22893    assert_breakpoint(
22894        &breakpoints,
22895        &abs_path,
22896        vec![
22897            (0, Breakpoint::new_standard()),
22898            (3, Breakpoint::new_standard()),
22899        ],
22900    );
22901
22902    editor.update_in(cx, |editor, window, cx| {
22903        editor.move_to_beginning(&MoveToBeginning, window, cx);
22904        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22905    });
22906
22907    let breakpoints = editor.update(cx, |editor, cx| {
22908        editor
22909            .breakpoint_store()
22910            .as_ref()
22911            .unwrap()
22912            .read(cx)
22913            .all_source_breakpoints(cx)
22914    });
22915
22916    assert_eq!(1, breakpoints.len());
22917    assert_breakpoint(
22918        &breakpoints,
22919        &abs_path,
22920        vec![(3, Breakpoint::new_standard())],
22921    );
22922
22923    editor.update_in(cx, |editor, window, cx| {
22924        editor.move_to_end(&MoveToEnd, window, cx);
22925        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22926    });
22927
22928    let breakpoints = editor.update(cx, |editor, cx| {
22929        editor
22930            .breakpoint_store()
22931            .as_ref()
22932            .unwrap()
22933            .read(cx)
22934            .all_source_breakpoints(cx)
22935    });
22936
22937    assert_eq!(0, breakpoints.len());
22938    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22939}
22940
22941#[gpui::test]
22942async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22943    init_test(cx, |_| {});
22944
22945    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22946
22947    let fs = FakeFs::new(cx.executor());
22948    fs.insert_tree(
22949        path!("/a"),
22950        json!({
22951            "main.rs": sample_text,
22952        }),
22953    )
22954    .await;
22955    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22956    let (workspace, cx) =
22957        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22958
22959    let worktree_id = workspace.update(cx, |workspace, cx| {
22960        workspace.project().update(cx, |project, cx| {
22961            project.worktrees(cx).next().unwrap().read(cx).id()
22962        })
22963    });
22964
22965    let buffer = project
22966        .update(cx, |project, cx| {
22967            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22968        })
22969        .await
22970        .unwrap();
22971
22972    let (editor, cx) = cx.add_window_view(|window, cx| {
22973        Editor::new(
22974            EditorMode::full(),
22975            MultiBuffer::build_from_buffer(buffer, cx),
22976            Some(project.clone()),
22977            window,
22978            cx,
22979        )
22980    });
22981
22982    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22983    let abs_path = project.read_with(cx, |project, cx| {
22984        project
22985            .absolute_path(&project_path, cx)
22986            .map(Arc::from)
22987            .unwrap()
22988    });
22989
22990    editor.update_in(cx, |editor, window, cx| {
22991        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22992    });
22993
22994    let breakpoints = editor.update(cx, |editor, cx| {
22995        editor
22996            .breakpoint_store()
22997            .as_ref()
22998            .unwrap()
22999            .read(cx)
23000            .all_source_breakpoints(cx)
23001    });
23002
23003    assert_breakpoint(
23004        &breakpoints,
23005        &abs_path,
23006        vec![(0, Breakpoint::new_log("hello world"))],
23007    );
23008
23009    // Removing a log message from a log breakpoint should remove it
23010    editor.update_in(cx, |editor, window, cx| {
23011        add_log_breakpoint_at_cursor(editor, "", window, cx);
23012    });
23013
23014    let breakpoints = editor.update(cx, |editor, cx| {
23015        editor
23016            .breakpoint_store()
23017            .as_ref()
23018            .unwrap()
23019            .read(cx)
23020            .all_source_breakpoints(cx)
23021    });
23022
23023    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23024
23025    editor.update_in(cx, |editor, window, cx| {
23026        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23027        editor.move_to_end(&MoveToEnd, window, cx);
23028        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23029        // Not adding a log message to a standard breakpoint shouldn't remove it
23030        add_log_breakpoint_at_cursor(editor, "", window, cx);
23031    });
23032
23033    let breakpoints = editor.update(cx, |editor, cx| {
23034        editor
23035            .breakpoint_store()
23036            .as_ref()
23037            .unwrap()
23038            .read(cx)
23039            .all_source_breakpoints(cx)
23040    });
23041
23042    assert_breakpoint(
23043        &breakpoints,
23044        &abs_path,
23045        vec![
23046            (0, Breakpoint::new_standard()),
23047            (3, Breakpoint::new_standard()),
23048        ],
23049    );
23050
23051    editor.update_in(cx, |editor, window, cx| {
23052        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23053    });
23054
23055    let breakpoints = editor.update(cx, |editor, cx| {
23056        editor
23057            .breakpoint_store()
23058            .as_ref()
23059            .unwrap()
23060            .read(cx)
23061            .all_source_breakpoints(cx)
23062    });
23063
23064    assert_breakpoint(
23065        &breakpoints,
23066        &abs_path,
23067        vec![
23068            (0, Breakpoint::new_standard()),
23069            (3, Breakpoint::new_log("hello world")),
23070        ],
23071    );
23072
23073    editor.update_in(cx, |editor, window, cx| {
23074        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23075    });
23076
23077    let breakpoints = editor.update(cx, |editor, cx| {
23078        editor
23079            .breakpoint_store()
23080            .as_ref()
23081            .unwrap()
23082            .read(cx)
23083            .all_source_breakpoints(cx)
23084    });
23085
23086    assert_breakpoint(
23087        &breakpoints,
23088        &abs_path,
23089        vec![
23090            (0, Breakpoint::new_standard()),
23091            (3, Breakpoint::new_log("hello Earth!!")),
23092        ],
23093    );
23094}
23095
23096/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23097/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23098/// or when breakpoints were placed out of order. This tests for a regression too
23099#[gpui::test]
23100async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23101    init_test(cx, |_| {});
23102
23103    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23104    let fs = FakeFs::new(cx.executor());
23105    fs.insert_tree(
23106        path!("/a"),
23107        json!({
23108            "main.rs": sample_text,
23109        }),
23110    )
23111    .await;
23112    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23113    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23114    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23115
23116    let fs = FakeFs::new(cx.executor());
23117    fs.insert_tree(
23118        path!("/a"),
23119        json!({
23120            "main.rs": sample_text,
23121        }),
23122    )
23123    .await;
23124    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23125    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23126    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23127    let worktree_id = workspace
23128        .update(cx, |workspace, _window, cx| {
23129            workspace.project().update(cx, |project, cx| {
23130                project.worktrees(cx).next().unwrap().read(cx).id()
23131            })
23132        })
23133        .unwrap();
23134
23135    let buffer = project
23136        .update(cx, |project, cx| {
23137            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23138        })
23139        .await
23140        .unwrap();
23141
23142    let (editor, cx) = cx.add_window_view(|window, cx| {
23143        Editor::new(
23144            EditorMode::full(),
23145            MultiBuffer::build_from_buffer(buffer, cx),
23146            Some(project.clone()),
23147            window,
23148            cx,
23149        )
23150    });
23151
23152    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23153    let abs_path = project.read_with(cx, |project, cx| {
23154        project
23155            .absolute_path(&project_path, cx)
23156            .map(Arc::from)
23157            .unwrap()
23158    });
23159
23160    // assert we can add breakpoint on the first line
23161    editor.update_in(cx, |editor, window, cx| {
23162        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23163        editor.move_to_end(&MoveToEnd, window, cx);
23164        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23165        editor.move_up(&MoveUp, window, cx);
23166        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23167    });
23168
23169    let breakpoints = editor.update(cx, |editor, cx| {
23170        editor
23171            .breakpoint_store()
23172            .as_ref()
23173            .unwrap()
23174            .read(cx)
23175            .all_source_breakpoints(cx)
23176    });
23177
23178    assert_eq!(1, breakpoints.len());
23179    assert_breakpoint(
23180        &breakpoints,
23181        &abs_path,
23182        vec![
23183            (0, Breakpoint::new_standard()),
23184            (2, Breakpoint::new_standard()),
23185            (3, Breakpoint::new_standard()),
23186        ],
23187    );
23188
23189    editor.update_in(cx, |editor, window, cx| {
23190        editor.move_to_beginning(&MoveToBeginning, window, cx);
23191        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23192        editor.move_to_end(&MoveToEnd, window, cx);
23193        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23194        // Disabling a breakpoint that doesn't exist should do nothing
23195        editor.move_up(&MoveUp, window, cx);
23196        editor.move_up(&MoveUp, window, cx);
23197        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23198    });
23199
23200    let breakpoints = editor.update(cx, |editor, cx| {
23201        editor
23202            .breakpoint_store()
23203            .as_ref()
23204            .unwrap()
23205            .read(cx)
23206            .all_source_breakpoints(cx)
23207    });
23208
23209    let disable_breakpoint = {
23210        let mut bp = Breakpoint::new_standard();
23211        bp.state = BreakpointState::Disabled;
23212        bp
23213    };
23214
23215    assert_eq!(1, breakpoints.len());
23216    assert_breakpoint(
23217        &breakpoints,
23218        &abs_path,
23219        vec![
23220            (0, disable_breakpoint.clone()),
23221            (2, Breakpoint::new_standard()),
23222            (3, disable_breakpoint.clone()),
23223        ],
23224    );
23225
23226    editor.update_in(cx, |editor, window, cx| {
23227        editor.move_to_beginning(&MoveToBeginning, window, cx);
23228        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23229        editor.move_to_end(&MoveToEnd, window, cx);
23230        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23231        editor.move_up(&MoveUp, window, cx);
23232        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23233    });
23234
23235    let breakpoints = editor.update(cx, |editor, cx| {
23236        editor
23237            .breakpoint_store()
23238            .as_ref()
23239            .unwrap()
23240            .read(cx)
23241            .all_source_breakpoints(cx)
23242    });
23243
23244    assert_eq!(1, breakpoints.len());
23245    assert_breakpoint(
23246        &breakpoints,
23247        &abs_path,
23248        vec![
23249            (0, Breakpoint::new_standard()),
23250            (2, disable_breakpoint),
23251            (3, Breakpoint::new_standard()),
23252        ],
23253    );
23254}
23255
23256#[gpui::test]
23257async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23258    init_test(cx, |_| {});
23259    let capabilities = lsp::ServerCapabilities {
23260        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23261            prepare_provider: Some(true),
23262            work_done_progress_options: Default::default(),
23263        })),
23264        ..Default::default()
23265    };
23266    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23267
23268    cx.set_state(indoc! {"
23269        struct Fˇoo {}
23270    "});
23271
23272    cx.update_editor(|editor, _, cx| {
23273        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23274        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23275        editor.highlight_background::<DocumentHighlightRead>(
23276            &[highlight_range],
23277            |theme| theme.colors().editor_document_highlight_read_background,
23278            cx,
23279        );
23280    });
23281
23282    let mut prepare_rename_handler = cx
23283        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23284            move |_, _, _| async move {
23285                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23286                    start: lsp::Position {
23287                        line: 0,
23288                        character: 7,
23289                    },
23290                    end: lsp::Position {
23291                        line: 0,
23292                        character: 10,
23293                    },
23294                })))
23295            },
23296        );
23297    let prepare_rename_task = cx
23298        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23299        .expect("Prepare rename was not started");
23300    prepare_rename_handler.next().await.unwrap();
23301    prepare_rename_task.await.expect("Prepare rename failed");
23302
23303    let mut rename_handler =
23304        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23305            let edit = lsp::TextEdit {
23306                range: lsp::Range {
23307                    start: lsp::Position {
23308                        line: 0,
23309                        character: 7,
23310                    },
23311                    end: lsp::Position {
23312                        line: 0,
23313                        character: 10,
23314                    },
23315                },
23316                new_text: "FooRenamed".to_string(),
23317            };
23318            Ok(Some(lsp::WorkspaceEdit::new(
23319                // Specify the same edit twice
23320                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23321            )))
23322        });
23323    let rename_task = cx
23324        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23325        .expect("Confirm rename was not started");
23326    rename_handler.next().await.unwrap();
23327    rename_task.await.expect("Confirm rename failed");
23328    cx.run_until_parked();
23329
23330    // Despite two edits, only one is actually applied as those are identical
23331    cx.assert_editor_state(indoc! {"
23332        struct FooRenamedˇ {}
23333    "});
23334}
23335
23336#[gpui::test]
23337async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23338    init_test(cx, |_| {});
23339    // These capabilities indicate that the server does not support prepare rename.
23340    let capabilities = lsp::ServerCapabilities {
23341        rename_provider: Some(lsp::OneOf::Left(true)),
23342        ..Default::default()
23343    };
23344    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23345
23346    cx.set_state(indoc! {"
23347        struct Fˇoo {}
23348    "});
23349
23350    cx.update_editor(|editor, _window, cx| {
23351        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23352        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23353        editor.highlight_background::<DocumentHighlightRead>(
23354            &[highlight_range],
23355            |theme| theme.colors().editor_document_highlight_read_background,
23356            cx,
23357        );
23358    });
23359
23360    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23361        .expect("Prepare rename was not started")
23362        .await
23363        .expect("Prepare rename failed");
23364
23365    let mut rename_handler =
23366        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23367            let edit = lsp::TextEdit {
23368                range: lsp::Range {
23369                    start: lsp::Position {
23370                        line: 0,
23371                        character: 7,
23372                    },
23373                    end: lsp::Position {
23374                        line: 0,
23375                        character: 10,
23376                    },
23377                },
23378                new_text: "FooRenamed".to_string(),
23379            };
23380            Ok(Some(lsp::WorkspaceEdit::new(
23381                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23382            )))
23383        });
23384    let rename_task = cx
23385        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23386        .expect("Confirm rename was not started");
23387    rename_handler.next().await.unwrap();
23388    rename_task.await.expect("Confirm rename failed");
23389    cx.run_until_parked();
23390
23391    // Correct range is renamed, as `surrounding_word` is used to find it.
23392    cx.assert_editor_state(indoc! {"
23393        struct FooRenamedˇ {}
23394    "});
23395}
23396
23397#[gpui::test]
23398async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23399    init_test(cx, |_| {});
23400    let mut cx = EditorTestContext::new(cx).await;
23401
23402    let language = Arc::new(
23403        Language::new(
23404            LanguageConfig::default(),
23405            Some(tree_sitter_html::LANGUAGE.into()),
23406        )
23407        .with_brackets_query(
23408            r#"
23409            ("<" @open "/>" @close)
23410            ("</" @open ">" @close)
23411            ("<" @open ">" @close)
23412            ("\"" @open "\"" @close)
23413            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23414        "#,
23415        )
23416        .unwrap(),
23417    );
23418    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23419
23420    cx.set_state(indoc! {"
23421        <span>ˇ</span>
23422    "});
23423    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23424    cx.assert_editor_state(indoc! {"
23425        <span>
23426        ˇ
23427        </span>
23428    "});
23429
23430    cx.set_state(indoc! {"
23431        <span><span></span>ˇ</span>
23432    "});
23433    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23434    cx.assert_editor_state(indoc! {"
23435        <span><span></span>
23436        ˇ</span>
23437    "});
23438
23439    cx.set_state(indoc! {"
23440        <span>ˇ
23441        </span>
23442    "});
23443    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23444    cx.assert_editor_state(indoc! {"
23445        <span>
23446        ˇ
23447        </span>
23448    "});
23449}
23450
23451#[gpui::test(iterations = 10)]
23452async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23453    init_test(cx, |_| {});
23454
23455    let fs = FakeFs::new(cx.executor());
23456    fs.insert_tree(
23457        path!("/dir"),
23458        json!({
23459            "a.ts": "a",
23460        }),
23461    )
23462    .await;
23463
23464    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23465    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23466    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23467
23468    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23469    language_registry.add(Arc::new(Language::new(
23470        LanguageConfig {
23471            name: "TypeScript".into(),
23472            matcher: LanguageMatcher {
23473                path_suffixes: vec!["ts".to_string()],
23474                ..Default::default()
23475            },
23476            ..Default::default()
23477        },
23478        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23479    )));
23480    let mut fake_language_servers = language_registry.register_fake_lsp(
23481        "TypeScript",
23482        FakeLspAdapter {
23483            capabilities: lsp::ServerCapabilities {
23484                code_lens_provider: Some(lsp::CodeLensOptions {
23485                    resolve_provider: Some(true),
23486                }),
23487                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23488                    commands: vec!["_the/command".to_string()],
23489                    ..lsp::ExecuteCommandOptions::default()
23490                }),
23491                ..lsp::ServerCapabilities::default()
23492            },
23493            ..FakeLspAdapter::default()
23494        },
23495    );
23496
23497    let editor = workspace
23498        .update(cx, |workspace, window, cx| {
23499            workspace.open_abs_path(
23500                PathBuf::from(path!("/dir/a.ts")),
23501                OpenOptions::default(),
23502                window,
23503                cx,
23504            )
23505        })
23506        .unwrap()
23507        .await
23508        .unwrap()
23509        .downcast::<Editor>()
23510        .unwrap();
23511    cx.executor().run_until_parked();
23512
23513    let fake_server = fake_language_servers.next().await.unwrap();
23514
23515    let buffer = editor.update(cx, |editor, cx| {
23516        editor
23517            .buffer()
23518            .read(cx)
23519            .as_singleton()
23520            .expect("have opened a single file by path")
23521    });
23522
23523    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23524    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23525    drop(buffer_snapshot);
23526    let actions = cx
23527        .update_window(*workspace, |_, window, cx| {
23528            project.code_actions(&buffer, anchor..anchor, window, cx)
23529        })
23530        .unwrap();
23531
23532    fake_server
23533        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23534            Ok(Some(vec![
23535                lsp::CodeLens {
23536                    range: lsp::Range::default(),
23537                    command: Some(lsp::Command {
23538                        title: "Code lens command".to_owned(),
23539                        command: "_the/command".to_owned(),
23540                        arguments: None,
23541                    }),
23542                    data: None,
23543                },
23544                lsp::CodeLens {
23545                    range: lsp::Range::default(),
23546                    command: Some(lsp::Command {
23547                        title: "Command not in capabilities".to_owned(),
23548                        command: "not in capabilities".to_owned(),
23549                        arguments: None,
23550                    }),
23551                    data: None,
23552                },
23553                lsp::CodeLens {
23554                    range: lsp::Range {
23555                        start: lsp::Position {
23556                            line: 1,
23557                            character: 1,
23558                        },
23559                        end: lsp::Position {
23560                            line: 1,
23561                            character: 1,
23562                        },
23563                    },
23564                    command: Some(lsp::Command {
23565                        title: "Command not in range".to_owned(),
23566                        command: "_the/command".to_owned(),
23567                        arguments: None,
23568                    }),
23569                    data: None,
23570                },
23571            ]))
23572        })
23573        .next()
23574        .await;
23575
23576    let actions = actions.await.unwrap();
23577    assert_eq!(
23578        actions.len(),
23579        1,
23580        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23581    );
23582    let action = actions[0].clone();
23583    let apply = project.update(cx, |project, cx| {
23584        project.apply_code_action(buffer.clone(), action, true, cx)
23585    });
23586
23587    // Resolving the code action does not populate its edits. In absence of
23588    // edits, we must execute the given command.
23589    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23590        |mut lens, _| async move {
23591            let lens_command = lens.command.as_mut().expect("should have a command");
23592            assert_eq!(lens_command.title, "Code lens command");
23593            lens_command.arguments = Some(vec![json!("the-argument")]);
23594            Ok(lens)
23595        },
23596    );
23597
23598    // While executing the command, the language server sends the editor
23599    // a `workspaceEdit` request.
23600    fake_server
23601        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23602            let fake = fake_server.clone();
23603            move |params, _| {
23604                assert_eq!(params.command, "_the/command");
23605                let fake = fake.clone();
23606                async move {
23607                    fake.server
23608                        .request::<lsp::request::ApplyWorkspaceEdit>(
23609                            lsp::ApplyWorkspaceEditParams {
23610                                label: None,
23611                                edit: lsp::WorkspaceEdit {
23612                                    changes: Some(
23613                                        [(
23614                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23615                                            vec![lsp::TextEdit {
23616                                                range: lsp::Range::new(
23617                                                    lsp::Position::new(0, 0),
23618                                                    lsp::Position::new(0, 0),
23619                                                ),
23620                                                new_text: "X".into(),
23621                                            }],
23622                                        )]
23623                                        .into_iter()
23624                                        .collect(),
23625                                    ),
23626                                    ..lsp::WorkspaceEdit::default()
23627                                },
23628                            },
23629                        )
23630                        .await
23631                        .into_response()
23632                        .unwrap();
23633                    Ok(Some(json!(null)))
23634                }
23635            }
23636        })
23637        .next()
23638        .await;
23639
23640    // Applying the code lens command returns a project transaction containing the edits
23641    // sent by the language server in its `workspaceEdit` request.
23642    let transaction = apply.await.unwrap();
23643    assert!(transaction.0.contains_key(&buffer));
23644    buffer.update(cx, |buffer, cx| {
23645        assert_eq!(buffer.text(), "Xa");
23646        buffer.undo(cx);
23647        assert_eq!(buffer.text(), "a");
23648    });
23649
23650    let actions_after_edits = cx
23651        .update_window(*workspace, |_, window, cx| {
23652            project.code_actions(&buffer, anchor..anchor, window, cx)
23653        })
23654        .unwrap()
23655        .await
23656        .unwrap();
23657    assert_eq!(
23658        actions, actions_after_edits,
23659        "For the same selection, same code lens actions should be returned"
23660    );
23661
23662    let _responses =
23663        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23664            panic!("No more code lens requests are expected");
23665        });
23666    editor.update_in(cx, |editor, window, cx| {
23667        editor.select_all(&SelectAll, window, cx);
23668    });
23669    cx.executor().run_until_parked();
23670    let new_actions = cx
23671        .update_window(*workspace, |_, window, cx| {
23672            project.code_actions(&buffer, anchor..anchor, window, cx)
23673        })
23674        .unwrap()
23675        .await
23676        .unwrap();
23677    assert_eq!(
23678        actions, new_actions,
23679        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23680    );
23681}
23682
23683#[gpui::test]
23684async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23685    init_test(cx, |_| {});
23686
23687    let fs = FakeFs::new(cx.executor());
23688    let main_text = r#"fn main() {
23689println!("1");
23690println!("2");
23691println!("3");
23692println!("4");
23693println!("5");
23694}"#;
23695    let lib_text = "mod foo {}";
23696    fs.insert_tree(
23697        path!("/a"),
23698        json!({
23699            "lib.rs": lib_text,
23700            "main.rs": main_text,
23701        }),
23702    )
23703    .await;
23704
23705    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23706    let (workspace, cx) =
23707        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23708    let worktree_id = workspace.update(cx, |workspace, cx| {
23709        workspace.project().update(cx, |project, cx| {
23710            project.worktrees(cx).next().unwrap().read(cx).id()
23711        })
23712    });
23713
23714    let expected_ranges = vec![
23715        Point::new(0, 0)..Point::new(0, 0),
23716        Point::new(1, 0)..Point::new(1, 1),
23717        Point::new(2, 0)..Point::new(2, 2),
23718        Point::new(3, 0)..Point::new(3, 3),
23719    ];
23720
23721    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23722    let editor_1 = workspace
23723        .update_in(cx, |workspace, window, cx| {
23724            workspace.open_path(
23725                (worktree_id, rel_path("main.rs")),
23726                Some(pane_1.downgrade()),
23727                true,
23728                window,
23729                cx,
23730            )
23731        })
23732        .unwrap()
23733        .await
23734        .downcast::<Editor>()
23735        .unwrap();
23736    pane_1.update(cx, |pane, cx| {
23737        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23738        open_editor.update(cx, |editor, cx| {
23739            assert_eq!(
23740                editor.display_text(cx),
23741                main_text,
23742                "Original main.rs text on initial open",
23743            );
23744            assert_eq!(
23745                editor
23746                    .selections
23747                    .all::<Point>(&editor.display_snapshot(cx))
23748                    .into_iter()
23749                    .map(|s| s.range())
23750                    .collect::<Vec<_>>(),
23751                vec![Point::zero()..Point::zero()],
23752                "Default selections on initial open",
23753            );
23754        })
23755    });
23756    editor_1.update_in(cx, |editor, window, cx| {
23757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23758            s.select_ranges(expected_ranges.clone());
23759        });
23760    });
23761
23762    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23763        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23764    });
23765    let editor_2 = workspace
23766        .update_in(cx, |workspace, window, cx| {
23767            workspace.open_path(
23768                (worktree_id, rel_path("main.rs")),
23769                Some(pane_2.downgrade()),
23770                true,
23771                window,
23772                cx,
23773            )
23774        })
23775        .unwrap()
23776        .await
23777        .downcast::<Editor>()
23778        .unwrap();
23779    pane_2.update(cx, |pane, cx| {
23780        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23781        open_editor.update(cx, |editor, cx| {
23782            assert_eq!(
23783                editor.display_text(cx),
23784                main_text,
23785                "Original main.rs text on initial open in another panel",
23786            );
23787            assert_eq!(
23788                editor
23789                    .selections
23790                    .all::<Point>(&editor.display_snapshot(cx))
23791                    .into_iter()
23792                    .map(|s| s.range())
23793                    .collect::<Vec<_>>(),
23794                vec![Point::zero()..Point::zero()],
23795                "Default selections on initial open in another panel",
23796            );
23797        })
23798    });
23799
23800    editor_2.update_in(cx, |editor, window, cx| {
23801        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23802    });
23803
23804    let _other_editor_1 = workspace
23805        .update_in(cx, |workspace, window, cx| {
23806            workspace.open_path(
23807                (worktree_id, rel_path("lib.rs")),
23808                Some(pane_1.downgrade()),
23809                true,
23810                window,
23811                cx,
23812            )
23813        })
23814        .unwrap()
23815        .await
23816        .downcast::<Editor>()
23817        .unwrap();
23818    pane_1
23819        .update_in(cx, |pane, window, cx| {
23820            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23821        })
23822        .await
23823        .unwrap();
23824    drop(editor_1);
23825    pane_1.update(cx, |pane, cx| {
23826        pane.active_item()
23827            .unwrap()
23828            .downcast::<Editor>()
23829            .unwrap()
23830            .update(cx, |editor, cx| {
23831                assert_eq!(
23832                    editor.display_text(cx),
23833                    lib_text,
23834                    "Other file should be open and active",
23835                );
23836            });
23837        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23838    });
23839
23840    let _other_editor_2 = workspace
23841        .update_in(cx, |workspace, window, cx| {
23842            workspace.open_path(
23843                (worktree_id, rel_path("lib.rs")),
23844                Some(pane_2.downgrade()),
23845                true,
23846                window,
23847                cx,
23848            )
23849        })
23850        .unwrap()
23851        .await
23852        .downcast::<Editor>()
23853        .unwrap();
23854    pane_2
23855        .update_in(cx, |pane, window, cx| {
23856            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23857        })
23858        .await
23859        .unwrap();
23860    drop(editor_2);
23861    pane_2.update(cx, |pane, cx| {
23862        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23863        open_editor.update(cx, |editor, cx| {
23864            assert_eq!(
23865                editor.display_text(cx),
23866                lib_text,
23867                "Other file should be open and active in another panel too",
23868            );
23869        });
23870        assert_eq!(
23871            pane.items().count(),
23872            1,
23873            "No other editors should be open in another pane",
23874        );
23875    });
23876
23877    let _editor_1_reopened = workspace
23878        .update_in(cx, |workspace, window, cx| {
23879            workspace.open_path(
23880                (worktree_id, rel_path("main.rs")),
23881                Some(pane_1.downgrade()),
23882                true,
23883                window,
23884                cx,
23885            )
23886        })
23887        .unwrap()
23888        .await
23889        .downcast::<Editor>()
23890        .unwrap();
23891    let _editor_2_reopened = workspace
23892        .update_in(cx, |workspace, window, cx| {
23893            workspace.open_path(
23894                (worktree_id, rel_path("main.rs")),
23895                Some(pane_2.downgrade()),
23896                true,
23897                window,
23898                cx,
23899            )
23900        })
23901        .unwrap()
23902        .await
23903        .downcast::<Editor>()
23904        .unwrap();
23905    pane_1.update(cx, |pane, cx| {
23906        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23907        open_editor.update(cx, |editor, cx| {
23908            assert_eq!(
23909                editor.display_text(cx),
23910                main_text,
23911                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23912            );
23913            assert_eq!(
23914                editor
23915                    .selections
23916                    .all::<Point>(&editor.display_snapshot(cx))
23917                    .into_iter()
23918                    .map(|s| s.range())
23919                    .collect::<Vec<_>>(),
23920                expected_ranges,
23921                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23922            );
23923        })
23924    });
23925    pane_2.update(cx, |pane, cx| {
23926        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23927        open_editor.update(cx, |editor, cx| {
23928            assert_eq!(
23929                editor.display_text(cx),
23930                r#"fn main() {
23931⋯rintln!("1");
23932⋯intln!("2");
23933⋯ntln!("3");
23934println!("4");
23935println!("5");
23936}"#,
23937                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23938            );
23939            assert_eq!(
23940                editor
23941                    .selections
23942                    .all::<Point>(&editor.display_snapshot(cx))
23943                    .into_iter()
23944                    .map(|s| s.range())
23945                    .collect::<Vec<_>>(),
23946                vec![Point::zero()..Point::zero()],
23947                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23948            );
23949        })
23950    });
23951}
23952
23953#[gpui::test]
23954async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23955    init_test(cx, |_| {});
23956
23957    let fs = FakeFs::new(cx.executor());
23958    let main_text = r#"fn main() {
23959println!("1");
23960println!("2");
23961println!("3");
23962println!("4");
23963println!("5");
23964}"#;
23965    let lib_text = "mod foo {}";
23966    fs.insert_tree(
23967        path!("/a"),
23968        json!({
23969            "lib.rs": lib_text,
23970            "main.rs": main_text,
23971        }),
23972    )
23973    .await;
23974
23975    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23976    let (workspace, cx) =
23977        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23978    let worktree_id = workspace.update(cx, |workspace, cx| {
23979        workspace.project().update(cx, |project, cx| {
23980            project.worktrees(cx).next().unwrap().read(cx).id()
23981        })
23982    });
23983
23984    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23985    let editor = workspace
23986        .update_in(cx, |workspace, window, cx| {
23987            workspace.open_path(
23988                (worktree_id, rel_path("main.rs")),
23989                Some(pane.downgrade()),
23990                true,
23991                window,
23992                cx,
23993            )
23994        })
23995        .unwrap()
23996        .await
23997        .downcast::<Editor>()
23998        .unwrap();
23999    pane.update(cx, |pane, cx| {
24000        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24001        open_editor.update(cx, |editor, cx| {
24002            assert_eq!(
24003                editor.display_text(cx),
24004                main_text,
24005                "Original main.rs text on initial open",
24006            );
24007        })
24008    });
24009    editor.update_in(cx, |editor, window, cx| {
24010        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24011    });
24012
24013    cx.update_global(|store: &mut SettingsStore, cx| {
24014        store.update_user_settings(cx, |s| {
24015            s.workspace.restore_on_file_reopen = Some(false);
24016        });
24017    });
24018    editor.update_in(cx, |editor, window, cx| {
24019        editor.fold_ranges(
24020            vec![
24021                Point::new(1, 0)..Point::new(1, 1),
24022                Point::new(2, 0)..Point::new(2, 2),
24023                Point::new(3, 0)..Point::new(3, 3),
24024            ],
24025            false,
24026            window,
24027            cx,
24028        );
24029    });
24030    pane.update_in(cx, |pane, window, cx| {
24031        pane.close_all_items(&CloseAllItems::default(), window, cx)
24032    })
24033    .await
24034    .unwrap();
24035    pane.update(cx, |pane, _| {
24036        assert!(pane.active_item().is_none());
24037    });
24038    cx.update_global(|store: &mut SettingsStore, cx| {
24039        store.update_user_settings(cx, |s| {
24040            s.workspace.restore_on_file_reopen = Some(true);
24041        });
24042    });
24043
24044    let _editor_reopened = workspace
24045        .update_in(cx, |workspace, window, cx| {
24046            workspace.open_path(
24047                (worktree_id, rel_path("main.rs")),
24048                Some(pane.downgrade()),
24049                true,
24050                window,
24051                cx,
24052            )
24053        })
24054        .unwrap()
24055        .await
24056        .downcast::<Editor>()
24057        .unwrap();
24058    pane.update(cx, |pane, cx| {
24059        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24060        open_editor.update(cx, |editor, cx| {
24061            assert_eq!(
24062                editor.display_text(cx),
24063                main_text,
24064                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24065            );
24066        })
24067    });
24068}
24069
24070#[gpui::test]
24071async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24072    struct EmptyModalView {
24073        focus_handle: gpui::FocusHandle,
24074    }
24075    impl EventEmitter<DismissEvent> for EmptyModalView {}
24076    impl Render for EmptyModalView {
24077        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24078            div()
24079        }
24080    }
24081    impl Focusable for EmptyModalView {
24082        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24083            self.focus_handle.clone()
24084        }
24085    }
24086    impl workspace::ModalView for EmptyModalView {}
24087    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24088        EmptyModalView {
24089            focus_handle: cx.focus_handle(),
24090        }
24091    }
24092
24093    init_test(cx, |_| {});
24094
24095    let fs = FakeFs::new(cx.executor());
24096    let project = Project::test(fs, [], cx).await;
24097    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24098    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24099    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24100    let editor = cx.new_window_entity(|window, cx| {
24101        Editor::new(
24102            EditorMode::full(),
24103            buffer,
24104            Some(project.clone()),
24105            window,
24106            cx,
24107        )
24108    });
24109    workspace
24110        .update(cx, |workspace, window, cx| {
24111            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24112        })
24113        .unwrap();
24114    editor.update_in(cx, |editor, window, cx| {
24115        editor.open_context_menu(&OpenContextMenu, window, cx);
24116        assert!(editor.mouse_context_menu.is_some());
24117    });
24118    workspace
24119        .update(cx, |workspace, window, cx| {
24120            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24121        })
24122        .unwrap();
24123    cx.read(|cx| {
24124        assert!(editor.read(cx).mouse_context_menu.is_none());
24125    });
24126}
24127
24128fn set_linked_edit_ranges(
24129    opening: (Point, Point),
24130    closing: (Point, Point),
24131    editor: &mut Editor,
24132    cx: &mut Context<Editor>,
24133) {
24134    let Some((buffer, _)) = editor
24135        .buffer
24136        .read(cx)
24137        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24138    else {
24139        panic!("Failed to get buffer for selection position");
24140    };
24141    let buffer = buffer.read(cx);
24142    let buffer_id = buffer.remote_id();
24143    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24144    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24145    let mut linked_ranges = HashMap::default();
24146    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24147    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24148}
24149
24150#[gpui::test]
24151async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24152    init_test(cx, |_| {});
24153
24154    let fs = FakeFs::new(cx.executor());
24155    fs.insert_file(path!("/file.html"), Default::default())
24156        .await;
24157
24158    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24159
24160    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24161    let html_language = Arc::new(Language::new(
24162        LanguageConfig {
24163            name: "HTML".into(),
24164            matcher: LanguageMatcher {
24165                path_suffixes: vec!["html".to_string()],
24166                ..LanguageMatcher::default()
24167            },
24168            brackets: BracketPairConfig {
24169                pairs: vec![BracketPair {
24170                    start: "<".into(),
24171                    end: ">".into(),
24172                    close: true,
24173                    ..Default::default()
24174                }],
24175                ..Default::default()
24176            },
24177            ..Default::default()
24178        },
24179        Some(tree_sitter_html::LANGUAGE.into()),
24180    ));
24181    language_registry.add(html_language);
24182    let mut fake_servers = language_registry.register_fake_lsp(
24183        "HTML",
24184        FakeLspAdapter {
24185            capabilities: lsp::ServerCapabilities {
24186                completion_provider: Some(lsp::CompletionOptions {
24187                    resolve_provider: Some(true),
24188                    ..Default::default()
24189                }),
24190                ..Default::default()
24191            },
24192            ..Default::default()
24193        },
24194    );
24195
24196    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24197    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24198
24199    let worktree_id = workspace
24200        .update(cx, |workspace, _window, cx| {
24201            workspace.project().update(cx, |project, cx| {
24202                project.worktrees(cx).next().unwrap().read(cx).id()
24203            })
24204        })
24205        .unwrap();
24206    project
24207        .update(cx, |project, cx| {
24208            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24209        })
24210        .await
24211        .unwrap();
24212    let editor = workspace
24213        .update(cx, |workspace, window, cx| {
24214            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24215        })
24216        .unwrap()
24217        .await
24218        .unwrap()
24219        .downcast::<Editor>()
24220        .unwrap();
24221
24222    let fake_server = fake_servers.next().await.unwrap();
24223    editor.update_in(cx, |editor, window, cx| {
24224        editor.set_text("<ad></ad>", window, cx);
24225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24226            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24227        });
24228        set_linked_edit_ranges(
24229            (Point::new(0, 1), Point::new(0, 3)),
24230            (Point::new(0, 6), Point::new(0, 8)),
24231            editor,
24232            cx,
24233        );
24234    });
24235    let mut completion_handle =
24236        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24237            Ok(Some(lsp::CompletionResponse::Array(vec![
24238                lsp::CompletionItem {
24239                    label: "head".to_string(),
24240                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24241                        lsp::InsertReplaceEdit {
24242                            new_text: "head".to_string(),
24243                            insert: lsp::Range::new(
24244                                lsp::Position::new(0, 1),
24245                                lsp::Position::new(0, 3),
24246                            ),
24247                            replace: lsp::Range::new(
24248                                lsp::Position::new(0, 1),
24249                                lsp::Position::new(0, 3),
24250                            ),
24251                        },
24252                    )),
24253                    ..Default::default()
24254                },
24255            ])))
24256        });
24257    editor.update_in(cx, |editor, window, cx| {
24258        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24259    });
24260    cx.run_until_parked();
24261    completion_handle.next().await.unwrap();
24262    editor.update(cx, |editor, _| {
24263        assert!(
24264            editor.context_menu_visible(),
24265            "Completion menu should be visible"
24266        );
24267    });
24268    editor.update_in(cx, |editor, window, cx| {
24269        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24270    });
24271    cx.executor().run_until_parked();
24272    editor.update(cx, |editor, cx| {
24273        assert_eq!(editor.text(cx), "<head></head>");
24274    });
24275}
24276
24277#[gpui::test]
24278async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24279    init_test(cx, |_| {});
24280
24281    let mut cx = EditorTestContext::new(cx).await;
24282    let language = Arc::new(Language::new(
24283        LanguageConfig {
24284            name: "TSX".into(),
24285            matcher: LanguageMatcher {
24286                path_suffixes: vec!["tsx".to_string()],
24287                ..LanguageMatcher::default()
24288            },
24289            brackets: BracketPairConfig {
24290                pairs: vec![BracketPair {
24291                    start: "<".into(),
24292                    end: ">".into(),
24293                    close: true,
24294                    ..Default::default()
24295                }],
24296                ..Default::default()
24297            },
24298            linked_edit_characters: HashSet::from_iter(['.']),
24299            ..Default::default()
24300        },
24301        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24302    ));
24303    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24304
24305    // Test typing > does not extend linked pair
24306    cx.set_state("<divˇ<div></div>");
24307    cx.update_editor(|editor, _, cx| {
24308        set_linked_edit_ranges(
24309            (Point::new(0, 1), Point::new(0, 4)),
24310            (Point::new(0, 11), Point::new(0, 14)),
24311            editor,
24312            cx,
24313        );
24314    });
24315    cx.update_editor(|editor, window, cx| {
24316        editor.handle_input(">", window, cx);
24317    });
24318    cx.assert_editor_state("<div>ˇ<div></div>");
24319
24320    // Test typing . do extend linked pair
24321    cx.set_state("<Animatedˇ></Animated>");
24322    cx.update_editor(|editor, _, cx| {
24323        set_linked_edit_ranges(
24324            (Point::new(0, 1), Point::new(0, 9)),
24325            (Point::new(0, 12), Point::new(0, 20)),
24326            editor,
24327            cx,
24328        );
24329    });
24330    cx.update_editor(|editor, window, cx| {
24331        editor.handle_input(".", window, cx);
24332    });
24333    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24334    cx.update_editor(|editor, _, cx| {
24335        set_linked_edit_ranges(
24336            (Point::new(0, 1), Point::new(0, 10)),
24337            (Point::new(0, 13), Point::new(0, 21)),
24338            editor,
24339            cx,
24340        );
24341    });
24342    cx.update_editor(|editor, window, cx| {
24343        editor.handle_input("V", window, cx);
24344    });
24345    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24346}
24347
24348#[gpui::test]
24349async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24350    init_test(cx, |_| {});
24351
24352    let fs = FakeFs::new(cx.executor());
24353    fs.insert_tree(
24354        path!("/root"),
24355        json!({
24356            "a": {
24357                "main.rs": "fn main() {}",
24358            },
24359            "foo": {
24360                "bar": {
24361                    "external_file.rs": "pub mod external {}",
24362                }
24363            }
24364        }),
24365    )
24366    .await;
24367
24368    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24369    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24370    language_registry.add(rust_lang());
24371    let _fake_servers = language_registry.register_fake_lsp(
24372        "Rust",
24373        FakeLspAdapter {
24374            ..FakeLspAdapter::default()
24375        },
24376    );
24377    let (workspace, cx) =
24378        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24379    let worktree_id = workspace.update(cx, |workspace, cx| {
24380        workspace.project().update(cx, |project, cx| {
24381            project.worktrees(cx).next().unwrap().read(cx).id()
24382        })
24383    });
24384
24385    let assert_language_servers_count =
24386        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24387            project.update(cx, |project, cx| {
24388                let current = project
24389                    .lsp_store()
24390                    .read(cx)
24391                    .as_local()
24392                    .unwrap()
24393                    .language_servers
24394                    .len();
24395                assert_eq!(expected, current, "{context}");
24396            });
24397        };
24398
24399    assert_language_servers_count(
24400        0,
24401        "No servers should be running before any file is open",
24402        cx,
24403    );
24404    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24405    let main_editor = workspace
24406        .update_in(cx, |workspace, window, cx| {
24407            workspace.open_path(
24408                (worktree_id, rel_path("main.rs")),
24409                Some(pane.downgrade()),
24410                true,
24411                window,
24412                cx,
24413            )
24414        })
24415        .unwrap()
24416        .await
24417        .downcast::<Editor>()
24418        .unwrap();
24419    pane.update(cx, |pane, cx| {
24420        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24421        open_editor.update(cx, |editor, cx| {
24422            assert_eq!(
24423                editor.display_text(cx),
24424                "fn main() {}",
24425                "Original main.rs text on initial open",
24426            );
24427        });
24428        assert_eq!(open_editor, main_editor);
24429    });
24430    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24431
24432    let external_editor = workspace
24433        .update_in(cx, |workspace, window, cx| {
24434            workspace.open_abs_path(
24435                PathBuf::from("/root/foo/bar/external_file.rs"),
24436                OpenOptions::default(),
24437                window,
24438                cx,
24439            )
24440        })
24441        .await
24442        .expect("opening external file")
24443        .downcast::<Editor>()
24444        .expect("downcasted external file's open element to editor");
24445    pane.update(cx, |pane, cx| {
24446        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24447        open_editor.update(cx, |editor, cx| {
24448            assert_eq!(
24449                editor.display_text(cx),
24450                "pub mod external {}",
24451                "External file is open now",
24452            );
24453        });
24454        assert_eq!(open_editor, external_editor);
24455    });
24456    assert_language_servers_count(
24457        1,
24458        "Second, external, *.rs file should join the existing server",
24459        cx,
24460    );
24461
24462    pane.update_in(cx, |pane, window, cx| {
24463        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24464    })
24465    .await
24466    .unwrap();
24467    pane.update_in(cx, |pane, window, cx| {
24468        pane.navigate_backward(&Default::default(), window, cx);
24469    });
24470    cx.run_until_parked();
24471    pane.update(cx, |pane, cx| {
24472        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24473        open_editor.update(cx, |editor, cx| {
24474            assert_eq!(
24475                editor.display_text(cx),
24476                "pub mod external {}",
24477                "External file is open now",
24478            );
24479        });
24480    });
24481    assert_language_servers_count(
24482        1,
24483        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24484        cx,
24485    );
24486
24487    cx.update(|_, cx| {
24488        workspace::reload(cx);
24489    });
24490    assert_language_servers_count(
24491        1,
24492        "After reloading the worktree with local and external files opened, only one project should be started",
24493        cx,
24494    );
24495}
24496
24497#[gpui::test]
24498async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24499    init_test(cx, |_| {});
24500
24501    let mut cx = EditorTestContext::new(cx).await;
24502    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24503    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24504
24505    // test cursor move to start of each line on tab
24506    // for `if`, `elif`, `else`, `while`, `with` and `for`
24507    cx.set_state(indoc! {"
24508        def main():
24509        ˇ    for item in items:
24510        ˇ        while item.active:
24511        ˇ            if item.value > 10:
24512        ˇ                continue
24513        ˇ            elif item.value < 0:
24514        ˇ                break
24515        ˇ            else:
24516        ˇ                with item.context() as ctx:
24517        ˇ                    yield count
24518        ˇ        else:
24519        ˇ            log('while else')
24520        ˇ    else:
24521        ˇ        log('for else')
24522    "});
24523    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24524    cx.assert_editor_state(indoc! {"
24525        def main():
24526            ˇfor item in items:
24527                ˇwhile item.active:
24528                    ˇif item.value > 10:
24529                        ˇcontinue
24530                    ˇelif item.value < 0:
24531                        ˇbreak
24532                    ˇelse:
24533                        ˇwith item.context() as ctx:
24534                            ˇyield count
24535                ˇelse:
24536                    ˇlog('while else')
24537            ˇelse:
24538                ˇlog('for else')
24539    "});
24540    // test relative indent is preserved when tab
24541    // for `if`, `elif`, `else`, `while`, `with` and `for`
24542    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24543    cx.assert_editor_state(indoc! {"
24544        def main():
24545                ˇfor item in items:
24546                    ˇwhile item.active:
24547                        ˇif item.value > 10:
24548                            ˇcontinue
24549                        ˇelif item.value < 0:
24550                            ˇbreak
24551                        ˇelse:
24552                            ˇwith item.context() as ctx:
24553                                ˇyield count
24554                    ˇelse:
24555                        ˇlog('while else')
24556                ˇelse:
24557                    ˇlog('for else')
24558    "});
24559
24560    // test cursor move to start of each line on tab
24561    // for `try`, `except`, `else`, `finally`, `match` and `def`
24562    cx.set_state(indoc! {"
24563        def main():
24564        ˇ    try:
24565        ˇ        fetch()
24566        ˇ    except ValueError:
24567        ˇ        handle_error()
24568        ˇ    else:
24569        ˇ        match value:
24570        ˇ            case _:
24571        ˇ    finally:
24572        ˇ        def status():
24573        ˇ            return 0
24574    "});
24575    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24576    cx.assert_editor_state(indoc! {"
24577        def main():
24578            ˇtry:
24579                ˇfetch()
24580            ˇexcept ValueError:
24581                ˇhandle_error()
24582            ˇelse:
24583                ˇmatch value:
24584                    ˇcase _:
24585            ˇfinally:
24586                ˇdef status():
24587                    ˇreturn 0
24588    "});
24589    // test relative indent is preserved when tab
24590    // for `try`, `except`, `else`, `finally`, `match` and `def`
24591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24592    cx.assert_editor_state(indoc! {"
24593        def main():
24594                ˇtry:
24595                    ˇfetch()
24596                ˇexcept ValueError:
24597                    ˇhandle_error()
24598                ˇelse:
24599                    ˇmatch value:
24600                        ˇcase _:
24601                ˇfinally:
24602                    ˇdef status():
24603                        ˇreturn 0
24604    "});
24605}
24606
24607#[gpui::test]
24608async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24609    init_test(cx, |_| {});
24610
24611    let mut cx = EditorTestContext::new(cx).await;
24612    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24613    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24614
24615    // test `else` auto outdents when typed inside `if` block
24616    cx.set_state(indoc! {"
24617        def main():
24618            if i == 2:
24619                return
24620                ˇ
24621    "});
24622    cx.update_editor(|editor, window, cx| {
24623        editor.handle_input("else:", window, cx);
24624    });
24625    cx.assert_editor_state(indoc! {"
24626        def main():
24627            if i == 2:
24628                return
24629            else:ˇ
24630    "});
24631
24632    // test `except` auto outdents when typed inside `try` block
24633    cx.set_state(indoc! {"
24634        def main():
24635            try:
24636                i = 2
24637                ˇ
24638    "});
24639    cx.update_editor(|editor, window, cx| {
24640        editor.handle_input("except:", window, cx);
24641    });
24642    cx.assert_editor_state(indoc! {"
24643        def main():
24644            try:
24645                i = 2
24646            except:ˇ
24647    "});
24648
24649    // test `else` auto outdents when typed inside `except` block
24650    cx.set_state(indoc! {"
24651        def main():
24652            try:
24653                i = 2
24654            except:
24655                j = 2
24656                ˇ
24657    "});
24658    cx.update_editor(|editor, window, cx| {
24659        editor.handle_input("else:", window, cx);
24660    });
24661    cx.assert_editor_state(indoc! {"
24662        def main():
24663            try:
24664                i = 2
24665            except:
24666                j = 2
24667            else:ˇ
24668    "});
24669
24670    // test `finally` auto outdents when typed inside `else` block
24671    cx.set_state(indoc! {"
24672        def main():
24673            try:
24674                i = 2
24675            except:
24676                j = 2
24677            else:
24678                k = 2
24679                ˇ
24680    "});
24681    cx.update_editor(|editor, window, cx| {
24682        editor.handle_input("finally:", window, cx);
24683    });
24684    cx.assert_editor_state(indoc! {"
24685        def main():
24686            try:
24687                i = 2
24688            except:
24689                j = 2
24690            else:
24691                k = 2
24692            finally:ˇ
24693    "});
24694
24695    // test `else` does not outdents when typed inside `except` block right after for block
24696    cx.set_state(indoc! {"
24697        def main():
24698            try:
24699                i = 2
24700            except:
24701                for i in range(n):
24702                    pass
24703                ˇ
24704    "});
24705    cx.update_editor(|editor, window, cx| {
24706        editor.handle_input("else:", window, cx);
24707    });
24708    cx.assert_editor_state(indoc! {"
24709        def main():
24710            try:
24711                i = 2
24712            except:
24713                for i in range(n):
24714                    pass
24715                else:ˇ
24716    "});
24717
24718    // test `finally` auto outdents when typed inside `else` block right after for block
24719    cx.set_state(indoc! {"
24720        def main():
24721            try:
24722                i = 2
24723            except:
24724                j = 2
24725            else:
24726                for i in range(n):
24727                    pass
24728                ˇ
24729    "});
24730    cx.update_editor(|editor, window, cx| {
24731        editor.handle_input("finally:", window, cx);
24732    });
24733    cx.assert_editor_state(indoc! {"
24734        def main():
24735            try:
24736                i = 2
24737            except:
24738                j = 2
24739            else:
24740                for i in range(n):
24741                    pass
24742            finally:ˇ
24743    "});
24744
24745    // test `except` outdents to inner "try" block
24746    cx.set_state(indoc! {"
24747        def main():
24748            try:
24749                i = 2
24750                if i == 2:
24751                    try:
24752                        i = 3
24753                        ˇ
24754    "});
24755    cx.update_editor(|editor, window, cx| {
24756        editor.handle_input("except:", window, cx);
24757    });
24758    cx.assert_editor_state(indoc! {"
24759        def main():
24760            try:
24761                i = 2
24762                if i == 2:
24763                    try:
24764                        i = 3
24765                    except:ˇ
24766    "});
24767
24768    // test `except` outdents to outer "try" block
24769    cx.set_state(indoc! {"
24770        def main():
24771            try:
24772                i = 2
24773                if i == 2:
24774                    try:
24775                        i = 3
24776                ˇ
24777    "});
24778    cx.update_editor(|editor, window, cx| {
24779        editor.handle_input("except:", window, cx);
24780    });
24781    cx.assert_editor_state(indoc! {"
24782        def main():
24783            try:
24784                i = 2
24785                if i == 2:
24786                    try:
24787                        i = 3
24788            except:ˇ
24789    "});
24790
24791    // test `else` stays at correct indent when typed after `for` block
24792    cx.set_state(indoc! {"
24793        def main():
24794            for i in range(10):
24795                if i == 3:
24796                    break
24797            ˇ
24798    "});
24799    cx.update_editor(|editor, window, cx| {
24800        editor.handle_input("else:", window, cx);
24801    });
24802    cx.assert_editor_state(indoc! {"
24803        def main():
24804            for i in range(10):
24805                if i == 3:
24806                    break
24807            else:ˇ
24808    "});
24809
24810    // test does not outdent on typing after line with square brackets
24811    cx.set_state(indoc! {"
24812        def f() -> list[str]:
24813            ˇ
24814    "});
24815    cx.update_editor(|editor, window, cx| {
24816        editor.handle_input("a", window, cx);
24817    });
24818    cx.assert_editor_state(indoc! {"
24819        def f() -> list[str]:
2482024821    "});
24822
24823    // test does not outdent on typing : after case keyword
24824    cx.set_state(indoc! {"
24825        match 1:
24826            caseˇ
24827    "});
24828    cx.update_editor(|editor, window, cx| {
24829        editor.handle_input(":", window, cx);
24830    });
24831    cx.assert_editor_state(indoc! {"
24832        match 1:
24833            case:ˇ
24834    "});
24835}
24836
24837#[gpui::test]
24838async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24839    init_test(cx, |_| {});
24840    update_test_language_settings(cx, |settings| {
24841        settings.defaults.extend_comment_on_newline = Some(false);
24842    });
24843    let mut cx = EditorTestContext::new(cx).await;
24844    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24845    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24846
24847    // test correct indent after newline on comment
24848    cx.set_state(indoc! {"
24849        # COMMENT:ˇ
24850    "});
24851    cx.update_editor(|editor, window, cx| {
24852        editor.newline(&Newline, window, cx);
24853    });
24854    cx.assert_editor_state(indoc! {"
24855        # COMMENT:
24856        ˇ
24857    "});
24858
24859    // test correct indent after newline in brackets
24860    cx.set_state(indoc! {"
24861        {ˇ}
24862    "});
24863    cx.update_editor(|editor, window, cx| {
24864        editor.newline(&Newline, window, cx);
24865    });
24866    cx.run_until_parked();
24867    cx.assert_editor_state(indoc! {"
24868        {
24869            ˇ
24870        }
24871    "});
24872
24873    cx.set_state(indoc! {"
24874        (ˇ)
24875    "});
24876    cx.update_editor(|editor, window, cx| {
24877        editor.newline(&Newline, window, cx);
24878    });
24879    cx.run_until_parked();
24880    cx.assert_editor_state(indoc! {"
24881        (
24882            ˇ
24883        )
24884    "});
24885
24886    // do not indent after empty lists or dictionaries
24887    cx.set_state(indoc! {"
24888        a = []ˇ
24889    "});
24890    cx.update_editor(|editor, window, cx| {
24891        editor.newline(&Newline, window, cx);
24892    });
24893    cx.run_until_parked();
24894    cx.assert_editor_state(indoc! {"
24895        a = []
24896        ˇ
24897    "});
24898}
24899
24900#[gpui::test]
24901async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24902    init_test(cx, |_| {});
24903
24904    let mut cx = EditorTestContext::new(cx).await;
24905    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24906    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24907
24908    // test cursor move to start of each line on tab
24909    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24910    cx.set_state(indoc! {"
24911        function main() {
24912        ˇ    for item in $items; do
24913        ˇ        while [ -n \"$item\" ]; do
24914        ˇ            if [ \"$value\" -gt 10 ]; then
24915        ˇ                continue
24916        ˇ            elif [ \"$value\" -lt 0 ]; then
24917        ˇ                break
24918        ˇ            else
24919        ˇ                echo \"$item\"
24920        ˇ            fi
24921        ˇ        done
24922        ˇ    done
24923        ˇ}
24924    "});
24925    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24926    cx.assert_editor_state(indoc! {"
24927        function main() {
24928            ˇfor item in $items; do
24929                ˇwhile [ -n \"$item\" ]; do
24930                    ˇif [ \"$value\" -gt 10 ]; then
24931                        ˇcontinue
24932                    ˇelif [ \"$value\" -lt 0 ]; then
24933                        ˇbreak
24934                    ˇelse
24935                        ˇecho \"$item\"
24936                    ˇfi
24937                ˇdone
24938            ˇdone
24939        ˇ}
24940    "});
24941    // test relative indent is preserved when tab
24942    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24943    cx.assert_editor_state(indoc! {"
24944        function main() {
24945                ˇfor item in $items; do
24946                    ˇwhile [ -n \"$item\" ]; do
24947                        ˇif [ \"$value\" -gt 10 ]; then
24948                            ˇcontinue
24949                        ˇelif [ \"$value\" -lt 0 ]; then
24950                            ˇbreak
24951                        ˇelse
24952                            ˇecho \"$item\"
24953                        ˇfi
24954                    ˇdone
24955                ˇdone
24956            ˇ}
24957    "});
24958
24959    // test cursor move to start of each line on tab
24960    // for `case` statement with patterns
24961    cx.set_state(indoc! {"
24962        function handle() {
24963        ˇ    case \"$1\" in
24964        ˇ        start)
24965        ˇ            echo \"a\"
24966        ˇ            ;;
24967        ˇ        stop)
24968        ˇ            echo \"b\"
24969        ˇ            ;;
24970        ˇ        *)
24971        ˇ            echo \"c\"
24972        ˇ            ;;
24973        ˇ    esac
24974        ˇ}
24975    "});
24976    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24977    cx.assert_editor_state(indoc! {"
24978        function handle() {
24979            ˇcase \"$1\" in
24980                ˇstart)
24981                    ˇecho \"a\"
24982                    ˇ;;
24983                ˇstop)
24984                    ˇecho \"b\"
24985                    ˇ;;
24986                ˇ*)
24987                    ˇecho \"c\"
24988                    ˇ;;
24989            ˇesac
24990        ˇ}
24991    "});
24992}
24993
24994#[gpui::test]
24995async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24996    init_test(cx, |_| {});
24997
24998    let mut cx = EditorTestContext::new(cx).await;
24999    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25001
25002    // test indents on comment insert
25003    cx.set_state(indoc! {"
25004        function main() {
25005        ˇ    for item in $items; do
25006        ˇ        while [ -n \"$item\" ]; do
25007        ˇ            if [ \"$value\" -gt 10 ]; then
25008        ˇ                continue
25009        ˇ            elif [ \"$value\" -lt 0 ]; then
25010        ˇ                break
25011        ˇ            else
25012        ˇ                echo \"$item\"
25013        ˇ            fi
25014        ˇ        done
25015        ˇ    done
25016        ˇ}
25017    "});
25018    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25019    cx.assert_editor_state(indoc! {"
25020        function main() {
25021        #ˇ    for item in $items; do
25022        #ˇ        while [ -n \"$item\" ]; do
25023        #ˇ            if [ \"$value\" -gt 10 ]; then
25024        #ˇ                continue
25025        #ˇ            elif [ \"$value\" -lt 0 ]; then
25026        #ˇ                break
25027        #ˇ            else
25028        #ˇ                echo \"$item\"
25029        #ˇ            fi
25030        #ˇ        done
25031        #ˇ    done
25032        #ˇ}
25033    "});
25034}
25035
25036#[gpui::test]
25037async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25038    init_test(cx, |_| {});
25039
25040    let mut cx = EditorTestContext::new(cx).await;
25041    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25042    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25043
25044    // test `else` auto outdents when typed inside `if` block
25045    cx.set_state(indoc! {"
25046        if [ \"$1\" = \"test\" ]; then
25047            echo \"foo bar\"
25048            ˇ
25049    "});
25050    cx.update_editor(|editor, window, cx| {
25051        editor.handle_input("else", window, cx);
25052    });
25053    cx.assert_editor_state(indoc! {"
25054        if [ \"$1\" = \"test\" ]; then
25055            echo \"foo bar\"
25056        elseˇ
25057    "});
25058
25059    // test `elif` auto outdents when typed inside `if` block
25060    cx.set_state(indoc! {"
25061        if [ \"$1\" = \"test\" ]; then
25062            echo \"foo bar\"
25063            ˇ
25064    "});
25065    cx.update_editor(|editor, window, cx| {
25066        editor.handle_input("elif", window, cx);
25067    });
25068    cx.assert_editor_state(indoc! {"
25069        if [ \"$1\" = \"test\" ]; then
25070            echo \"foo bar\"
25071        elifˇ
25072    "});
25073
25074    // test `fi` auto outdents when typed inside `else` block
25075    cx.set_state(indoc! {"
25076        if [ \"$1\" = \"test\" ]; then
25077            echo \"foo bar\"
25078        else
25079            echo \"bar baz\"
25080            ˇ
25081    "});
25082    cx.update_editor(|editor, window, cx| {
25083        editor.handle_input("fi", window, cx);
25084    });
25085    cx.assert_editor_state(indoc! {"
25086        if [ \"$1\" = \"test\" ]; then
25087            echo \"foo bar\"
25088        else
25089            echo \"bar baz\"
25090        fiˇ
25091    "});
25092
25093    // test `done` auto outdents when typed inside `while` block
25094    cx.set_state(indoc! {"
25095        while read line; do
25096            echo \"$line\"
25097            ˇ
25098    "});
25099    cx.update_editor(|editor, window, cx| {
25100        editor.handle_input("done", window, cx);
25101    });
25102    cx.assert_editor_state(indoc! {"
25103        while read line; do
25104            echo \"$line\"
25105        doneˇ
25106    "});
25107
25108    // test `done` auto outdents when typed inside `for` block
25109    cx.set_state(indoc! {"
25110        for file in *.txt; do
25111            cat \"$file\"
25112            ˇ
25113    "});
25114    cx.update_editor(|editor, window, cx| {
25115        editor.handle_input("done", window, cx);
25116    });
25117    cx.assert_editor_state(indoc! {"
25118        for file in *.txt; do
25119            cat \"$file\"
25120        doneˇ
25121    "});
25122
25123    // test `esac` auto outdents when typed inside `case` block
25124    cx.set_state(indoc! {"
25125        case \"$1\" in
25126            start)
25127                echo \"foo bar\"
25128                ;;
25129            stop)
25130                echo \"bar baz\"
25131                ;;
25132            ˇ
25133    "});
25134    cx.update_editor(|editor, window, cx| {
25135        editor.handle_input("esac", window, cx);
25136    });
25137    cx.assert_editor_state(indoc! {"
25138        case \"$1\" in
25139            start)
25140                echo \"foo bar\"
25141                ;;
25142            stop)
25143                echo \"bar baz\"
25144                ;;
25145        esacˇ
25146    "});
25147
25148    // test `*)` auto outdents when typed inside `case` block
25149    cx.set_state(indoc! {"
25150        case \"$1\" in
25151            start)
25152                echo \"foo bar\"
25153                ;;
25154                ˇ
25155    "});
25156    cx.update_editor(|editor, window, cx| {
25157        editor.handle_input("*)", window, cx);
25158    });
25159    cx.assert_editor_state(indoc! {"
25160        case \"$1\" in
25161            start)
25162                echo \"foo bar\"
25163                ;;
25164            *)ˇ
25165    "});
25166
25167    // test `fi` outdents to correct level with nested if blocks
25168    cx.set_state(indoc! {"
25169        if [ \"$1\" = \"test\" ]; then
25170            echo \"outer if\"
25171            if [ \"$2\" = \"debug\" ]; then
25172                echo \"inner if\"
25173                ˇ
25174    "});
25175    cx.update_editor(|editor, window, cx| {
25176        editor.handle_input("fi", window, cx);
25177    });
25178    cx.assert_editor_state(indoc! {"
25179        if [ \"$1\" = \"test\" ]; then
25180            echo \"outer if\"
25181            if [ \"$2\" = \"debug\" ]; then
25182                echo \"inner if\"
25183            fiˇ
25184    "});
25185}
25186
25187#[gpui::test]
25188async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25189    init_test(cx, |_| {});
25190    update_test_language_settings(cx, |settings| {
25191        settings.defaults.extend_comment_on_newline = Some(false);
25192    });
25193    let mut cx = EditorTestContext::new(cx).await;
25194    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25196
25197    // test correct indent after newline on comment
25198    cx.set_state(indoc! {"
25199        # COMMENT:ˇ
25200    "});
25201    cx.update_editor(|editor, window, cx| {
25202        editor.newline(&Newline, window, cx);
25203    });
25204    cx.assert_editor_state(indoc! {"
25205        # COMMENT:
25206        ˇ
25207    "});
25208
25209    // test correct indent after newline after `then`
25210    cx.set_state(indoc! {"
25211
25212        if [ \"$1\" = \"test\" ]; thenˇ
25213    "});
25214    cx.update_editor(|editor, window, cx| {
25215        editor.newline(&Newline, window, cx);
25216    });
25217    cx.run_until_parked();
25218    cx.assert_editor_state(indoc! {"
25219
25220        if [ \"$1\" = \"test\" ]; then
25221            ˇ
25222    "});
25223
25224    // test correct indent after newline after `else`
25225    cx.set_state(indoc! {"
25226        if [ \"$1\" = \"test\" ]; then
25227        elseˇ
25228    "});
25229    cx.update_editor(|editor, window, cx| {
25230        editor.newline(&Newline, window, cx);
25231    });
25232    cx.run_until_parked();
25233    cx.assert_editor_state(indoc! {"
25234        if [ \"$1\" = \"test\" ]; then
25235        else
25236            ˇ
25237    "});
25238
25239    // test correct indent after newline after `elif`
25240    cx.set_state(indoc! {"
25241        if [ \"$1\" = \"test\" ]; then
25242        elifˇ
25243    "});
25244    cx.update_editor(|editor, window, cx| {
25245        editor.newline(&Newline, window, cx);
25246    });
25247    cx.run_until_parked();
25248    cx.assert_editor_state(indoc! {"
25249        if [ \"$1\" = \"test\" ]; then
25250        elif
25251            ˇ
25252    "});
25253
25254    // test correct indent after newline after `do`
25255    cx.set_state(indoc! {"
25256        for file in *.txt; doˇ
25257    "});
25258    cx.update_editor(|editor, window, cx| {
25259        editor.newline(&Newline, window, cx);
25260    });
25261    cx.run_until_parked();
25262    cx.assert_editor_state(indoc! {"
25263        for file in *.txt; do
25264            ˇ
25265    "});
25266
25267    // test correct indent after newline after case pattern
25268    cx.set_state(indoc! {"
25269        case \"$1\" in
25270            start)ˇ
25271    "});
25272    cx.update_editor(|editor, window, cx| {
25273        editor.newline(&Newline, window, cx);
25274    });
25275    cx.run_until_parked();
25276    cx.assert_editor_state(indoc! {"
25277        case \"$1\" in
25278            start)
25279                ˇ
25280    "});
25281
25282    // test correct indent after newline after case pattern
25283    cx.set_state(indoc! {"
25284        case \"$1\" in
25285            start)
25286                ;;
25287            *)ˇ
25288    "});
25289    cx.update_editor(|editor, window, cx| {
25290        editor.newline(&Newline, window, cx);
25291    });
25292    cx.run_until_parked();
25293    cx.assert_editor_state(indoc! {"
25294        case \"$1\" in
25295            start)
25296                ;;
25297            *)
25298                ˇ
25299    "});
25300
25301    // test correct indent after newline after function opening brace
25302    cx.set_state(indoc! {"
25303        function test() {ˇ}
25304    "});
25305    cx.update_editor(|editor, window, cx| {
25306        editor.newline(&Newline, window, cx);
25307    });
25308    cx.run_until_parked();
25309    cx.assert_editor_state(indoc! {"
25310        function test() {
25311            ˇ
25312        }
25313    "});
25314
25315    // test no extra indent after semicolon on same line
25316    cx.set_state(indoc! {"
25317        echo \"test\"25318    "});
25319    cx.update_editor(|editor, window, cx| {
25320        editor.newline(&Newline, window, cx);
25321    });
25322    cx.run_until_parked();
25323    cx.assert_editor_state(indoc! {"
25324        echo \"test\";
25325        ˇ
25326    "});
25327}
25328
25329fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25330    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25331    point..point
25332}
25333
25334#[track_caller]
25335fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25336    let (text, ranges) = marked_text_ranges(marked_text, true);
25337    assert_eq!(editor.text(cx), text);
25338    assert_eq!(
25339        editor.selections.ranges(&editor.display_snapshot(cx)),
25340        ranges,
25341        "Assert selections are {}",
25342        marked_text
25343    );
25344}
25345
25346pub fn handle_signature_help_request(
25347    cx: &mut EditorLspTestContext,
25348    mocked_response: lsp::SignatureHelp,
25349) -> impl Future<Output = ()> + use<> {
25350    let mut request =
25351        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25352            let mocked_response = mocked_response.clone();
25353            async move { Ok(Some(mocked_response)) }
25354        });
25355
25356    async move {
25357        request.next().await;
25358    }
25359}
25360
25361#[track_caller]
25362pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25363    cx.update_editor(|editor, _, _| {
25364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25365            let entries = menu.entries.borrow();
25366            let entries = entries
25367                .iter()
25368                .map(|entry| entry.string.as_str())
25369                .collect::<Vec<_>>();
25370            assert_eq!(entries, expected);
25371        } else {
25372            panic!("Expected completions menu");
25373        }
25374    });
25375}
25376
25377/// Handle completion request passing a marked string specifying where the completion
25378/// should be triggered from using '|' character, what range should be replaced, and what completions
25379/// should be returned using '<' and '>' to delimit the range.
25380///
25381/// Also see `handle_completion_request_with_insert_and_replace`.
25382#[track_caller]
25383pub fn handle_completion_request(
25384    marked_string: &str,
25385    completions: Vec<&'static str>,
25386    is_incomplete: bool,
25387    counter: Arc<AtomicUsize>,
25388    cx: &mut EditorLspTestContext,
25389) -> impl Future<Output = ()> {
25390    let complete_from_marker: TextRangeMarker = '|'.into();
25391    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25392    let (_, mut marked_ranges) = marked_text_ranges_by(
25393        marked_string,
25394        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25395    );
25396
25397    let complete_from_position =
25398        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25399    let replace_range =
25400        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25401
25402    let mut request =
25403        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25404            let completions = completions.clone();
25405            counter.fetch_add(1, atomic::Ordering::Release);
25406            async move {
25407                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25408                assert_eq!(
25409                    params.text_document_position.position,
25410                    complete_from_position
25411                );
25412                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25413                    is_incomplete,
25414                    item_defaults: None,
25415                    items: completions
25416                        .iter()
25417                        .map(|completion_text| lsp::CompletionItem {
25418                            label: completion_text.to_string(),
25419                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25420                                range: replace_range,
25421                                new_text: completion_text.to_string(),
25422                            })),
25423                            ..Default::default()
25424                        })
25425                        .collect(),
25426                })))
25427            }
25428        });
25429
25430    async move {
25431        request.next().await;
25432    }
25433}
25434
25435/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25436/// given instead, which also contains an `insert` range.
25437///
25438/// This function uses markers to define ranges:
25439/// - `|` marks the cursor position
25440/// - `<>` marks the replace range
25441/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25442pub fn handle_completion_request_with_insert_and_replace(
25443    cx: &mut EditorLspTestContext,
25444    marked_string: &str,
25445    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25446    counter: Arc<AtomicUsize>,
25447) -> impl Future<Output = ()> {
25448    let complete_from_marker: TextRangeMarker = '|'.into();
25449    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25450    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25451
25452    let (_, mut marked_ranges) = marked_text_ranges_by(
25453        marked_string,
25454        vec![
25455            complete_from_marker.clone(),
25456            replace_range_marker.clone(),
25457            insert_range_marker.clone(),
25458        ],
25459    );
25460
25461    let complete_from_position =
25462        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25463    let replace_range =
25464        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25465
25466    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25467        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25468        _ => lsp::Range {
25469            start: replace_range.start,
25470            end: complete_from_position,
25471        },
25472    };
25473
25474    let mut request =
25475        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25476            let completions = completions.clone();
25477            counter.fetch_add(1, atomic::Ordering::Release);
25478            async move {
25479                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25480                assert_eq!(
25481                    params.text_document_position.position, complete_from_position,
25482                    "marker `|` position doesn't match",
25483                );
25484                Ok(Some(lsp::CompletionResponse::Array(
25485                    completions
25486                        .iter()
25487                        .map(|(label, new_text)| lsp::CompletionItem {
25488                            label: label.to_string(),
25489                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25490                                lsp::InsertReplaceEdit {
25491                                    insert: insert_range,
25492                                    replace: replace_range,
25493                                    new_text: new_text.to_string(),
25494                                },
25495                            )),
25496                            ..Default::default()
25497                        })
25498                        .collect(),
25499                )))
25500            }
25501        });
25502
25503    async move {
25504        request.next().await;
25505    }
25506}
25507
25508fn handle_resolve_completion_request(
25509    cx: &mut EditorLspTestContext,
25510    edits: Option<Vec<(&'static str, &'static str)>>,
25511) -> impl Future<Output = ()> {
25512    let edits = edits.map(|edits| {
25513        edits
25514            .iter()
25515            .map(|(marked_string, new_text)| {
25516                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25517                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25518                lsp::TextEdit::new(replace_range, new_text.to_string())
25519            })
25520            .collect::<Vec<_>>()
25521    });
25522
25523    let mut request =
25524        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25525            let edits = edits.clone();
25526            async move {
25527                Ok(lsp::CompletionItem {
25528                    additional_text_edits: edits,
25529                    ..Default::default()
25530                })
25531            }
25532        });
25533
25534    async move {
25535        request.next().await;
25536    }
25537}
25538
25539pub(crate) fn update_test_language_settings(
25540    cx: &mut TestAppContext,
25541    f: impl Fn(&mut AllLanguageSettingsContent),
25542) {
25543    cx.update(|cx| {
25544        SettingsStore::update_global(cx, |store, cx| {
25545            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25546        });
25547    });
25548}
25549
25550pub(crate) fn update_test_project_settings(
25551    cx: &mut TestAppContext,
25552    f: impl Fn(&mut ProjectSettingsContent),
25553) {
25554    cx.update(|cx| {
25555        SettingsStore::update_global(cx, |store, cx| {
25556            store.update_user_settings(cx, |settings| f(&mut settings.project));
25557        });
25558    });
25559}
25560
25561pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25562    cx.update(|cx| {
25563        assets::Assets.load_test_fonts(cx);
25564        let store = SettingsStore::test(cx);
25565        cx.set_global(store);
25566        theme::init(theme::LoadThemes::JustBase, cx);
25567        release_channel::init(SemanticVersion::default(), cx);
25568        client::init_settings(cx);
25569        language::init(cx);
25570        Project::init_settings(cx);
25571        workspace::init_settings(cx);
25572        crate::init(cx);
25573    });
25574    zlog::init_test();
25575    update_test_language_settings(cx, f);
25576}
25577
25578#[track_caller]
25579fn assert_hunk_revert(
25580    not_reverted_text_with_selections: &str,
25581    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25582    expected_reverted_text_with_selections: &str,
25583    base_text: &str,
25584    cx: &mut EditorLspTestContext,
25585) {
25586    cx.set_state(not_reverted_text_with_selections);
25587    cx.set_head_text(base_text);
25588    cx.executor().run_until_parked();
25589
25590    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25591        let snapshot = editor.snapshot(window, cx);
25592        let reverted_hunk_statuses = snapshot
25593            .buffer_snapshot()
25594            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25595            .map(|hunk| hunk.status().kind)
25596            .collect::<Vec<_>>();
25597
25598        editor.git_restore(&Default::default(), window, cx);
25599        reverted_hunk_statuses
25600    });
25601    cx.executor().run_until_parked();
25602    cx.assert_editor_state(expected_reverted_text_with_selections);
25603    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25604}
25605
25606#[gpui::test(iterations = 10)]
25607async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25608    init_test(cx, |_| {});
25609
25610    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25611    let counter = diagnostic_requests.clone();
25612
25613    let fs = FakeFs::new(cx.executor());
25614    fs.insert_tree(
25615        path!("/a"),
25616        json!({
25617            "first.rs": "fn main() { let a = 5; }",
25618            "second.rs": "// Test file",
25619        }),
25620    )
25621    .await;
25622
25623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25625    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25626
25627    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25628    language_registry.add(rust_lang());
25629    let mut fake_servers = language_registry.register_fake_lsp(
25630        "Rust",
25631        FakeLspAdapter {
25632            capabilities: lsp::ServerCapabilities {
25633                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25634                    lsp::DiagnosticOptions {
25635                        identifier: None,
25636                        inter_file_dependencies: true,
25637                        workspace_diagnostics: true,
25638                        work_done_progress_options: Default::default(),
25639                    },
25640                )),
25641                ..Default::default()
25642            },
25643            ..Default::default()
25644        },
25645    );
25646
25647    let editor = workspace
25648        .update(cx, |workspace, window, cx| {
25649            workspace.open_abs_path(
25650                PathBuf::from(path!("/a/first.rs")),
25651                OpenOptions::default(),
25652                window,
25653                cx,
25654            )
25655        })
25656        .unwrap()
25657        .await
25658        .unwrap()
25659        .downcast::<Editor>()
25660        .unwrap();
25661    let fake_server = fake_servers.next().await.unwrap();
25662    let server_id = fake_server.server.server_id();
25663    let mut first_request = fake_server
25664        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25665            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25666            let result_id = Some(new_result_id.to_string());
25667            assert_eq!(
25668                params.text_document.uri,
25669                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25670            );
25671            async move {
25672                Ok(lsp::DocumentDiagnosticReportResult::Report(
25673                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25674                        related_documents: None,
25675                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25676                            items: Vec::new(),
25677                            result_id,
25678                        },
25679                    }),
25680                ))
25681            }
25682        });
25683
25684    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25685        project.update(cx, |project, cx| {
25686            let buffer_id = editor
25687                .read(cx)
25688                .buffer()
25689                .read(cx)
25690                .as_singleton()
25691                .expect("created a singleton buffer")
25692                .read(cx)
25693                .remote_id();
25694            let buffer_result_id = project
25695                .lsp_store()
25696                .read(cx)
25697                .result_id(server_id, buffer_id, cx);
25698            assert_eq!(expected, buffer_result_id);
25699        });
25700    };
25701
25702    ensure_result_id(None, cx);
25703    cx.executor().advance_clock(Duration::from_millis(60));
25704    cx.executor().run_until_parked();
25705    assert_eq!(
25706        diagnostic_requests.load(atomic::Ordering::Acquire),
25707        1,
25708        "Opening file should trigger diagnostic request"
25709    );
25710    first_request
25711        .next()
25712        .await
25713        .expect("should have sent the first diagnostics pull request");
25714    ensure_result_id(Some("1".to_string()), cx);
25715
25716    // Editing should trigger diagnostics
25717    editor.update_in(cx, |editor, window, cx| {
25718        editor.handle_input("2", window, cx)
25719    });
25720    cx.executor().advance_clock(Duration::from_millis(60));
25721    cx.executor().run_until_parked();
25722    assert_eq!(
25723        diagnostic_requests.load(atomic::Ordering::Acquire),
25724        2,
25725        "Editing should trigger diagnostic request"
25726    );
25727    ensure_result_id(Some("2".to_string()), cx);
25728
25729    // Moving cursor should not trigger diagnostic request
25730    editor.update_in(cx, |editor, window, cx| {
25731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25732            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25733        });
25734    });
25735    cx.executor().advance_clock(Duration::from_millis(60));
25736    cx.executor().run_until_parked();
25737    assert_eq!(
25738        diagnostic_requests.load(atomic::Ordering::Acquire),
25739        2,
25740        "Cursor movement should not trigger diagnostic request"
25741    );
25742    ensure_result_id(Some("2".to_string()), cx);
25743    // Multiple rapid edits should be debounced
25744    for _ in 0..5 {
25745        editor.update_in(cx, |editor, window, cx| {
25746            editor.handle_input("x", window, cx)
25747        });
25748    }
25749    cx.executor().advance_clock(Duration::from_millis(60));
25750    cx.executor().run_until_parked();
25751
25752    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25753    assert!(
25754        final_requests <= 4,
25755        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25756    );
25757    ensure_result_id(Some(final_requests.to_string()), cx);
25758}
25759
25760#[gpui::test]
25761async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25762    // Regression test for issue #11671
25763    // Previously, adding a cursor after moving multiple cursors would reset
25764    // the cursor count instead of adding to the existing cursors.
25765    init_test(cx, |_| {});
25766    let mut cx = EditorTestContext::new(cx).await;
25767
25768    // Create a simple buffer with cursor at start
25769    cx.set_state(indoc! {"
25770        ˇaaaa
25771        bbbb
25772        cccc
25773        dddd
25774        eeee
25775        ffff
25776        gggg
25777        hhhh"});
25778
25779    // Add 2 cursors below (so we have 3 total)
25780    cx.update_editor(|editor, window, cx| {
25781        editor.add_selection_below(&Default::default(), window, cx);
25782        editor.add_selection_below(&Default::default(), window, cx);
25783    });
25784
25785    // Verify we have 3 cursors
25786    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25787    assert_eq!(
25788        initial_count, 3,
25789        "Should have 3 cursors after adding 2 below"
25790    );
25791
25792    // Move down one line
25793    cx.update_editor(|editor, window, cx| {
25794        editor.move_down(&MoveDown, window, cx);
25795    });
25796
25797    // Add another cursor below
25798    cx.update_editor(|editor, window, cx| {
25799        editor.add_selection_below(&Default::default(), window, cx);
25800    });
25801
25802    // Should now have 4 cursors (3 original + 1 new)
25803    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25804    assert_eq!(
25805        final_count, 4,
25806        "Should have 4 cursors after moving and adding another"
25807    );
25808}
25809
25810#[gpui::test]
25811async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25812    init_test(cx, |_| {});
25813
25814    let mut cx = EditorTestContext::new(cx).await;
25815
25816    cx.set_state(indoc!(
25817        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25818           Second line here"#
25819    ));
25820
25821    cx.update_editor(|editor, window, cx| {
25822        // Enable soft wrapping with a narrow width to force soft wrapping and
25823        // confirm that more than 2 rows are being displayed.
25824        editor.set_wrap_width(Some(100.0.into()), cx);
25825        assert!(editor.display_text(cx).lines().count() > 2);
25826
25827        editor.add_selection_below(
25828            &AddSelectionBelow {
25829                skip_soft_wrap: true,
25830            },
25831            window,
25832            cx,
25833        );
25834
25835        assert_eq!(
25836            editor.selections.display_ranges(cx),
25837            &[
25838                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25839                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25840            ]
25841        );
25842
25843        editor.add_selection_above(
25844            &AddSelectionAbove {
25845                skip_soft_wrap: true,
25846            },
25847            window,
25848            cx,
25849        );
25850
25851        assert_eq!(
25852            editor.selections.display_ranges(cx),
25853            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25854        );
25855
25856        editor.add_selection_below(
25857            &AddSelectionBelow {
25858                skip_soft_wrap: false,
25859            },
25860            window,
25861            cx,
25862        );
25863
25864        assert_eq!(
25865            editor.selections.display_ranges(cx),
25866            &[
25867                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25868                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25869            ]
25870        );
25871
25872        editor.add_selection_above(
25873            &AddSelectionAbove {
25874                skip_soft_wrap: false,
25875            },
25876            window,
25877            cx,
25878        );
25879
25880        assert_eq!(
25881            editor.selections.display_ranges(cx),
25882            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25883        );
25884    });
25885}
25886
25887#[gpui::test(iterations = 10)]
25888async fn test_document_colors(cx: &mut TestAppContext) {
25889    let expected_color = Rgba {
25890        r: 0.33,
25891        g: 0.33,
25892        b: 0.33,
25893        a: 0.33,
25894    };
25895
25896    init_test(cx, |_| {});
25897
25898    let fs = FakeFs::new(cx.executor());
25899    fs.insert_tree(
25900        path!("/a"),
25901        json!({
25902            "first.rs": "fn main() { let a = 5; }",
25903        }),
25904    )
25905    .await;
25906
25907    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25908    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25909    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25910
25911    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25912    language_registry.add(rust_lang());
25913    let mut fake_servers = language_registry.register_fake_lsp(
25914        "Rust",
25915        FakeLspAdapter {
25916            capabilities: lsp::ServerCapabilities {
25917                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25918                ..lsp::ServerCapabilities::default()
25919            },
25920            name: "rust-analyzer",
25921            ..FakeLspAdapter::default()
25922        },
25923    );
25924    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25925        "Rust",
25926        FakeLspAdapter {
25927            capabilities: lsp::ServerCapabilities {
25928                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25929                ..lsp::ServerCapabilities::default()
25930            },
25931            name: "not-rust-analyzer",
25932            ..FakeLspAdapter::default()
25933        },
25934    );
25935
25936    let editor = workspace
25937        .update(cx, |workspace, window, cx| {
25938            workspace.open_abs_path(
25939                PathBuf::from(path!("/a/first.rs")),
25940                OpenOptions::default(),
25941                window,
25942                cx,
25943            )
25944        })
25945        .unwrap()
25946        .await
25947        .unwrap()
25948        .downcast::<Editor>()
25949        .unwrap();
25950    let fake_language_server = fake_servers.next().await.unwrap();
25951    let fake_language_server_without_capabilities =
25952        fake_servers_without_capabilities.next().await.unwrap();
25953    let requests_made = Arc::new(AtomicUsize::new(0));
25954    let closure_requests_made = Arc::clone(&requests_made);
25955    let mut color_request_handle = fake_language_server
25956        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25957            let requests_made = Arc::clone(&closure_requests_made);
25958            async move {
25959                assert_eq!(
25960                    params.text_document.uri,
25961                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25962                );
25963                requests_made.fetch_add(1, atomic::Ordering::Release);
25964                Ok(vec![
25965                    lsp::ColorInformation {
25966                        range: lsp::Range {
25967                            start: lsp::Position {
25968                                line: 0,
25969                                character: 0,
25970                            },
25971                            end: lsp::Position {
25972                                line: 0,
25973                                character: 1,
25974                            },
25975                        },
25976                        color: lsp::Color {
25977                            red: 0.33,
25978                            green: 0.33,
25979                            blue: 0.33,
25980                            alpha: 0.33,
25981                        },
25982                    },
25983                    lsp::ColorInformation {
25984                        range: lsp::Range {
25985                            start: lsp::Position {
25986                                line: 0,
25987                                character: 0,
25988                            },
25989                            end: lsp::Position {
25990                                line: 0,
25991                                character: 1,
25992                            },
25993                        },
25994                        color: lsp::Color {
25995                            red: 0.33,
25996                            green: 0.33,
25997                            blue: 0.33,
25998                            alpha: 0.33,
25999                        },
26000                    },
26001                ])
26002            }
26003        });
26004
26005    let _handle = fake_language_server_without_capabilities
26006        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26007            panic!("Should not be called");
26008        });
26009    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26010    color_request_handle.next().await.unwrap();
26011    cx.run_until_parked();
26012    assert_eq!(
26013        1,
26014        requests_made.load(atomic::Ordering::Acquire),
26015        "Should query for colors once per editor open"
26016    );
26017    editor.update_in(cx, |editor, _, cx| {
26018        assert_eq!(
26019            vec![expected_color],
26020            extract_color_inlays(editor, cx),
26021            "Should have an initial inlay"
26022        );
26023    });
26024
26025    // opening another file in a split should not influence the LSP query counter
26026    workspace
26027        .update(cx, |workspace, window, cx| {
26028            assert_eq!(
26029                workspace.panes().len(),
26030                1,
26031                "Should have one pane with one editor"
26032            );
26033            workspace.move_item_to_pane_in_direction(
26034                &MoveItemToPaneInDirection {
26035                    direction: SplitDirection::Right,
26036                    focus: false,
26037                    clone: true,
26038                },
26039                window,
26040                cx,
26041            );
26042        })
26043        .unwrap();
26044    cx.run_until_parked();
26045    workspace
26046        .update(cx, |workspace, _, cx| {
26047            let panes = workspace.panes();
26048            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26049            for pane in panes {
26050                let editor = pane
26051                    .read(cx)
26052                    .active_item()
26053                    .and_then(|item| item.downcast::<Editor>())
26054                    .expect("Should have opened an editor in each split");
26055                let editor_file = editor
26056                    .read(cx)
26057                    .buffer()
26058                    .read(cx)
26059                    .as_singleton()
26060                    .expect("test deals with singleton buffers")
26061                    .read(cx)
26062                    .file()
26063                    .expect("test buffese should have a file")
26064                    .path();
26065                assert_eq!(
26066                    editor_file.as_ref(),
26067                    rel_path("first.rs"),
26068                    "Both editors should be opened for the same file"
26069                )
26070            }
26071        })
26072        .unwrap();
26073
26074    cx.executor().advance_clock(Duration::from_millis(500));
26075    let save = editor.update_in(cx, |editor, window, cx| {
26076        editor.move_to_end(&MoveToEnd, window, cx);
26077        editor.handle_input("dirty", window, cx);
26078        editor.save(
26079            SaveOptions {
26080                format: true,
26081                autosave: true,
26082            },
26083            project.clone(),
26084            window,
26085            cx,
26086        )
26087    });
26088    save.await.unwrap();
26089
26090    color_request_handle.next().await.unwrap();
26091    cx.run_until_parked();
26092    assert_eq!(
26093        2,
26094        requests_made.load(atomic::Ordering::Acquire),
26095        "Should query for colors once per save (deduplicated) and once per formatting after save"
26096    );
26097
26098    drop(editor);
26099    let close = workspace
26100        .update(cx, |workspace, window, cx| {
26101            workspace.active_pane().update(cx, |pane, cx| {
26102                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26103            })
26104        })
26105        .unwrap();
26106    close.await.unwrap();
26107    let close = workspace
26108        .update(cx, |workspace, window, cx| {
26109            workspace.active_pane().update(cx, |pane, cx| {
26110                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26111            })
26112        })
26113        .unwrap();
26114    close.await.unwrap();
26115    assert_eq!(
26116        2,
26117        requests_made.load(atomic::Ordering::Acquire),
26118        "After saving and closing all editors, no extra requests should be made"
26119    );
26120    workspace
26121        .update(cx, |workspace, _, cx| {
26122            assert!(
26123                workspace.active_item(cx).is_none(),
26124                "Should close all editors"
26125            )
26126        })
26127        .unwrap();
26128
26129    workspace
26130        .update(cx, |workspace, window, cx| {
26131            workspace.active_pane().update(cx, |pane, cx| {
26132                pane.navigate_backward(&workspace::GoBack, window, cx);
26133            })
26134        })
26135        .unwrap();
26136    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26137    cx.run_until_parked();
26138    let editor = workspace
26139        .update(cx, |workspace, _, cx| {
26140            workspace
26141                .active_item(cx)
26142                .expect("Should have reopened the editor again after navigating back")
26143                .downcast::<Editor>()
26144                .expect("Should be an editor")
26145        })
26146        .unwrap();
26147
26148    assert_eq!(
26149        2,
26150        requests_made.load(atomic::Ordering::Acquire),
26151        "Cache should be reused on buffer close and reopen"
26152    );
26153    editor.update(cx, |editor, cx| {
26154        assert_eq!(
26155            vec![expected_color],
26156            extract_color_inlays(editor, cx),
26157            "Should have an initial inlay"
26158        );
26159    });
26160
26161    drop(color_request_handle);
26162    let closure_requests_made = Arc::clone(&requests_made);
26163    let mut empty_color_request_handle = fake_language_server
26164        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26165            let requests_made = Arc::clone(&closure_requests_made);
26166            async move {
26167                assert_eq!(
26168                    params.text_document.uri,
26169                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26170                );
26171                requests_made.fetch_add(1, atomic::Ordering::Release);
26172                Ok(Vec::new())
26173            }
26174        });
26175    let save = editor.update_in(cx, |editor, window, cx| {
26176        editor.move_to_end(&MoveToEnd, window, cx);
26177        editor.handle_input("dirty_again", window, cx);
26178        editor.save(
26179            SaveOptions {
26180                format: false,
26181                autosave: true,
26182            },
26183            project.clone(),
26184            window,
26185            cx,
26186        )
26187    });
26188    save.await.unwrap();
26189
26190    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26191    empty_color_request_handle.next().await.unwrap();
26192    cx.run_until_parked();
26193    assert_eq!(
26194        3,
26195        requests_made.load(atomic::Ordering::Acquire),
26196        "Should query for colors once per save only, as formatting was not requested"
26197    );
26198    editor.update(cx, |editor, cx| {
26199        assert_eq!(
26200            Vec::<Rgba>::new(),
26201            extract_color_inlays(editor, cx),
26202            "Should clear all colors when the server returns an empty response"
26203        );
26204    });
26205}
26206
26207#[gpui::test]
26208async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26209    init_test(cx, |_| {});
26210    let (editor, cx) = cx.add_window_view(Editor::single_line);
26211    editor.update_in(cx, |editor, window, cx| {
26212        editor.set_text("oops\n\nwow\n", window, cx)
26213    });
26214    cx.run_until_parked();
26215    editor.update(cx, |editor, cx| {
26216        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26217    });
26218    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26219    cx.run_until_parked();
26220    editor.update(cx, |editor, cx| {
26221        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26222    });
26223}
26224
26225#[gpui::test]
26226async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26227    init_test(cx, |_| {});
26228
26229    cx.update(|cx| {
26230        register_project_item::<Editor>(cx);
26231    });
26232
26233    let fs = FakeFs::new(cx.executor());
26234    fs.insert_tree("/root1", json!({})).await;
26235    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26236        .await;
26237
26238    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26239    let (workspace, cx) =
26240        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26241
26242    let worktree_id = project.update(cx, |project, cx| {
26243        project.worktrees(cx).next().unwrap().read(cx).id()
26244    });
26245
26246    let handle = workspace
26247        .update_in(cx, |workspace, window, cx| {
26248            let project_path = (worktree_id, rel_path("one.pdf"));
26249            workspace.open_path(project_path, None, true, window, cx)
26250        })
26251        .await
26252        .unwrap();
26253
26254    assert_eq!(
26255        handle.to_any().entity_type(),
26256        TypeId::of::<InvalidItemView>()
26257    );
26258}
26259
26260#[gpui::test]
26261async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26262    init_test(cx, |_| {});
26263
26264    let language = Arc::new(Language::new(
26265        LanguageConfig::default(),
26266        Some(tree_sitter_rust::LANGUAGE.into()),
26267    ));
26268
26269    // Test hierarchical sibling navigation
26270    let text = r#"
26271        fn outer() {
26272            if condition {
26273                let a = 1;
26274            }
26275            let b = 2;
26276        }
26277
26278        fn another() {
26279            let c = 3;
26280        }
26281    "#;
26282
26283    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26285    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26286
26287    // Wait for parsing to complete
26288    editor
26289        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26290        .await;
26291
26292    editor.update_in(cx, |editor, window, cx| {
26293        // Start by selecting "let a = 1;" inside the if block
26294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26295            s.select_display_ranges([
26296                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26297            ]);
26298        });
26299
26300        let initial_selection = editor.selections.display_ranges(cx);
26301        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26302
26303        // Test select next sibling - should move up levels to find the next sibling
26304        // Since "let a = 1;" has no siblings in the if block, it should move up
26305        // to find "let b = 2;" which is a sibling of the if block
26306        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26307        let next_selection = editor.selections.display_ranges(cx);
26308
26309        // Should have a selection and it should be different from the initial
26310        assert_eq!(
26311            next_selection.len(),
26312            1,
26313            "Should have one selection after next"
26314        );
26315        assert_ne!(
26316            next_selection[0], initial_selection[0],
26317            "Next sibling selection should be different"
26318        );
26319
26320        // Test hierarchical navigation by going to the end of the current function
26321        // and trying to navigate to the next function
26322        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26323            s.select_display_ranges([
26324                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26325            ]);
26326        });
26327
26328        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26329        let function_next_selection = editor.selections.display_ranges(cx);
26330
26331        // Should move to the next function
26332        assert_eq!(
26333            function_next_selection.len(),
26334            1,
26335            "Should have one selection after function next"
26336        );
26337
26338        // Test select previous sibling navigation
26339        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26340        let prev_selection = editor.selections.display_ranges(cx);
26341
26342        // Should have a selection and it should be different
26343        assert_eq!(
26344            prev_selection.len(),
26345            1,
26346            "Should have one selection after prev"
26347        );
26348        assert_ne!(
26349            prev_selection[0], function_next_selection[0],
26350            "Previous sibling selection should be different from next"
26351        );
26352    });
26353}
26354
26355#[gpui::test]
26356async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26357    init_test(cx, |_| {});
26358
26359    let mut cx = EditorTestContext::new(cx).await;
26360    cx.set_state(
26361        "let ˇvariable = 42;
26362let another = variable + 1;
26363let result = variable * 2;",
26364    );
26365
26366    // Set up document highlights manually (simulating LSP response)
26367    cx.update_editor(|editor, _window, cx| {
26368        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26369
26370        // Create highlights for "variable" occurrences
26371        let highlight_ranges = [
26372            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26373            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26374            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26375        ];
26376
26377        let anchor_ranges: Vec<_> = highlight_ranges
26378            .iter()
26379            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26380            .collect();
26381
26382        editor.highlight_background::<DocumentHighlightRead>(
26383            &anchor_ranges,
26384            |theme| theme.colors().editor_document_highlight_read_background,
26385            cx,
26386        );
26387    });
26388
26389    // Go to next highlight - should move to second "variable"
26390    cx.update_editor(|editor, window, cx| {
26391        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26392    });
26393    cx.assert_editor_state(
26394        "let variable = 42;
26395let another = ˇvariable + 1;
26396let result = variable * 2;",
26397    );
26398
26399    // Go to next highlight - should move to third "variable"
26400    cx.update_editor(|editor, window, cx| {
26401        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26402    });
26403    cx.assert_editor_state(
26404        "let variable = 42;
26405let another = variable + 1;
26406let result = ˇvariable * 2;",
26407    );
26408
26409    // Go to next highlight - should stay at third "variable" (no wrap-around)
26410    cx.update_editor(|editor, window, cx| {
26411        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26412    });
26413    cx.assert_editor_state(
26414        "let variable = 42;
26415let another = variable + 1;
26416let result = ˇvariable * 2;",
26417    );
26418
26419    // Now test going backwards from third position
26420    cx.update_editor(|editor, window, cx| {
26421        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26422    });
26423    cx.assert_editor_state(
26424        "let variable = 42;
26425let another = ˇvariable + 1;
26426let result = variable * 2;",
26427    );
26428
26429    // Go to previous highlight - should move to first "variable"
26430    cx.update_editor(|editor, window, cx| {
26431        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26432    });
26433    cx.assert_editor_state(
26434        "let ˇvariable = 42;
26435let another = variable + 1;
26436let result = variable * 2;",
26437    );
26438
26439    // Go to previous highlight - should stay on first "variable"
26440    cx.update_editor(|editor, window, cx| {
26441        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26442    });
26443    cx.assert_editor_state(
26444        "let ˇvariable = 42;
26445let another = variable + 1;
26446let result = variable * 2;",
26447    );
26448}
26449
26450#[gpui::test]
26451async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26452    cx: &mut gpui::TestAppContext,
26453) {
26454    init_test(cx, |_| {});
26455
26456    let url = "https://zed.dev";
26457
26458    let markdown_language = Arc::new(Language::new(
26459        LanguageConfig {
26460            name: "Markdown".into(),
26461            ..LanguageConfig::default()
26462        },
26463        None,
26464    ));
26465
26466    let mut cx = EditorTestContext::new(cx).await;
26467    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26468    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26469
26470    cx.update_editor(|editor, window, cx| {
26471        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26472        editor.paste(&Paste, window, cx);
26473    });
26474
26475    cx.assert_editor_state(&format!(
26476        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26477    ));
26478}
26479
26480#[gpui::test]
26481async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26482    cx: &mut gpui::TestAppContext,
26483) {
26484    init_test(cx, |_| {});
26485
26486    let url = "https://zed.dev";
26487
26488    let markdown_language = Arc::new(Language::new(
26489        LanguageConfig {
26490            name: "Markdown".into(),
26491            ..LanguageConfig::default()
26492        },
26493        None,
26494    ));
26495
26496    let mut cx = EditorTestContext::new(cx).await;
26497    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26498    cx.set_state(&format!(
26499        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26500    ));
26501
26502    cx.update_editor(|editor, window, cx| {
26503        editor.copy(&Copy, window, cx);
26504    });
26505
26506    cx.set_state(&format!(
26507        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26508    ));
26509
26510    cx.update_editor(|editor, window, cx| {
26511        editor.paste(&Paste, window, cx);
26512    });
26513
26514    cx.assert_editor_state(&format!(
26515        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26516    ));
26517}
26518
26519#[gpui::test]
26520async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26521    cx: &mut gpui::TestAppContext,
26522) {
26523    init_test(cx, |_| {});
26524
26525    let url = "https://zed.dev";
26526
26527    let markdown_language = Arc::new(Language::new(
26528        LanguageConfig {
26529            name: "Markdown".into(),
26530            ..LanguageConfig::default()
26531        },
26532        None,
26533    ));
26534
26535    let mut cx = EditorTestContext::new(cx).await;
26536    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26537    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26538
26539    cx.update_editor(|editor, window, cx| {
26540        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26541        editor.paste(&Paste, window, cx);
26542    });
26543
26544    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26545}
26546
26547#[gpui::test]
26548async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26549    cx: &mut gpui::TestAppContext,
26550) {
26551    init_test(cx, |_| {});
26552
26553    let text = "Awesome";
26554
26555    let markdown_language = Arc::new(Language::new(
26556        LanguageConfig {
26557            name: "Markdown".into(),
26558            ..LanguageConfig::default()
26559        },
26560        None,
26561    ));
26562
26563    let mut cx = EditorTestContext::new(cx).await;
26564    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26565    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26566
26567    cx.update_editor(|editor, window, cx| {
26568        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26569        editor.paste(&Paste, window, cx);
26570    });
26571
26572    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26573}
26574
26575#[gpui::test]
26576async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26577    cx: &mut gpui::TestAppContext,
26578) {
26579    init_test(cx, |_| {});
26580
26581    let url = "https://zed.dev";
26582
26583    let markdown_language = Arc::new(Language::new(
26584        LanguageConfig {
26585            name: "Rust".into(),
26586            ..LanguageConfig::default()
26587        },
26588        None,
26589    ));
26590
26591    let mut cx = EditorTestContext::new(cx).await;
26592    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26593    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26594
26595    cx.update_editor(|editor, window, cx| {
26596        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26597        editor.paste(&Paste, window, cx);
26598    });
26599
26600    cx.assert_editor_state(&format!(
26601        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26602    ));
26603}
26604
26605#[gpui::test]
26606async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26607    cx: &mut TestAppContext,
26608) {
26609    init_test(cx, |_| {});
26610
26611    let url = "https://zed.dev";
26612
26613    let markdown_language = Arc::new(Language::new(
26614        LanguageConfig {
26615            name: "Markdown".into(),
26616            ..LanguageConfig::default()
26617        },
26618        None,
26619    ));
26620
26621    let (editor, cx) = cx.add_window_view(|window, cx| {
26622        let multi_buffer = MultiBuffer::build_multi(
26623            [
26624                ("this will embed -> link", vec![Point::row_range(0..1)]),
26625                ("this will replace -> link", vec![Point::row_range(0..1)]),
26626            ],
26627            cx,
26628        );
26629        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26631            s.select_ranges(vec![
26632                Point::new(0, 19)..Point::new(0, 23),
26633                Point::new(1, 21)..Point::new(1, 25),
26634            ])
26635        });
26636        let first_buffer_id = multi_buffer
26637            .read(cx)
26638            .excerpt_buffer_ids()
26639            .into_iter()
26640            .next()
26641            .unwrap();
26642        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26643        first_buffer.update(cx, |buffer, cx| {
26644            buffer.set_language(Some(markdown_language.clone()), cx);
26645        });
26646
26647        editor
26648    });
26649    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26650
26651    cx.update_editor(|editor, window, cx| {
26652        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26653        editor.paste(&Paste, window, cx);
26654    });
26655
26656    cx.assert_editor_state(&format!(
26657        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26658    ));
26659}
26660
26661#[gpui::test]
26662async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26663    init_test(cx, |_| {});
26664
26665    let fs = FakeFs::new(cx.executor());
26666    fs.insert_tree(
26667        path!("/project"),
26668        json!({
26669            "first.rs": "# First Document\nSome content here.",
26670            "second.rs": "Plain text content for second file.",
26671        }),
26672    )
26673    .await;
26674
26675    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26676    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26677    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26678
26679    let language = rust_lang();
26680    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26681    language_registry.add(language.clone());
26682    let mut fake_servers = language_registry.register_fake_lsp(
26683        "Rust",
26684        FakeLspAdapter {
26685            ..FakeLspAdapter::default()
26686        },
26687    );
26688
26689    let buffer1 = project
26690        .update(cx, |project, cx| {
26691            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26692        })
26693        .await
26694        .unwrap();
26695    let buffer2 = project
26696        .update(cx, |project, cx| {
26697            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26698        })
26699        .await
26700        .unwrap();
26701
26702    let multi_buffer = cx.new(|cx| {
26703        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26704        multi_buffer.set_excerpts_for_path(
26705            PathKey::for_buffer(&buffer1, cx),
26706            buffer1.clone(),
26707            [Point::zero()..buffer1.read(cx).max_point()],
26708            3,
26709            cx,
26710        );
26711        multi_buffer.set_excerpts_for_path(
26712            PathKey::for_buffer(&buffer2, cx),
26713            buffer2.clone(),
26714            [Point::zero()..buffer1.read(cx).max_point()],
26715            3,
26716            cx,
26717        );
26718        multi_buffer
26719    });
26720
26721    let (editor, cx) = cx.add_window_view(|window, cx| {
26722        Editor::new(
26723            EditorMode::full(),
26724            multi_buffer,
26725            Some(project.clone()),
26726            window,
26727            cx,
26728        )
26729    });
26730
26731    let fake_language_server = fake_servers.next().await.unwrap();
26732
26733    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26734
26735    let save = editor.update_in(cx, |editor, window, cx| {
26736        assert!(editor.is_dirty(cx));
26737
26738        editor.save(
26739            SaveOptions {
26740                format: true,
26741                autosave: true,
26742            },
26743            project,
26744            window,
26745            cx,
26746        )
26747    });
26748    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26749    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26750    let mut done_edit_rx = Some(done_edit_rx);
26751    let mut start_edit_tx = Some(start_edit_tx);
26752
26753    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26754        start_edit_tx.take().unwrap().send(()).unwrap();
26755        let done_edit_rx = done_edit_rx.take().unwrap();
26756        async move {
26757            done_edit_rx.await.unwrap();
26758            Ok(None)
26759        }
26760    });
26761
26762    start_edit_rx.await.unwrap();
26763    buffer2
26764        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26765        .unwrap();
26766
26767    done_edit_tx.send(()).unwrap();
26768
26769    save.await.unwrap();
26770    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26771}
26772
26773#[track_caller]
26774fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26775    editor
26776        .all_inlays(cx)
26777        .into_iter()
26778        .filter_map(|inlay| inlay.get_color())
26779        .map(Rgba::from)
26780        .collect()
26781}
26782
26783#[gpui::test]
26784fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26785    init_test(cx, |_| {});
26786
26787    let editor = cx.add_window(|window, cx| {
26788        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26789        build_editor(buffer, window, cx)
26790    });
26791
26792    editor
26793        .update(cx, |editor, window, cx| {
26794            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26795                s.select_display_ranges([
26796                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26797                ])
26798            });
26799
26800            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26801
26802            assert_eq!(
26803                editor.display_text(cx),
26804                "line1\nline2\nline2",
26805                "Duplicating last line upward should create duplicate above, not on same line"
26806            );
26807
26808            assert_eq!(
26809                editor.selections.display_ranges(cx),
26810                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26811                "Selection should remain on the original line"
26812            );
26813        })
26814        .unwrap();
26815}
26816
26817#[gpui::test]
26818async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26819    init_test(cx, |_| {});
26820
26821    let mut cx = EditorTestContext::new(cx).await;
26822
26823    cx.set_state("line1\nline2ˇ");
26824
26825    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26826
26827    let clipboard_text = cx
26828        .read_from_clipboard()
26829        .and_then(|item| item.text().as_deref().map(str::to_string));
26830
26831    assert_eq!(
26832        clipboard_text,
26833        Some("line2\n".to_string()),
26834        "Copying a line without trailing newline should include a newline"
26835    );
26836
26837    cx.set_state("line1\nˇ");
26838
26839    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26840
26841    cx.assert_editor_state("line1\nline2\nˇ");
26842}
26843
26844#[gpui::test]
26845async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26846    init_test(cx, |_| {});
26847
26848    let mut cx = EditorTestContext::new(cx).await;
26849
26850    cx.set_state("line1\nline2ˇ");
26851    cx.update_editor(|e, window, cx| {
26852        e.set_mode(EditorMode::SingleLine);
26853        assert!(e.key_context(window, cx).contains("end_of_input"));
26854    });
26855    cx.set_state("ˇline1\nline2");
26856    cx.update_editor(|e, window, cx| {
26857        assert!(!e.key_context(window, cx).contains("end_of_input"));
26858    });
26859    cx.set_state("line1ˇ\nline2");
26860    cx.update_editor(|e, window, cx| {
26861        assert!(!e.key_context(window, cx).contains("end_of_input"));
26862    });
26863}