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]
 3140async fn test_newline_yaml(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let mut cx = EditorTestContext::new(cx).await;
 3144    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3145    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3146
 3147    // Object (between 2 fields)
 3148    cx.set_state(indoc! {"
 3149    test:ˇ
 3150    hello: bye"});
 3151    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3152    cx.assert_editor_state(indoc! {"
 3153    test:
 3154        ˇ
 3155    hello: bye"});
 3156
 3157    // Object (first and single line)
 3158    cx.set_state(indoc! {"
 3159    test:ˇ"});
 3160    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3161    cx.assert_editor_state(indoc! {"
 3162    test:
 3163        ˇ"});
 3164
 3165    // Array with objects (after first element)
 3166    cx.set_state(indoc! {"
 3167    test:
 3168        - foo: barˇ"});
 3169    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3170    cx.assert_editor_state(indoc! {"
 3171    test:
 3172        - foo: bar
 3173        ˇ"});
 3174
 3175    // Array with objects and comment
 3176    cx.set_state(indoc! {"
 3177    test:
 3178        - foo: bar
 3179        - bar: # testˇ"});
 3180    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182    test:
 3183        - foo: bar
 3184        - bar: # test
 3185            ˇ"});
 3186
 3187    // Array with objects (after second element)
 3188    cx.set_state(indoc! {"
 3189    test:
 3190        - foo: bar
 3191        - bar: fooˇ"});
 3192    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194    test:
 3195        - foo: bar
 3196        - bar: foo
 3197        ˇ"});
 3198
 3199    // Array with strings (after first element)
 3200    cx.set_state(indoc! {"
 3201    test:
 3202        - fooˇ"});
 3203    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3204    cx.assert_editor_state(indoc! {"
 3205    test:
 3206        - foo
 3207        ˇ"});
 3208}
 3209
 3210#[gpui::test]
 3211fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3212    init_test(cx, |_| {});
 3213
 3214    let editor = cx.add_window(|window, cx| {
 3215        let buffer = MultiBuffer::build_simple(
 3216            "
 3217                a
 3218                b(
 3219                    X
 3220                )
 3221                c(
 3222                    X
 3223                )
 3224            "
 3225            .unindent()
 3226            .as_str(),
 3227            cx,
 3228        );
 3229        let mut editor = build_editor(buffer, window, cx);
 3230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3231            s.select_ranges([
 3232                Point::new(2, 4)..Point::new(2, 5),
 3233                Point::new(5, 4)..Point::new(5, 5),
 3234            ])
 3235        });
 3236        editor
 3237    });
 3238
 3239    _ = editor.update(cx, |editor, window, cx| {
 3240        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3241        editor.buffer.update(cx, |buffer, cx| {
 3242            buffer.edit(
 3243                [
 3244                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3245                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3246                ],
 3247                None,
 3248                cx,
 3249            );
 3250            assert_eq!(
 3251                buffer.read(cx).text(),
 3252                "
 3253                    a
 3254                    b()
 3255                    c()
 3256                "
 3257                .unindent()
 3258            );
 3259        });
 3260        assert_eq!(
 3261            editor.selections.ranges(&editor.display_snapshot(cx)),
 3262            &[
 3263                Point::new(1, 2)..Point::new(1, 2),
 3264                Point::new(2, 2)..Point::new(2, 2),
 3265            ],
 3266        );
 3267
 3268        editor.newline(&Newline, window, cx);
 3269        assert_eq!(
 3270            editor.text(cx),
 3271            "
 3272                a
 3273                b(
 3274                )
 3275                c(
 3276                )
 3277            "
 3278            .unindent()
 3279        );
 3280
 3281        // The selections are moved after the inserted newlines
 3282        assert_eq!(
 3283            editor.selections.ranges(&editor.display_snapshot(cx)),
 3284            &[
 3285                Point::new(2, 0)..Point::new(2, 0),
 3286                Point::new(4, 0)..Point::new(4, 0),
 3287            ],
 3288        );
 3289    });
 3290}
 3291
 3292#[gpui::test]
 3293async fn test_newline_above(cx: &mut TestAppContext) {
 3294    init_test(cx, |settings| {
 3295        settings.defaults.tab_size = NonZeroU32::new(4)
 3296    });
 3297
 3298    let language = Arc::new(
 3299        Language::new(
 3300            LanguageConfig::default(),
 3301            Some(tree_sitter_rust::LANGUAGE.into()),
 3302        )
 3303        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3304        .unwrap(),
 3305    );
 3306
 3307    let mut cx = EditorTestContext::new(cx).await;
 3308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3309    cx.set_state(indoc! {"
 3310        const a: ˇA = (
 3311 3312                «const_functionˇ»(ˇ),
 3313                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3314 3315        ˇ);ˇ
 3316    "});
 3317
 3318    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        ˇ
 3321        const a: A = (
 3322            ˇ
 3323            (
 3324                ˇ
 3325                ˇ
 3326                const_function(),
 3327                ˇ
 3328                ˇ
 3329                ˇ
 3330                ˇ
 3331                something_else,
 3332                ˇ
 3333            )
 3334            ˇ
 3335            ˇ
 3336        );
 3337    "});
 3338}
 3339
 3340#[gpui::test]
 3341async fn test_newline_below(cx: &mut TestAppContext) {
 3342    init_test(cx, |settings| {
 3343        settings.defaults.tab_size = NonZeroU32::new(4)
 3344    });
 3345
 3346    let language = Arc::new(
 3347        Language::new(
 3348            LanguageConfig::default(),
 3349            Some(tree_sitter_rust::LANGUAGE.into()),
 3350        )
 3351        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3352        .unwrap(),
 3353    );
 3354
 3355    let mut cx = EditorTestContext::new(cx).await;
 3356    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3357    cx.set_state(indoc! {"
 3358        const a: ˇA = (
 3359 3360                «const_functionˇ»(ˇ),
 3361                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3362 3363        ˇ);ˇ
 3364    "});
 3365
 3366    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: A = (
 3369            ˇ
 3370            (
 3371                ˇ
 3372                const_function(),
 3373                ˇ
 3374                ˇ
 3375                something_else,
 3376                ˇ
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380            )
 3381            ˇ
 3382        );
 3383        ˇ
 3384        ˇ
 3385    "});
 3386}
 3387
 3388#[gpui::test]
 3389async fn test_newline_comments(cx: &mut TestAppContext) {
 3390    init_test(cx, |settings| {
 3391        settings.defaults.tab_size = NonZeroU32::new(4)
 3392    });
 3393
 3394    let language = Arc::new(Language::new(
 3395        LanguageConfig {
 3396            line_comments: vec!["// ".into()],
 3397            ..LanguageConfig::default()
 3398        },
 3399        None,
 3400    ));
 3401    {
 3402        let mut cx = EditorTestContext::new(cx).await;
 3403        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3404        cx.set_state(indoc! {"
 3405        // Fooˇ
 3406    "});
 3407
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        // Foo
 3411        // ˇ
 3412    "});
 3413        // Ensure that we add comment prefix when existing line contains space
 3414        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3415        cx.assert_editor_state(
 3416            indoc! {"
 3417        // Foo
 3418        //s
 3419        // ˇ
 3420    "}
 3421            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3422            .as_str(),
 3423        );
 3424        // Ensure that we add comment prefix when existing line does not contain space
 3425        cx.set_state(indoc! {"
 3426        // Foo
 3427        //ˇ
 3428    "});
 3429        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3430        cx.assert_editor_state(indoc! {"
 3431        // Foo
 3432        //
 3433        // ˇ
 3434    "});
 3435        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3436        cx.set_state(indoc! {"
 3437        ˇ// Foo
 3438    "});
 3439        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3440        cx.assert_editor_state(indoc! {"
 3441
 3442        ˇ// Foo
 3443    "});
 3444    }
 3445    // Ensure that comment continuations can be disabled.
 3446    update_test_language_settings(cx, |settings| {
 3447        settings.defaults.extend_comment_on_newline = Some(false);
 3448    });
 3449    let mut cx = EditorTestContext::new(cx).await;
 3450    cx.set_state(indoc! {"
 3451        // Fooˇ
 3452    "});
 3453    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3454    cx.assert_editor_state(indoc! {"
 3455        // Foo
 3456        ˇ
 3457    "});
 3458}
 3459
 3460#[gpui::test]
 3461async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3462    init_test(cx, |settings| {
 3463        settings.defaults.tab_size = NonZeroU32::new(4)
 3464    });
 3465
 3466    let language = Arc::new(Language::new(
 3467        LanguageConfig {
 3468            line_comments: vec!["// ".into(), "/// ".into()],
 3469            ..LanguageConfig::default()
 3470        },
 3471        None,
 3472    ));
 3473    {
 3474        let mut cx = EditorTestContext::new(cx).await;
 3475        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3476        cx.set_state(indoc! {"
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        //
 3482        // ˇ
 3483    "});
 3484
 3485        cx.set_state(indoc! {"
 3486        ///ˇ
 3487    "});
 3488        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3489        cx.assert_editor_state(indoc! {"
 3490        ///
 3491        /// ˇ
 3492    "});
 3493    }
 3494}
 3495
 3496#[gpui::test]
 3497async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3498    init_test(cx, |settings| {
 3499        settings.defaults.tab_size = NonZeroU32::new(4)
 3500    });
 3501
 3502    let language = Arc::new(
 3503        Language::new(
 3504            LanguageConfig {
 3505                documentation_comment: Some(language::BlockCommentConfig {
 3506                    start: "/**".into(),
 3507                    end: "*/".into(),
 3508                    prefix: "* ".into(),
 3509                    tab_size: 1,
 3510                }),
 3511
 3512                ..LanguageConfig::default()
 3513            },
 3514            Some(tree_sitter_rust::LANGUAGE.into()),
 3515        )
 3516        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3517        .unwrap(),
 3518    );
 3519
 3520    {
 3521        let mut cx = EditorTestContext::new(cx).await;
 3522        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3523        cx.set_state(indoc! {"
 3524        /**ˇ
 3525    "});
 3526
 3527        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3528        cx.assert_editor_state(indoc! {"
 3529        /**
 3530         * ˇ
 3531    "});
 3532        // Ensure that if cursor is before the comment start,
 3533        // we do not actually insert a comment prefix.
 3534        cx.set_state(indoc! {"
 3535        ˇ/**
 3536    "});
 3537        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3538        cx.assert_editor_state(indoc! {"
 3539
 3540        ˇ/**
 3541    "});
 3542        // Ensure that if cursor is between it doesn't add comment prefix.
 3543        cx.set_state(indoc! {"
 3544        /*ˇ*
 3545    "});
 3546        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3547        cx.assert_editor_state(indoc! {"
 3548        /*
 3549        ˇ*
 3550    "});
 3551        // Ensure that if suffix exists on same line after cursor it adds new line.
 3552        cx.set_state(indoc! {"
 3553        /**ˇ*/
 3554    "});
 3555        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3556        cx.assert_editor_state(indoc! {"
 3557        /**
 3558         * ˇ
 3559         */
 3560    "});
 3561        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3562        cx.set_state(indoc! {"
 3563        /**ˇ */
 3564    "});
 3565        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3566        cx.assert_editor_state(indoc! {"
 3567        /**
 3568         * ˇ
 3569         */
 3570    "});
 3571        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3572        cx.set_state(indoc! {"
 3573        /** ˇ*/
 3574    "});
 3575        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3576        cx.assert_editor_state(
 3577            indoc! {"
 3578        /**s
 3579         * ˇ
 3580         */
 3581    "}
 3582            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3583            .as_str(),
 3584        );
 3585        // Ensure that delimiter space is preserved when newline on already
 3586        // spaced delimiter.
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(
 3589            indoc! {"
 3590        /**s
 3591         *s
 3592         * ˇ
 3593         */
 3594    "}
 3595            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3596            .as_str(),
 3597        );
 3598        // Ensure that delimiter space is preserved when space is not
 3599        // on existing delimiter.
 3600        cx.set_state(indoc! {"
 3601        /**
 3602 3603         */
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         * ˇ
 3610         */
 3611    "});
 3612        // Ensure that if suffix exists on same line after cursor it
 3613        // doesn't add extra new line if prefix is not on same line.
 3614        cx.set_state(indoc! {"
 3615        /**
 3616        ˇ*/
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        /**
 3621
 3622        ˇ*/
 3623    "});
 3624        // Ensure that it detects suffix after existing prefix.
 3625        cx.set_state(indoc! {"
 3626        /**ˇ/
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        /**
 3631        ˇ/
 3632    "});
 3633        // Ensure that if suffix exists on same line before
 3634        // cursor it does not add comment prefix.
 3635        cx.set_state(indoc! {"
 3636        /** */ˇ
 3637    "});
 3638        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3639        cx.assert_editor_state(indoc! {"
 3640        /** */
 3641        ˇ
 3642    "});
 3643        // Ensure that if suffix exists on same line before
 3644        // cursor it does not add comment prefix.
 3645        cx.set_state(indoc! {"
 3646        /**
 3647         *
 3648         */ˇ
 3649    "});
 3650        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3651        cx.assert_editor_state(indoc! {"
 3652        /**
 3653         *
 3654         */
 3655         ˇ
 3656    "});
 3657
 3658        // Ensure that inline comment followed by code
 3659        // doesn't add comment prefix on newline
 3660        cx.set_state(indoc! {"
 3661        /** */ textˇ
 3662    "});
 3663        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3664        cx.assert_editor_state(indoc! {"
 3665        /** */ text
 3666        ˇ
 3667    "});
 3668
 3669        // Ensure that text after comment end tag
 3670        // doesn't add comment prefix on newline
 3671        cx.set_state(indoc! {"
 3672        /**
 3673         *
 3674         */ˇtext
 3675    "});
 3676        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677        cx.assert_editor_state(indoc! {"
 3678        /**
 3679         *
 3680         */
 3681         ˇtext
 3682    "});
 3683
 3684        // Ensure if not comment block it doesn't
 3685        // add comment prefix on newline
 3686        cx.set_state(indoc! {"
 3687        * textˇ
 3688    "});
 3689        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3690        cx.assert_editor_state(indoc! {"
 3691        * text
 3692        ˇ
 3693    "});
 3694    }
 3695    // Ensure that comment continuations can be disabled.
 3696    update_test_language_settings(cx, |settings| {
 3697        settings.defaults.extend_comment_on_newline = Some(false);
 3698    });
 3699    let mut cx = EditorTestContext::new(cx).await;
 3700    cx.set_state(indoc! {"
 3701        /**ˇ
 3702    "});
 3703    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3704    cx.assert_editor_state(indoc! {"
 3705        /**
 3706        ˇ
 3707    "});
 3708}
 3709
 3710#[gpui::test]
 3711async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3712    init_test(cx, |settings| {
 3713        settings.defaults.tab_size = NonZeroU32::new(4)
 3714    });
 3715
 3716    let lua_language = Arc::new(Language::new(
 3717        LanguageConfig {
 3718            line_comments: vec!["--".into()],
 3719            block_comment: Some(language::BlockCommentConfig {
 3720                start: "--[[".into(),
 3721                prefix: "".into(),
 3722                end: "]]".into(),
 3723                tab_size: 0,
 3724            }),
 3725            ..LanguageConfig::default()
 3726        },
 3727        None,
 3728    ));
 3729
 3730    let mut cx = EditorTestContext::new(cx).await;
 3731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3732
 3733    // Line with line comment should extend
 3734    cx.set_state(indoc! {"
 3735        --ˇ
 3736    "});
 3737    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3738    cx.assert_editor_state(indoc! {"
 3739        --
 3740        --ˇ
 3741    "});
 3742
 3743    // Line with block comment that matches line comment should not extend
 3744    cx.set_state(indoc! {"
 3745        --[[ˇ
 3746    "});
 3747    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3748    cx.assert_editor_state(indoc! {"
 3749        --[[
 3750        ˇ
 3751    "});
 3752}
 3753
 3754#[gpui::test]
 3755fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3756    init_test(cx, |_| {});
 3757
 3758    let editor = cx.add_window(|window, cx| {
 3759        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3760        let mut editor = build_editor(buffer, window, cx);
 3761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3762            s.select_ranges([3..4, 11..12, 19..20])
 3763        });
 3764        editor
 3765    });
 3766
 3767    _ = editor.update(cx, |editor, window, cx| {
 3768        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3769        editor.buffer.update(cx, |buffer, cx| {
 3770            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3771            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3772        });
 3773        assert_eq!(
 3774            editor.selections.ranges(&editor.display_snapshot(cx)),
 3775            &[2..2, 7..7, 12..12],
 3776        );
 3777
 3778        editor.insert("Z", window, cx);
 3779        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3780
 3781        // The selections are moved after the inserted characters
 3782        assert_eq!(
 3783            editor.selections.ranges(&editor.display_snapshot(cx)),
 3784            &[3..3, 9..9, 15..15],
 3785        );
 3786    });
 3787}
 3788
 3789#[gpui::test]
 3790async fn test_tab(cx: &mut TestAppContext) {
 3791    init_test(cx, |settings| {
 3792        settings.defaults.tab_size = NonZeroU32::new(3)
 3793    });
 3794
 3795    let mut cx = EditorTestContext::new(cx).await;
 3796    cx.set_state(indoc! {"
 3797        ˇabˇc
 3798        ˇ🏀ˇ🏀ˇefg
 3799 3800    "});
 3801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803           ˇab ˇc
 3804           ˇ🏀  ˇ🏀  ˇefg
 3805        d  ˇ
 3806    "});
 3807
 3808    cx.set_state(indoc! {"
 3809        a
 3810        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3811    "});
 3812    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3813    cx.assert_editor_state(indoc! {"
 3814        a
 3815           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    let language = Arc::new(
 3825        Language::new(
 3826            LanguageConfig::default(),
 3827            Some(tree_sitter_rust::LANGUAGE.into()),
 3828        )
 3829        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3830        .unwrap(),
 3831    );
 3832    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3833
 3834    // test when all cursors are not at suggested indent
 3835    // then simply move to their suggested indent location
 3836    cx.set_state(indoc! {"
 3837        const a: B = (
 3838            c(
 3839        ˇ
 3840        ˇ    )
 3841        );
 3842    "});
 3843    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        const a: B = (
 3846            c(
 3847                ˇ
 3848            ˇ)
 3849        );
 3850    "});
 3851
 3852    // test cursor already at suggested indent not moving when
 3853    // other cursors are yet to reach their suggested indents
 3854    cx.set_state(indoc! {"
 3855        ˇ
 3856        const a: B = (
 3857            c(
 3858                d(
 3859        ˇ
 3860                )
 3861        ˇ
 3862        ˇ    )
 3863        );
 3864    "});
 3865    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3866    cx.assert_editor_state(indoc! {"
 3867        ˇ
 3868        const a: B = (
 3869            c(
 3870                d(
 3871                    ˇ
 3872                )
 3873                ˇ
 3874            ˇ)
 3875        );
 3876    "});
 3877    // test when all cursors are at suggested indent then tab is inserted
 3878    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3879    cx.assert_editor_state(indoc! {"
 3880            ˇ
 3881        const a: B = (
 3882            c(
 3883                d(
 3884                        ˇ
 3885                )
 3886                    ˇ
 3887                ˇ)
 3888        );
 3889    "});
 3890
 3891    // test when current indent is less than suggested indent,
 3892    // we adjust line to match suggested indent and move cursor to it
 3893    //
 3894    // when no other cursor is at word boundary, all of them should move
 3895    cx.set_state(indoc! {"
 3896        const a: B = (
 3897            c(
 3898                d(
 3899        ˇ
 3900        ˇ   )
 3901        ˇ   )
 3902        );
 3903    "});
 3904    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3905    cx.assert_editor_state(indoc! {"
 3906        const a: B = (
 3907            c(
 3908                d(
 3909                    ˇ
 3910                ˇ)
 3911            ˇ)
 3912        );
 3913    "});
 3914
 3915    // test when current indent is less than suggested indent,
 3916    // we adjust line to match suggested indent and move cursor to it
 3917    //
 3918    // when some other cursor is at word boundary, it should not move
 3919    cx.set_state(indoc! {"
 3920        const a: B = (
 3921            c(
 3922                d(
 3923        ˇ
 3924        ˇ   )
 3925           ˇ)
 3926        );
 3927    "});
 3928    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3929    cx.assert_editor_state(indoc! {"
 3930        const a: B = (
 3931            c(
 3932                d(
 3933                    ˇ
 3934                ˇ)
 3935            ˇ)
 3936        );
 3937    "});
 3938
 3939    // test when current indent is more than suggested indent,
 3940    // we just move cursor to current indent instead of suggested indent
 3941    //
 3942    // when no other cursor is at word boundary, all of them should move
 3943    cx.set_state(indoc! {"
 3944        const a: B = (
 3945            c(
 3946                d(
 3947        ˇ
 3948        ˇ                )
 3949        ˇ   )
 3950        );
 3951    "});
 3952    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3953    cx.assert_editor_state(indoc! {"
 3954        const a: B = (
 3955            c(
 3956                d(
 3957                    ˇ
 3958                        ˇ)
 3959            ˇ)
 3960        );
 3961    "});
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964        const a: B = (
 3965            c(
 3966                d(
 3967                        ˇ
 3968                            ˇ)
 3969                ˇ)
 3970        );
 3971    "});
 3972
 3973    // test when current indent is more than suggested indent,
 3974    // we just move cursor to current indent instead of suggested indent
 3975    //
 3976    // when some other cursor is at word boundary, it doesn't move
 3977    cx.set_state(indoc! {"
 3978        const a: B = (
 3979            c(
 3980                d(
 3981        ˇ
 3982        ˇ                )
 3983            ˇ)
 3984        );
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        const a: B = (
 3989            c(
 3990                d(
 3991                    ˇ
 3992                        ˇ)
 3993            ˇ)
 3994        );
 3995    "});
 3996
 3997    // handle auto-indent when there are multiple cursors on the same line
 3998    cx.set_state(indoc! {"
 3999        const a: B = (
 4000            c(
 4001        ˇ    ˇ
 4002        ˇ    )
 4003        );
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        const a: B = (
 4008            c(
 4009                ˇ
 4010            ˇ)
 4011        );
 4012    "});
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4017    init_test(cx, |settings| {
 4018        settings.defaults.tab_size = NonZeroU32::new(3)
 4019    });
 4020
 4021    let mut cx = EditorTestContext::new(cx).await;
 4022    cx.set_state(indoc! {"
 4023         ˇ
 4024        \t ˇ
 4025        \t  ˇ
 4026        \t   ˇ
 4027         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4028    "});
 4029
 4030    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4031    cx.assert_editor_state(indoc! {"
 4032           ˇ
 4033        \t   ˇ
 4034        \t   ˇ
 4035        \t      ˇ
 4036         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4037    "});
 4038}
 4039
 4040#[gpui::test]
 4041async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4042    init_test(cx, |settings| {
 4043        settings.defaults.tab_size = NonZeroU32::new(4)
 4044    });
 4045
 4046    let language = Arc::new(
 4047        Language::new(
 4048            LanguageConfig::default(),
 4049            Some(tree_sitter_rust::LANGUAGE.into()),
 4050        )
 4051        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4052        .unwrap(),
 4053    );
 4054
 4055    let mut cx = EditorTestContext::new(cx).await;
 4056    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4057    cx.set_state(indoc! {"
 4058        fn a() {
 4059            if b {
 4060        \t ˇc
 4061            }
 4062        }
 4063    "});
 4064
 4065    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4066    cx.assert_editor_state(indoc! {"
 4067        fn a() {
 4068            if b {
 4069                ˇc
 4070            }
 4071        }
 4072    "});
 4073}
 4074
 4075#[gpui::test]
 4076async fn test_indent_outdent(cx: &mut TestAppContext) {
 4077    init_test(cx, |settings| {
 4078        settings.defaults.tab_size = NonZeroU32::new(4);
 4079    });
 4080
 4081    let mut cx = EditorTestContext::new(cx).await;
 4082
 4083    cx.set_state(indoc! {"
 4084          «oneˇ» «twoˇ»
 4085        three
 4086         four
 4087    "});
 4088    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4089    cx.assert_editor_state(indoc! {"
 4090            «oneˇ» «twoˇ»
 4091        three
 4092         four
 4093    "});
 4094
 4095    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4096    cx.assert_editor_state(indoc! {"
 4097        «oneˇ» «twoˇ»
 4098        three
 4099         four
 4100    "});
 4101
 4102    // select across line ending
 4103    cx.set_state(indoc! {"
 4104        one two
 4105        t«hree
 4106        ˇ» four
 4107    "});
 4108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4109    cx.assert_editor_state(indoc! {"
 4110        one two
 4111            t«hree
 4112        ˇ» four
 4113    "});
 4114
 4115    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4116    cx.assert_editor_state(indoc! {"
 4117        one two
 4118        t«hree
 4119        ˇ» four
 4120    "});
 4121
 4122    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4123    cx.set_state(indoc! {"
 4124        one two
 4125        ˇthree
 4126            four
 4127    "});
 4128    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4129    cx.assert_editor_state(indoc! {"
 4130        one two
 4131            ˇthree
 4132            four
 4133    "});
 4134
 4135    cx.set_state(indoc! {"
 4136        one two
 4137        ˇ    three
 4138            four
 4139    "});
 4140    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4141    cx.assert_editor_state(indoc! {"
 4142        one two
 4143        ˇthree
 4144            four
 4145    "});
 4146}
 4147
 4148#[gpui::test]
 4149async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4150    // This is a regression test for issue #33761
 4151    init_test(cx, |_| {});
 4152
 4153    let mut cx = EditorTestContext::new(cx).await;
 4154    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4156
 4157    cx.set_state(
 4158        r#"ˇ#     ingress:
 4159ˇ#         api:
 4160ˇ#             enabled: false
 4161ˇ#             pathType: Prefix
 4162ˇ#           console:
 4163ˇ#               enabled: false
 4164ˇ#               pathType: Prefix
 4165"#,
 4166    );
 4167
 4168    // Press tab to indent all lines
 4169    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4170
 4171    cx.assert_editor_state(
 4172        r#"    ˇ#     ingress:
 4173    ˇ#         api:
 4174    ˇ#             enabled: false
 4175    ˇ#             pathType: Prefix
 4176    ˇ#           console:
 4177    ˇ#               enabled: false
 4178    ˇ#               pathType: Prefix
 4179"#,
 4180    );
 4181}
 4182
 4183#[gpui::test]
 4184async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4185    // This is a test to make sure our fix for issue #33761 didn't break anything
 4186    init_test(cx, |_| {});
 4187
 4188    let mut cx = EditorTestContext::new(cx).await;
 4189    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4191
 4192    cx.set_state(
 4193        r#"ˇingress:
 4194ˇ  api:
 4195ˇ    enabled: false
 4196ˇ    pathType: Prefix
 4197"#,
 4198    );
 4199
 4200    // Press tab to indent all lines
 4201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4202
 4203    cx.assert_editor_state(
 4204        r#"ˇingress:
 4205    ˇapi:
 4206        ˇenabled: false
 4207        ˇpathType: Prefix
 4208"#,
 4209    );
 4210}
 4211
 4212#[gpui::test]
 4213async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4214    init_test(cx, |settings| {
 4215        settings.defaults.hard_tabs = Some(true);
 4216    });
 4217
 4218    let mut cx = EditorTestContext::new(cx).await;
 4219
 4220    // select two ranges on one line
 4221    cx.set_state(indoc! {"
 4222        «oneˇ» «twoˇ»
 4223        three
 4224        four
 4225    "});
 4226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4227    cx.assert_editor_state(indoc! {"
 4228        \t«oneˇ» «twoˇ»
 4229        three
 4230        four
 4231    "});
 4232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4233    cx.assert_editor_state(indoc! {"
 4234        \t\t«oneˇ» «twoˇ»
 4235        three
 4236        four
 4237    "});
 4238    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4239    cx.assert_editor_state(indoc! {"
 4240        \t«oneˇ» «twoˇ»
 4241        three
 4242        four
 4243    "});
 4244    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4245    cx.assert_editor_state(indoc! {"
 4246        «oneˇ» «twoˇ»
 4247        three
 4248        four
 4249    "});
 4250
 4251    // select across a line ending
 4252    cx.set_state(indoc! {"
 4253        one two
 4254        t«hree
 4255        ˇ»four
 4256    "});
 4257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4258    cx.assert_editor_state(indoc! {"
 4259        one two
 4260        \tt«hree
 4261        ˇ»four
 4262    "});
 4263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4264    cx.assert_editor_state(indoc! {"
 4265        one two
 4266        \t\tt«hree
 4267        ˇ»four
 4268    "});
 4269    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4270    cx.assert_editor_state(indoc! {"
 4271        one two
 4272        \tt«hree
 4273        ˇ»four
 4274    "});
 4275    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4276    cx.assert_editor_state(indoc! {"
 4277        one two
 4278        t«hree
 4279        ˇ»four
 4280    "});
 4281
 4282    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4283    cx.set_state(indoc! {"
 4284        one two
 4285        ˇthree
 4286        four
 4287    "});
 4288    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4289    cx.assert_editor_state(indoc! {"
 4290        one two
 4291        ˇthree
 4292        four
 4293    "});
 4294    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4295    cx.assert_editor_state(indoc! {"
 4296        one two
 4297        \tˇthree
 4298        four
 4299    "});
 4300    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4301    cx.assert_editor_state(indoc! {"
 4302        one two
 4303        ˇthree
 4304        four
 4305    "});
 4306}
 4307
 4308#[gpui::test]
 4309fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4310    init_test(cx, |settings| {
 4311        settings.languages.0.extend([
 4312            (
 4313                "TOML".into(),
 4314                LanguageSettingsContent {
 4315                    tab_size: NonZeroU32::new(2),
 4316                    ..Default::default()
 4317                },
 4318            ),
 4319            (
 4320                "Rust".into(),
 4321                LanguageSettingsContent {
 4322                    tab_size: NonZeroU32::new(4),
 4323                    ..Default::default()
 4324                },
 4325            ),
 4326        ]);
 4327    });
 4328
 4329    let toml_language = Arc::new(Language::new(
 4330        LanguageConfig {
 4331            name: "TOML".into(),
 4332            ..Default::default()
 4333        },
 4334        None,
 4335    ));
 4336    let rust_language = Arc::new(Language::new(
 4337        LanguageConfig {
 4338            name: "Rust".into(),
 4339            ..Default::default()
 4340        },
 4341        None,
 4342    ));
 4343
 4344    let toml_buffer =
 4345        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4346    let rust_buffer =
 4347        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4348    let multibuffer = cx.new(|cx| {
 4349        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4350        multibuffer.push_excerpts(
 4351            toml_buffer.clone(),
 4352            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4353            cx,
 4354        );
 4355        multibuffer.push_excerpts(
 4356            rust_buffer.clone(),
 4357            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4358            cx,
 4359        );
 4360        multibuffer
 4361    });
 4362
 4363    cx.add_window(|window, cx| {
 4364        let mut editor = build_editor(multibuffer, window, cx);
 4365
 4366        assert_eq!(
 4367            editor.text(cx),
 4368            indoc! {"
 4369                a = 1
 4370                b = 2
 4371
 4372                const c: usize = 3;
 4373            "}
 4374        );
 4375
 4376        select_ranges(
 4377            &mut editor,
 4378            indoc! {"
 4379                «aˇ» = 1
 4380                b = 2
 4381
 4382                «const c:ˇ» usize = 3;
 4383            "},
 4384            window,
 4385            cx,
 4386        );
 4387
 4388        editor.tab(&Tab, window, cx);
 4389        assert_text_with_selections(
 4390            &mut editor,
 4391            indoc! {"
 4392                  «aˇ» = 1
 4393                b = 2
 4394
 4395                    «const c:ˇ» usize = 3;
 4396            "},
 4397            cx,
 4398        );
 4399        editor.backtab(&Backtab, window, cx);
 4400        assert_text_with_selections(
 4401            &mut editor,
 4402            indoc! {"
 4403                «aˇ» = 1
 4404                b = 2
 4405
 4406                «const c:ˇ» usize = 3;
 4407            "},
 4408            cx,
 4409        );
 4410
 4411        editor
 4412    });
 4413}
 4414
 4415#[gpui::test]
 4416async fn test_backspace(cx: &mut TestAppContext) {
 4417    init_test(cx, |_| {});
 4418
 4419    let mut cx = EditorTestContext::new(cx).await;
 4420
 4421    // Basic backspace
 4422    cx.set_state(indoc! {"
 4423        onˇe two three
 4424        fou«rˇ» five six
 4425        seven «ˇeight nine
 4426        »ten
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        oˇe two three
 4431        fouˇ five six
 4432        seven ˇten
 4433    "});
 4434
 4435    // Test backspace inside and around indents
 4436    cx.set_state(indoc! {"
 4437        zero
 4438            ˇone
 4439                ˇtwo
 4440            ˇ ˇ ˇ  three
 4441        ˇ  ˇ  four
 4442    "});
 4443    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4444    cx.assert_editor_state(indoc! {"
 4445        zero
 4446        ˇone
 4447            ˇtwo
 4448        ˇ  threeˇ  four
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_delete(cx: &mut TestAppContext) {
 4454    init_test(cx, |_| {});
 4455
 4456    let mut cx = EditorTestContext::new(cx).await;
 4457    cx.set_state(indoc! {"
 4458        onˇe two three
 4459        fou«rˇ» five six
 4460        seven «ˇeight nine
 4461        »ten
 4462    "});
 4463    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4464    cx.assert_editor_state(indoc! {"
 4465        onˇ two three
 4466        fouˇ five six
 4467        seven ˇten
 4468    "});
 4469}
 4470
 4471#[gpui::test]
 4472fn test_delete_line(cx: &mut TestAppContext) {
 4473    init_test(cx, |_| {});
 4474
 4475    let editor = cx.add_window(|window, cx| {
 4476        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4477        build_editor(buffer, window, cx)
 4478    });
 4479    _ = editor.update(cx, |editor, window, cx| {
 4480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4481            s.select_display_ranges([
 4482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4483                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4484                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4485            ])
 4486        });
 4487        editor.delete_line(&DeleteLine, window, cx);
 4488        assert_eq!(editor.display_text(cx), "ghi");
 4489        assert_eq!(
 4490            editor.selections.display_ranges(cx),
 4491            vec![
 4492                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4493                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4494            ]
 4495        );
 4496    });
 4497
 4498    let editor = cx.add_window(|window, cx| {
 4499        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4500        build_editor(buffer, window, cx)
 4501    });
 4502    _ = editor.update(cx, |editor, window, cx| {
 4503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4504            s.select_display_ranges([
 4505                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4506            ])
 4507        });
 4508        editor.delete_line(&DeleteLine, window, cx);
 4509        assert_eq!(editor.display_text(cx), "ghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4513        );
 4514    });
 4515
 4516    let editor = cx.add_window(|window, cx| {
 4517        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4518        build_editor(buffer, window, cx)
 4519    });
 4520    _ = editor.update(cx, |editor, window, cx| {
 4521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4522            s.select_display_ranges([
 4523                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4524            ])
 4525        });
 4526        editor.delete_line(&DeleteLine, window, cx);
 4527        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4528        assert_eq!(
 4529            editor.selections.display_ranges(cx),
 4530            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4531        );
 4532    });
 4533}
 4534
 4535#[gpui::test]
 4536fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4537    init_test(cx, |_| {});
 4538
 4539    cx.add_window(|window, cx| {
 4540        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4541        let mut editor = build_editor(buffer.clone(), window, cx);
 4542        let buffer = buffer.read(cx).as_singleton().unwrap();
 4543
 4544        assert_eq!(
 4545            editor
 4546                .selections
 4547                .ranges::<Point>(&editor.display_snapshot(cx)),
 4548            &[Point::new(0, 0)..Point::new(0, 0)]
 4549        );
 4550
 4551        // When on single line, replace newline at end by space
 4552        editor.join_lines(&JoinLines, window, cx);
 4553        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4554        assert_eq!(
 4555            editor
 4556                .selections
 4557                .ranges::<Point>(&editor.display_snapshot(cx)),
 4558            &[Point::new(0, 3)..Point::new(0, 3)]
 4559        );
 4560
 4561        // When multiple lines are selected, remove newlines that are spanned by the selection
 4562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4563            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4564        });
 4565        editor.join_lines(&JoinLines, window, cx);
 4566        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4567        assert_eq!(
 4568            editor
 4569                .selections
 4570                .ranges::<Point>(&editor.display_snapshot(cx)),
 4571            &[Point::new(0, 11)..Point::new(0, 11)]
 4572        );
 4573
 4574        // Undo should be transactional
 4575        editor.undo(&Undo, window, cx);
 4576        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4577        assert_eq!(
 4578            editor
 4579                .selections
 4580                .ranges::<Point>(&editor.display_snapshot(cx)),
 4581            &[Point::new(0, 5)..Point::new(2, 2)]
 4582        );
 4583
 4584        // When joining an empty line don't insert a space
 4585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4586            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4587        });
 4588        editor.join_lines(&JoinLines, window, cx);
 4589        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4590        assert_eq!(
 4591            editor
 4592                .selections
 4593                .ranges::<Point>(&editor.display_snapshot(cx)),
 4594            [Point::new(2, 3)..Point::new(2, 3)]
 4595        );
 4596
 4597        // We can remove trailing newlines
 4598        editor.join_lines(&JoinLines, window, cx);
 4599        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4600        assert_eq!(
 4601            editor
 4602                .selections
 4603                .ranges::<Point>(&editor.display_snapshot(cx)),
 4604            [Point::new(2, 3)..Point::new(2, 3)]
 4605        );
 4606
 4607        // We don't blow up on the last line
 4608        editor.join_lines(&JoinLines, window, cx);
 4609        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4610        assert_eq!(
 4611            editor
 4612                .selections
 4613                .ranges::<Point>(&editor.display_snapshot(cx)),
 4614            [Point::new(2, 3)..Point::new(2, 3)]
 4615        );
 4616
 4617        // reset to test indentation
 4618        editor.buffer.update(cx, |buffer, cx| {
 4619            buffer.edit(
 4620                [
 4621                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4622                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4623                ],
 4624                None,
 4625                cx,
 4626            )
 4627        });
 4628
 4629        // We remove any leading spaces
 4630        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4632            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4633        });
 4634        editor.join_lines(&JoinLines, window, cx);
 4635        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4636
 4637        // We don't insert a space for a line containing only spaces
 4638        editor.join_lines(&JoinLines, window, cx);
 4639        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4640
 4641        // We ignore any leading tabs
 4642        editor.join_lines(&JoinLines, window, cx);
 4643        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4644
 4645        editor
 4646    });
 4647}
 4648
 4649#[gpui::test]
 4650fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4651    init_test(cx, |_| {});
 4652
 4653    cx.add_window(|window, cx| {
 4654        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4655        let mut editor = build_editor(buffer.clone(), window, cx);
 4656        let buffer = buffer.read(cx).as_singleton().unwrap();
 4657
 4658        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4659            s.select_ranges([
 4660                Point::new(0, 2)..Point::new(1, 1),
 4661                Point::new(1, 2)..Point::new(1, 2),
 4662                Point::new(3, 1)..Point::new(3, 2),
 4663            ])
 4664        });
 4665
 4666        editor.join_lines(&JoinLines, window, cx);
 4667        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4668
 4669        assert_eq!(
 4670            editor
 4671                .selections
 4672                .ranges::<Point>(&editor.display_snapshot(cx)),
 4673            [
 4674                Point::new(0, 7)..Point::new(0, 7),
 4675                Point::new(1, 3)..Point::new(1, 3)
 4676            ]
 4677        );
 4678        editor
 4679    });
 4680}
 4681
 4682#[gpui::test]
 4683async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4684    init_test(cx, |_| {});
 4685
 4686    let mut cx = EditorTestContext::new(cx).await;
 4687
 4688    let diff_base = r#"
 4689        Line 0
 4690        Line 1
 4691        Line 2
 4692        Line 3
 4693        "#
 4694    .unindent();
 4695
 4696    cx.set_state(
 4697        &r#"
 4698        ˇLine 0
 4699        Line 1
 4700        Line 2
 4701        Line 3
 4702        "#
 4703        .unindent(),
 4704    );
 4705
 4706    cx.set_head_text(&diff_base);
 4707    executor.run_until_parked();
 4708
 4709    // Join lines
 4710    cx.update_editor(|editor, window, cx| {
 4711        editor.join_lines(&JoinLines, window, cx);
 4712    });
 4713    executor.run_until_parked();
 4714
 4715    cx.assert_editor_state(
 4716        &r#"
 4717        Line 0ˇ Line 1
 4718        Line 2
 4719        Line 3
 4720        "#
 4721        .unindent(),
 4722    );
 4723    // Join again
 4724    cx.update_editor(|editor, window, cx| {
 4725        editor.join_lines(&JoinLines, window, cx);
 4726    });
 4727    executor.run_until_parked();
 4728
 4729    cx.assert_editor_state(
 4730        &r#"
 4731        Line 0 Line 1ˇ Line 2
 4732        Line 3
 4733        "#
 4734        .unindent(),
 4735    );
 4736}
 4737
 4738#[gpui::test]
 4739async fn test_custom_newlines_cause_no_false_positive_diffs(
 4740    executor: BackgroundExecutor,
 4741    cx: &mut TestAppContext,
 4742) {
 4743    init_test(cx, |_| {});
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4746    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4747    executor.run_until_parked();
 4748
 4749    cx.update_editor(|editor, window, cx| {
 4750        let snapshot = editor.snapshot(window, cx);
 4751        assert_eq!(
 4752            snapshot
 4753                .buffer_snapshot()
 4754                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4755                .collect::<Vec<_>>(),
 4756            Vec::new(),
 4757            "Should not have any diffs for files with custom newlines"
 4758        );
 4759    });
 4760}
 4761
 4762#[gpui::test]
 4763async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4764    init_test(cx, |_| {});
 4765
 4766    let mut cx = EditorTestContext::new(cx).await;
 4767
 4768    // Test sort_lines_case_insensitive()
 4769    cx.set_state(indoc! {"
 4770        «z
 4771        y
 4772        x
 4773        Z
 4774        Y
 4775        Xˇ»
 4776    "});
 4777    cx.update_editor(|e, window, cx| {
 4778        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4779    });
 4780    cx.assert_editor_state(indoc! {"
 4781        «x
 4782        X
 4783        y
 4784        Y
 4785        z
 4786        Zˇ»
 4787    "});
 4788
 4789    // Test sort_lines_by_length()
 4790    //
 4791    // Demonstrates:
 4792    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4793    // - sort is stable
 4794    cx.set_state(indoc! {"
 4795        «123
 4796        æ
 4797        12
 4798 4799        1
 4800        æˇ»
 4801    "});
 4802    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4803    cx.assert_editor_state(indoc! {"
 4804        «æ
 4805 4806        1
 4807        æ
 4808        12
 4809        123ˇ»
 4810    "});
 4811
 4812    // Test reverse_lines()
 4813    cx.set_state(indoc! {"
 4814        «5
 4815        4
 4816        3
 4817        2
 4818        1ˇ»
 4819    "});
 4820    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4821    cx.assert_editor_state(indoc! {"
 4822        «1
 4823        2
 4824        3
 4825        4
 4826        5ˇ»
 4827    "});
 4828
 4829    // Skip testing shuffle_line()
 4830
 4831    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4832    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4833
 4834    // Don't manipulate when cursor is on single line, but expand the selection
 4835    cx.set_state(indoc! {"
 4836        ddˇdd
 4837        ccc
 4838        bb
 4839        a
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4843    });
 4844    cx.assert_editor_state(indoc! {"
 4845        «ddddˇ»
 4846        ccc
 4847        bb
 4848        a
 4849    "});
 4850
 4851    // Basic manipulate case
 4852    // Start selection moves to column 0
 4853    // End of selection shrinks to fit shorter line
 4854    cx.set_state(indoc! {"
 4855        dd«d
 4856        ccc
 4857        bb
 4858        aaaaaˇ»
 4859    "});
 4860    cx.update_editor(|e, window, cx| {
 4861        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4862    });
 4863    cx.assert_editor_state(indoc! {"
 4864        «aaaaa
 4865        bb
 4866        ccc
 4867        dddˇ»
 4868    "});
 4869
 4870    // Manipulate case with newlines
 4871    cx.set_state(indoc! {"
 4872        dd«d
 4873        ccc
 4874
 4875        bb
 4876        aaaaa
 4877
 4878        ˇ»
 4879    "});
 4880    cx.update_editor(|e, window, cx| {
 4881        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4882    });
 4883    cx.assert_editor_state(indoc! {"
 4884        «
 4885
 4886        aaaaa
 4887        bb
 4888        ccc
 4889        dddˇ»
 4890
 4891    "});
 4892
 4893    // Adding new line
 4894    cx.set_state(indoc! {"
 4895        aa«a
 4896        bbˇ»b
 4897    "});
 4898    cx.update_editor(|e, window, cx| {
 4899        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4900    });
 4901    cx.assert_editor_state(indoc! {"
 4902        «aaa
 4903        bbb
 4904        added_lineˇ»
 4905    "});
 4906
 4907    // Removing line
 4908    cx.set_state(indoc! {"
 4909        aa«a
 4910        bbbˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.manipulate_immutable_lines(window, cx, |lines| {
 4914            lines.pop();
 4915        })
 4916    });
 4917    cx.assert_editor_state(indoc! {"
 4918        «aaaˇ»
 4919    "});
 4920
 4921    // Removing all lines
 4922    cx.set_state(indoc! {"
 4923        aa«a
 4924        bbbˇ»
 4925    "});
 4926    cx.update_editor(|e, window, cx| {
 4927        e.manipulate_immutable_lines(window, cx, |lines| {
 4928            lines.drain(..);
 4929        })
 4930    });
 4931    cx.assert_editor_state(indoc! {"
 4932        ˇ
 4933    "});
 4934}
 4935
 4936#[gpui::test]
 4937async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4938    init_test(cx, |_| {});
 4939
 4940    let mut cx = EditorTestContext::new(cx).await;
 4941
 4942    // Consider continuous selection as single selection
 4943    cx.set_state(indoc! {"
 4944        Aaa«aa
 4945        cˇ»c«c
 4946        bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4951    });
 4952    cx.assert_editor_state(indoc! {"
 4953        «Aaaaa
 4954        ccc
 4955        bb
 4956        aaaaaˇ»
 4957    "});
 4958
 4959    cx.set_state(indoc! {"
 4960        Aaa«aa
 4961        cˇ»c«c
 4962        bb
 4963        aaaˇ»aa
 4964    "});
 4965    cx.update_editor(|e, window, cx| {
 4966        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4967    });
 4968    cx.assert_editor_state(indoc! {"
 4969        «Aaaaa
 4970        ccc
 4971        bbˇ»
 4972    "});
 4973
 4974    // Consider non continuous selection as distinct dedup operations
 4975    cx.set_state(indoc! {"
 4976        «aaaaa
 4977        bb
 4978        aaaaa
 4979        aaaaaˇ»
 4980
 4981        aaa«aaˇ»
 4982    "});
 4983    cx.update_editor(|e, window, cx| {
 4984        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4985    });
 4986    cx.assert_editor_state(indoc! {"
 4987        «aaaaa
 4988        bbˇ»
 4989
 4990        «aaaaaˇ»
 4991    "});
 4992}
 4993
 4994#[gpui::test]
 4995async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4996    init_test(cx, |_| {});
 4997
 4998    let mut cx = EditorTestContext::new(cx).await;
 4999
 5000    cx.set_state(indoc! {"
 5001        «Aaa
 5002        aAa
 5003        Aaaˇ»
 5004    "});
 5005    cx.update_editor(|e, window, cx| {
 5006        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5007    });
 5008    cx.assert_editor_state(indoc! {"
 5009        «Aaa
 5010        aAaˇ»
 5011    "});
 5012
 5013    cx.set_state(indoc! {"
 5014        «Aaa
 5015        aAa
 5016        aaAˇ»
 5017    "});
 5018    cx.update_editor(|e, window, cx| {
 5019        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5020    });
 5021    cx.assert_editor_state(indoc! {"
 5022        «Aaaˇ»
 5023    "});
 5024}
 5025
 5026#[gpui::test]
 5027async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5028    init_test(cx, |_| {});
 5029
 5030    let mut cx = EditorTestContext::new(cx).await;
 5031
 5032    let js_language = Arc::new(Language::new(
 5033        LanguageConfig {
 5034            name: "JavaScript".into(),
 5035            wrap_characters: Some(language::WrapCharactersConfig {
 5036                start_prefix: "<".into(),
 5037                start_suffix: ">".into(),
 5038                end_prefix: "</".into(),
 5039                end_suffix: ">".into(),
 5040            }),
 5041            ..LanguageConfig::default()
 5042        },
 5043        None,
 5044    ));
 5045
 5046    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5047
 5048    cx.set_state(indoc! {"
 5049        «testˇ»
 5050    "});
 5051    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5052    cx.assert_editor_state(indoc! {"
 5053        <«ˇ»>test</«ˇ»>
 5054    "});
 5055
 5056    cx.set_state(indoc! {"
 5057        «test
 5058         testˇ»
 5059    "});
 5060    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5061    cx.assert_editor_state(indoc! {"
 5062        <«ˇ»>test
 5063         test</«ˇ»>
 5064    "});
 5065
 5066    cx.set_state(indoc! {"
 5067        teˇst
 5068    "});
 5069    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5070    cx.assert_editor_state(indoc! {"
 5071        te<«ˇ»></«ˇ»>st
 5072    "});
 5073}
 5074
 5075#[gpui::test]
 5076async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5077    init_test(cx, |_| {});
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    let js_language = Arc::new(Language::new(
 5082        LanguageConfig {
 5083            name: "JavaScript".into(),
 5084            wrap_characters: Some(language::WrapCharactersConfig {
 5085                start_prefix: "<".into(),
 5086                start_suffix: ">".into(),
 5087                end_prefix: "</".into(),
 5088                end_suffix: ">".into(),
 5089            }),
 5090            ..LanguageConfig::default()
 5091        },
 5092        None,
 5093    ));
 5094
 5095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5096
 5097    cx.set_state(indoc! {"
 5098        «testˇ»
 5099        «testˇ» «testˇ»
 5100        «testˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5103    cx.assert_editor_state(indoc! {"
 5104        <«ˇ»>test</«ˇ»>
 5105        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5106        <«ˇ»>test</«ˇ»>
 5107    "});
 5108
 5109    cx.set_state(indoc! {"
 5110        «test
 5111         testˇ»
 5112        «test
 5113         testˇ»
 5114    "});
 5115    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5116    cx.assert_editor_state(indoc! {"
 5117        <«ˇ»>test
 5118         test</«ˇ»>
 5119        <«ˇ»>test
 5120         test</«ˇ»>
 5121    "});
 5122}
 5123
 5124#[gpui::test]
 5125async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5126    init_test(cx, |_| {});
 5127
 5128    let mut cx = EditorTestContext::new(cx).await;
 5129
 5130    let plaintext_language = Arc::new(Language::new(
 5131        LanguageConfig {
 5132            name: "Plain Text".into(),
 5133            ..LanguageConfig::default()
 5134        },
 5135        None,
 5136    ));
 5137
 5138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5139
 5140    cx.set_state(indoc! {"
 5141        «testˇ»
 5142    "});
 5143    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5144    cx.assert_editor_state(indoc! {"
 5145      «testˇ»
 5146    "});
 5147}
 5148
 5149#[gpui::test]
 5150async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5151    init_test(cx, |_| {});
 5152
 5153    let mut cx = EditorTestContext::new(cx).await;
 5154
 5155    // Manipulate with multiple selections on a single line
 5156    cx.set_state(indoc! {"
 5157        dd«dd
 5158        cˇ»c«c
 5159        bb
 5160        aaaˇ»aa
 5161    "});
 5162    cx.update_editor(|e, window, cx| {
 5163        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5164    });
 5165    cx.assert_editor_state(indoc! {"
 5166        «aaaaa
 5167        bb
 5168        ccc
 5169        ddddˇ»
 5170    "});
 5171
 5172    // Manipulate with multiple disjoin selections
 5173    cx.set_state(indoc! {"
 5174 5175        4
 5176        3
 5177        2
 5178        1ˇ»
 5179
 5180        dd«dd
 5181        ccc
 5182        bb
 5183        aaaˇ»aa
 5184    "});
 5185    cx.update_editor(|e, window, cx| {
 5186        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5187    });
 5188    cx.assert_editor_state(indoc! {"
 5189        «1
 5190        2
 5191        3
 5192        4
 5193        5ˇ»
 5194
 5195        «aaaaa
 5196        bb
 5197        ccc
 5198        ddddˇ»
 5199    "});
 5200
 5201    // Adding lines on each selection
 5202    cx.set_state(indoc! {"
 5203 5204        1ˇ»
 5205
 5206        bb«bb
 5207        aaaˇ»aa
 5208    "});
 5209    cx.update_editor(|e, window, cx| {
 5210        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5211    });
 5212    cx.assert_editor_state(indoc! {"
 5213        «2
 5214        1
 5215        added lineˇ»
 5216
 5217        «bbbb
 5218        aaaaa
 5219        added lineˇ»
 5220    "});
 5221
 5222    // Removing lines on each selection
 5223    cx.set_state(indoc! {"
 5224 5225        1ˇ»
 5226
 5227        bb«bb
 5228        aaaˇ»aa
 5229    "});
 5230    cx.update_editor(|e, window, cx| {
 5231        e.manipulate_immutable_lines(window, cx, |lines| {
 5232            lines.pop();
 5233        })
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «2ˇ»
 5237
 5238        «bbbbˇ»
 5239    "});
 5240}
 5241
 5242#[gpui::test]
 5243async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5244    init_test(cx, |settings| {
 5245        settings.defaults.tab_size = NonZeroU32::new(3)
 5246    });
 5247
 5248    let mut cx = EditorTestContext::new(cx).await;
 5249
 5250    // MULTI SELECTION
 5251    // Ln.1 "«" tests empty lines
 5252    // Ln.9 tests just leading whitespace
 5253    cx.set_state(indoc! {"
 5254        «
 5255        abc                 // No indentationˇ»
 5256        «\tabc              // 1 tabˇ»
 5257        \t\tabc «      ˇ»   // 2 tabs
 5258        \t ab«c             // Tab followed by space
 5259         \tabc              // Space followed by tab (3 spaces should be the result)
 5260        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5261           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5262        \t
 5263        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5264    "});
 5265    cx.update_editor(|e, window, cx| {
 5266        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5267    });
 5268    cx.assert_editor_state(
 5269        indoc! {"
 5270            «
 5271            abc                 // No indentation
 5272               abc              // 1 tab
 5273                  abc          // 2 tabs
 5274                abc             // Tab followed by space
 5275               abc              // Space followed by tab (3 spaces should be the result)
 5276                           abc   // Mixed indentation (tab conversion depends on the column)
 5277               abc         // Already space indented
 5278               ·
 5279               abc\tdef          // Only the leading tab is manipulatedˇ»
 5280        "}
 5281        .replace("·", "")
 5282        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5283    );
 5284
 5285    // Test on just a few lines, the others should remain unchanged
 5286    // Only lines (3, 5, 10, 11) should change
 5287    cx.set_state(
 5288        indoc! {"
 5289            ·
 5290            abc                 // No indentation
 5291            \tabcˇ               // 1 tab
 5292            \t\tabc             // 2 tabs
 5293            \t abcˇ              // Tab followed by space
 5294             \tabc              // Space followed by tab (3 spaces should be the result)
 5295            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5296               abc              // Already space indented
 5297            «\t
 5298            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5299        "}
 5300        .replace("·", "")
 5301        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5302    );
 5303    cx.update_editor(|e, window, cx| {
 5304        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5305    });
 5306    cx.assert_editor_state(
 5307        indoc! {"
 5308            ·
 5309            abc                 // No indentation
 5310            «   abc               // 1 tabˇ»
 5311            \t\tabc             // 2 tabs
 5312            «    abc              // Tab followed by spaceˇ»
 5313             \tabc              // Space followed by tab (3 spaces should be the result)
 5314            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5315               abc              // Already space indented
 5316            «   ·
 5317               abc\tdef          // Only the leading tab is manipulatedˇ»
 5318        "}
 5319        .replace("·", "")
 5320        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5321    );
 5322
 5323    // SINGLE SELECTION
 5324    // Ln.1 "«" tests empty lines
 5325    // Ln.9 tests just leading whitespace
 5326    cx.set_state(indoc! {"
 5327        «
 5328        abc                 // No indentation
 5329        \tabc               // 1 tab
 5330        \t\tabc             // 2 tabs
 5331        \t abc              // Tab followed by space
 5332         \tabc              // Space followed by tab (3 spaces should be the result)
 5333        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5334           abc              // Already space indented
 5335        \t
 5336        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5340    });
 5341    cx.assert_editor_state(
 5342        indoc! {"
 5343            «
 5344            abc                 // No indentation
 5345               abc               // 1 tab
 5346                  abc             // 2 tabs
 5347                abc              // Tab followed by space
 5348               abc              // Space followed by tab (3 spaces should be the result)
 5349                           abc   // Mixed indentation (tab conversion depends on the column)
 5350               abc              // Already space indented
 5351               ·
 5352               abc\tdef          // Only the leading tab is manipulatedˇ»
 5353        "}
 5354        .replace("·", "")
 5355        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5356    );
 5357}
 5358
 5359#[gpui::test]
 5360async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5361    init_test(cx, |settings| {
 5362        settings.defaults.tab_size = NonZeroU32::new(3)
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    // MULTI SELECTION
 5368    // Ln.1 "«" tests empty lines
 5369    // Ln.11 tests just leading whitespace
 5370    cx.set_state(indoc! {"
 5371        «
 5372        abˇ»ˇc                 // No indentation
 5373         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5374          abc  «             // 2 spaces (< 3 so dont convert)
 5375           abc              // 3 spaces (convert)
 5376             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5377        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5378        «\t abc              // Tab followed by space
 5379         \tabc              // Space followed by tab (should be consumed due to tab)
 5380        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5381           \tˇ»  «\t
 5382           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5383    "});
 5384    cx.update_editor(|e, window, cx| {
 5385        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5386    });
 5387    cx.assert_editor_state(indoc! {"
 5388        «
 5389        abc                 // No indentation
 5390         abc                // 1 space (< 3 so dont convert)
 5391          abc               // 2 spaces (< 3 so dont convert)
 5392        \tabc              // 3 spaces (convert)
 5393        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5394        \t\t\tabc           // Already tab indented
 5395        \t abc              // Tab followed by space
 5396        \tabc              // Space followed by tab (should be consumed due to tab)
 5397        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5398        \t\t\t
 5399        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5400    "});
 5401
 5402    // Test on just a few lines, the other should remain unchanged
 5403    // Only lines (4, 8, 11, 12) should change
 5404    cx.set_state(
 5405        indoc! {"
 5406            ·
 5407            abc                 // No indentation
 5408             abc                // 1 space (< 3 so dont convert)
 5409              abc               // 2 spaces (< 3 so dont convert)
 5410            «   abc              // 3 spaces (convert)ˇ»
 5411                 abc            // 5 spaces (1 tab + 2 spaces)
 5412            \t\t\tabc           // Already tab indented
 5413            \t abc              // Tab followed by space
 5414             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5415               \t\t  \tabc      // Mixed indentation
 5416            \t \t  \t   \tabc   // Mixed indentation
 5417               \t  \tˇ
 5418            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5419        "}
 5420        .replace("·", "")
 5421        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5422    );
 5423    cx.update_editor(|e, window, cx| {
 5424        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5425    });
 5426    cx.assert_editor_state(
 5427        indoc! {"
 5428            ·
 5429            abc                 // No indentation
 5430             abc                // 1 space (< 3 so dont convert)
 5431              abc               // 2 spaces (< 3 so dont convert)
 5432            «\tabc              // 3 spaces (convert)ˇ»
 5433                 abc            // 5 spaces (1 tab + 2 spaces)
 5434            \t\t\tabc           // Already tab indented
 5435            \t abc              // Tab followed by space
 5436            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5437               \t\t  \tabc      // Mixed indentation
 5438            \t \t  \t   \tabc   // Mixed indentation
 5439            «\t\t\t
 5440            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5441        "}
 5442        .replace("·", "")
 5443        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5444    );
 5445
 5446    // SINGLE SELECTION
 5447    // Ln.1 "«" tests empty lines
 5448    // Ln.11 tests just leading whitespace
 5449    cx.set_state(indoc! {"
 5450        «
 5451        abc                 // No indentation
 5452         abc                // 1 space (< 3 so dont convert)
 5453          abc               // 2 spaces (< 3 so dont convert)
 5454           abc              // 3 spaces (convert)
 5455             abc            // 5 spaces (1 tab + 2 spaces)
 5456        \t\t\tabc           // Already tab indented
 5457        \t abc              // Tab followed by space
 5458         \tabc              // Space followed by tab (should be consumed due to tab)
 5459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5460           \t  \t
 5461           abc   \t         // Only the leading spaces should be convertedˇ»
 5462    "});
 5463    cx.update_editor(|e, window, cx| {
 5464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5465    });
 5466    cx.assert_editor_state(indoc! {"
 5467        «
 5468        abc                 // No indentation
 5469         abc                // 1 space (< 3 so dont convert)
 5470          abc               // 2 spaces (< 3 so dont convert)
 5471        \tabc              // 3 spaces (convert)
 5472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5473        \t\t\tabc           // Already tab indented
 5474        \t abc              // Tab followed by space
 5475        \tabc              // Space followed by tab (should be consumed due to tab)
 5476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5477        \t\t\t
 5478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5479    "});
 5480}
 5481
 5482#[gpui::test]
 5483async fn test_toggle_case(cx: &mut TestAppContext) {
 5484    init_test(cx, |_| {});
 5485
 5486    let mut cx = EditorTestContext::new(cx).await;
 5487
 5488    // If all lower case -> upper case
 5489    cx.set_state(indoc! {"
 5490        «hello worldˇ»
 5491    "});
 5492    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        «HELLO WORLDˇ»
 5495    "});
 5496
 5497    // If all upper case -> lower case
 5498    cx.set_state(indoc! {"
 5499        «HELLO WORLDˇ»
 5500    "});
 5501    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5502    cx.assert_editor_state(indoc! {"
 5503        «hello worldˇ»
 5504    "});
 5505
 5506    // If any upper case characters are identified -> lower case
 5507    // This matches JetBrains IDEs
 5508    cx.set_state(indoc! {"
 5509        «hEllo worldˇ»
 5510    "});
 5511    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5512    cx.assert_editor_state(indoc! {"
 5513        «hello worldˇ»
 5514    "});
 5515}
 5516
 5517#[gpui::test]
 5518async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5519    init_test(cx, |_| {});
 5520
 5521    let mut cx = EditorTestContext::new(cx).await;
 5522
 5523    cx.set_state(indoc! {"
 5524        «implement-windows-supportˇ»
 5525    "});
 5526    cx.update_editor(|e, window, cx| {
 5527        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5528    });
 5529    cx.assert_editor_state(indoc! {"
 5530        «Implement windows supportˇ»
 5531    "});
 5532}
 5533
 5534#[gpui::test]
 5535async fn test_manipulate_text(cx: &mut TestAppContext) {
 5536    init_test(cx, |_| {});
 5537
 5538    let mut cx = EditorTestContext::new(cx).await;
 5539
 5540    // Test convert_to_upper_case()
 5541    cx.set_state(indoc! {"
 5542        «hello worldˇ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «HELLO WORLDˇ»
 5547    "});
 5548
 5549    // Test convert_to_lower_case()
 5550    cx.set_state(indoc! {"
 5551        «HELLO WORLDˇ»
 5552    "});
 5553    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5554    cx.assert_editor_state(indoc! {"
 5555        «hello worldˇ»
 5556    "});
 5557
 5558    // Test multiple line, single selection case
 5559    cx.set_state(indoc! {"
 5560        «The quick brown
 5561        fox jumps over
 5562        the lazy dogˇ»
 5563    "});
 5564    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5565    cx.assert_editor_state(indoc! {"
 5566        «The Quick Brown
 5567        Fox Jumps Over
 5568        The Lazy Dogˇ»
 5569    "});
 5570
 5571    // Test multiple line, single selection case
 5572    cx.set_state(indoc! {"
 5573        «The quick brown
 5574        fox jumps over
 5575        the lazy dogˇ»
 5576    "});
 5577    cx.update_editor(|e, window, cx| {
 5578        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5579    });
 5580    cx.assert_editor_state(indoc! {"
 5581        «TheQuickBrown
 5582        FoxJumpsOver
 5583        TheLazyDogˇ»
 5584    "});
 5585
 5586    // From here on out, test more complex cases of manipulate_text()
 5587
 5588    // Test no selection case - should affect words cursors are in
 5589    // Cursor at beginning, middle, and end of word
 5590    cx.set_state(indoc! {"
 5591        ˇhello big beauˇtiful worldˇ
 5592    "});
 5593    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5594    cx.assert_editor_state(indoc! {"
 5595        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5596    "});
 5597
 5598    // Test multiple selections on a single line and across multiple lines
 5599    cx.set_state(indoc! {"
 5600        «Theˇ» quick «brown
 5601        foxˇ» jumps «overˇ»
 5602        the «lazyˇ» dog
 5603    "});
 5604    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5605    cx.assert_editor_state(indoc! {"
 5606        «THEˇ» quick «BROWN
 5607        FOXˇ» jumps «OVERˇ»
 5608        the «LAZYˇ» dog
 5609    "});
 5610
 5611    // Test case where text length grows
 5612    cx.set_state(indoc! {"
 5613        «tschüߡ»
 5614    "});
 5615    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5616    cx.assert_editor_state(indoc! {"
 5617        «TSCHÜSSˇ»
 5618    "});
 5619
 5620    // Test to make sure we don't crash when text shrinks
 5621    cx.set_state(indoc! {"
 5622        aaa_bbbˇ
 5623    "});
 5624    cx.update_editor(|e, window, cx| {
 5625        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5626    });
 5627    cx.assert_editor_state(indoc! {"
 5628        «aaaBbbˇ»
 5629    "});
 5630
 5631    // Test to make sure we all aware of the fact that each word can grow and shrink
 5632    // Final selections should be aware of this fact
 5633    cx.set_state(indoc! {"
 5634        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5635    "});
 5636    cx.update_editor(|e, window, cx| {
 5637        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5638    });
 5639    cx.assert_editor_state(indoc! {"
 5640        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5641    "});
 5642
 5643    cx.set_state(indoc! {"
 5644        «hElLo, WoRld!ˇ»
 5645    "});
 5646    cx.update_editor(|e, window, cx| {
 5647        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5648    });
 5649    cx.assert_editor_state(indoc! {"
 5650        «HeLlO, wOrLD!ˇ»
 5651    "});
 5652
 5653    // Test selections with `line_mode() = true`.
 5654    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5655    cx.set_state(indoc! {"
 5656        «The quick brown
 5657        fox jumps over
 5658        tˇ»he lazy dog
 5659    "});
 5660    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5661    cx.assert_editor_state(indoc! {"
 5662        «THE QUICK BROWN
 5663        FOX JUMPS OVER
 5664        THE LAZY DOGˇ»
 5665    "});
 5666}
 5667
 5668#[gpui::test]
 5669fn test_duplicate_line(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let editor = cx.add_window(|window, cx| {
 5673        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5674        build_editor(buffer, window, cx)
 5675    });
 5676    _ = editor.update(cx, |editor, window, cx| {
 5677        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5678            s.select_display_ranges([
 5679                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5681                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5682                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5683            ])
 5684        });
 5685        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5686        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5687        assert_eq!(
 5688            editor.selections.display_ranges(cx),
 5689            vec![
 5690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5691                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5692                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5693                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5694            ]
 5695        );
 5696    });
 5697
 5698    let editor = cx.add_window(|window, cx| {
 5699        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5700        build_editor(buffer, window, cx)
 5701    });
 5702    _ = editor.update(cx, |editor, window, cx| {
 5703        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5704            s.select_display_ranges([
 5705                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5707            ])
 5708        });
 5709        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5710        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5711        assert_eq!(
 5712            editor.selections.display_ranges(cx),
 5713            vec![
 5714                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5715                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5716            ]
 5717        );
 5718    });
 5719
 5720    // With `duplicate_line_up` the selections move to the duplicated lines,
 5721    // which are inserted above the original lines
 5722    let editor = cx.add_window(|window, cx| {
 5723        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5724        build_editor(buffer, window, cx)
 5725    });
 5726    _ = editor.update(cx, |editor, window, cx| {
 5727        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5728            s.select_display_ranges([
 5729                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5730                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5731                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5732                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5733            ])
 5734        });
 5735        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5736        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5737        assert_eq!(
 5738            editor.selections.display_ranges(cx),
 5739            vec![
 5740                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5741                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5742                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5743                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5744            ]
 5745        );
 5746    });
 5747
 5748    let editor = cx.add_window(|window, cx| {
 5749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5750        build_editor(buffer, window, cx)
 5751    });
 5752    _ = editor.update(cx, |editor, window, cx| {
 5753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5754            s.select_display_ranges([
 5755                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5756                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5757            ])
 5758        });
 5759        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5760        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5761        assert_eq!(
 5762            editor.selections.display_ranges(cx),
 5763            vec![
 5764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5765                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5766            ]
 5767        );
 5768    });
 5769
 5770    let editor = cx.add_window(|window, cx| {
 5771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5772        build_editor(buffer, window, cx)
 5773    });
 5774    _ = editor.update(cx, |editor, window, cx| {
 5775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5776            s.select_display_ranges([
 5777                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5778                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5779            ])
 5780        });
 5781        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5782        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5783        assert_eq!(
 5784            editor.selections.display_ranges(cx),
 5785            vec![
 5786                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5787                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5788            ]
 5789        );
 5790    });
 5791}
 5792
 5793#[gpui::test]
 5794fn test_move_line_up_down(cx: &mut TestAppContext) {
 5795    init_test(cx, |_| {});
 5796
 5797    let editor = cx.add_window(|window, cx| {
 5798        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5799        build_editor(buffer, window, cx)
 5800    });
 5801    _ = editor.update(cx, |editor, window, cx| {
 5802        editor.fold_creases(
 5803            vec![
 5804                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5805                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5806                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5807            ],
 5808            true,
 5809            window,
 5810            cx,
 5811        );
 5812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5813            s.select_display_ranges([
 5814                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5815                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5816                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5817                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5818            ])
 5819        });
 5820        assert_eq!(
 5821            editor.display_text(cx),
 5822            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5823        );
 5824
 5825        editor.move_line_up(&MoveLineUp, window, cx);
 5826        assert_eq!(
 5827            editor.display_text(cx),
 5828            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5829        );
 5830        assert_eq!(
 5831            editor.selections.display_ranges(cx),
 5832            vec![
 5833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5834                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5836                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5837            ]
 5838        );
 5839    });
 5840
 5841    _ = editor.update(cx, |editor, window, cx| {
 5842        editor.move_line_down(&MoveLineDown, window, cx);
 5843        assert_eq!(
 5844            editor.display_text(cx),
 5845            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5846        );
 5847        assert_eq!(
 5848            editor.selections.display_ranges(cx),
 5849            vec![
 5850                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5851                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5852                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5853                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5854            ]
 5855        );
 5856    });
 5857
 5858    _ = editor.update(cx, |editor, window, cx| {
 5859        editor.move_line_down(&MoveLineDown, window, cx);
 5860        assert_eq!(
 5861            editor.display_text(cx),
 5862            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5863        );
 5864        assert_eq!(
 5865            editor.selections.display_ranges(cx),
 5866            vec![
 5867                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5868                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5869                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5870                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5871            ]
 5872        );
 5873    });
 5874
 5875    _ = editor.update(cx, |editor, window, cx| {
 5876        editor.move_line_up(&MoveLineUp, window, cx);
 5877        assert_eq!(
 5878            editor.display_text(cx),
 5879            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5880        );
 5881        assert_eq!(
 5882            editor.selections.display_ranges(cx),
 5883            vec![
 5884                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5885                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5886                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5887                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5888            ]
 5889        );
 5890    });
 5891}
 5892
 5893#[gpui::test]
 5894fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5895    init_test(cx, |_| {});
 5896    let editor = cx.add_window(|window, cx| {
 5897        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5898        build_editor(buffer, window, cx)
 5899    });
 5900    _ = editor.update(cx, |editor, window, cx| {
 5901        editor.fold_creases(
 5902            vec![Crease::simple(
 5903                Point::new(6, 4)..Point::new(7, 4),
 5904                FoldPlaceholder::test(),
 5905            )],
 5906            true,
 5907            window,
 5908            cx,
 5909        );
 5910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5911            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5912        });
 5913        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5914        editor.move_line_up(&MoveLineUp, window, cx);
 5915        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5916        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5917    });
 5918}
 5919
 5920#[gpui::test]
 5921fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5922    init_test(cx, |_| {});
 5923
 5924    let editor = cx.add_window(|window, cx| {
 5925        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5926        build_editor(buffer, window, cx)
 5927    });
 5928    _ = editor.update(cx, |editor, window, cx| {
 5929        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5930        editor.insert_blocks(
 5931            [BlockProperties {
 5932                style: BlockStyle::Fixed,
 5933                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5934                height: Some(1),
 5935                render: Arc::new(|_| div().into_any()),
 5936                priority: 0,
 5937            }],
 5938            Some(Autoscroll::fit()),
 5939            cx,
 5940        );
 5941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5942            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5943        });
 5944        editor.move_line_down(&MoveLineDown, window, cx);
 5945    });
 5946}
 5947
 5948#[gpui::test]
 5949async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5950    init_test(cx, |_| {});
 5951
 5952    let mut cx = EditorTestContext::new(cx).await;
 5953    cx.set_state(
 5954        &"
 5955            ˇzero
 5956            one
 5957            two
 5958            three
 5959            four
 5960            five
 5961        "
 5962        .unindent(),
 5963    );
 5964
 5965    // Create a four-line block that replaces three lines of text.
 5966    cx.update_editor(|editor, window, cx| {
 5967        let snapshot = editor.snapshot(window, cx);
 5968        let snapshot = &snapshot.buffer_snapshot();
 5969        let placement = BlockPlacement::Replace(
 5970            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5971        );
 5972        editor.insert_blocks(
 5973            [BlockProperties {
 5974                placement,
 5975                height: Some(4),
 5976                style: BlockStyle::Sticky,
 5977                render: Arc::new(|_| gpui::div().into_any_element()),
 5978                priority: 0,
 5979            }],
 5980            None,
 5981            cx,
 5982        );
 5983    });
 5984
 5985    // Move down so that the cursor touches the block.
 5986    cx.update_editor(|editor, window, cx| {
 5987        editor.move_down(&Default::default(), window, cx);
 5988    });
 5989    cx.assert_editor_state(
 5990        &"
 5991            zero
 5992            «one
 5993            two
 5994            threeˇ»
 5995            four
 5996            five
 5997        "
 5998        .unindent(),
 5999    );
 6000
 6001    // Move down past the block.
 6002    cx.update_editor(|editor, window, cx| {
 6003        editor.move_down(&Default::default(), window, cx);
 6004    });
 6005    cx.assert_editor_state(
 6006        &"
 6007            zero
 6008            one
 6009            two
 6010            three
 6011            ˇfour
 6012            five
 6013        "
 6014        .unindent(),
 6015    );
 6016}
 6017
 6018#[gpui::test]
 6019fn test_transpose(cx: &mut TestAppContext) {
 6020    init_test(cx, |_| {});
 6021
 6022    _ = cx.add_window(|window, cx| {
 6023        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6024        editor.set_style(EditorStyle::default(), window, cx);
 6025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6026            s.select_ranges([1..1])
 6027        });
 6028        editor.transpose(&Default::default(), window, cx);
 6029        assert_eq!(editor.text(cx), "bac");
 6030        assert_eq!(
 6031            editor.selections.ranges(&editor.display_snapshot(cx)),
 6032            [2..2]
 6033        );
 6034
 6035        editor.transpose(&Default::default(), window, cx);
 6036        assert_eq!(editor.text(cx), "bca");
 6037        assert_eq!(
 6038            editor.selections.ranges(&editor.display_snapshot(cx)),
 6039            [3..3]
 6040        );
 6041
 6042        editor.transpose(&Default::default(), window, cx);
 6043        assert_eq!(editor.text(cx), "bac");
 6044        assert_eq!(
 6045            editor.selections.ranges(&editor.display_snapshot(cx)),
 6046            [3..3]
 6047        );
 6048
 6049        editor
 6050    });
 6051
 6052    _ = cx.add_window(|window, cx| {
 6053        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6054        editor.set_style(EditorStyle::default(), window, cx);
 6055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6056            s.select_ranges([3..3])
 6057        });
 6058        editor.transpose(&Default::default(), window, cx);
 6059        assert_eq!(editor.text(cx), "acb\nde");
 6060        assert_eq!(
 6061            editor.selections.ranges(&editor.display_snapshot(cx)),
 6062            [3..3]
 6063        );
 6064
 6065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6066            s.select_ranges([4..4])
 6067        });
 6068        editor.transpose(&Default::default(), window, cx);
 6069        assert_eq!(editor.text(cx), "acbd\ne");
 6070        assert_eq!(
 6071            editor.selections.ranges(&editor.display_snapshot(cx)),
 6072            [5..5]
 6073        );
 6074
 6075        editor.transpose(&Default::default(), window, cx);
 6076        assert_eq!(editor.text(cx), "acbde\n");
 6077        assert_eq!(
 6078            editor.selections.ranges(&editor.display_snapshot(cx)),
 6079            [6..6]
 6080        );
 6081
 6082        editor.transpose(&Default::default(), window, cx);
 6083        assert_eq!(editor.text(cx), "acbd\ne");
 6084        assert_eq!(
 6085            editor.selections.ranges(&editor.display_snapshot(cx)),
 6086            [6..6]
 6087        );
 6088
 6089        editor
 6090    });
 6091
 6092    _ = cx.add_window(|window, cx| {
 6093        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6094        editor.set_style(EditorStyle::default(), window, cx);
 6095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6096            s.select_ranges([1..1, 2..2, 4..4])
 6097        });
 6098        editor.transpose(&Default::default(), window, cx);
 6099        assert_eq!(editor.text(cx), "bacd\ne");
 6100        assert_eq!(
 6101            editor.selections.ranges(&editor.display_snapshot(cx)),
 6102            [2..2, 3..3, 5..5]
 6103        );
 6104
 6105        editor.transpose(&Default::default(), window, cx);
 6106        assert_eq!(editor.text(cx), "bcade\n");
 6107        assert_eq!(
 6108            editor.selections.ranges(&editor.display_snapshot(cx)),
 6109            [3..3, 4..4, 6..6]
 6110        );
 6111
 6112        editor.transpose(&Default::default(), window, cx);
 6113        assert_eq!(editor.text(cx), "bcda\ne");
 6114        assert_eq!(
 6115            editor.selections.ranges(&editor.display_snapshot(cx)),
 6116            [4..4, 6..6]
 6117        );
 6118
 6119        editor.transpose(&Default::default(), window, cx);
 6120        assert_eq!(editor.text(cx), "bcade\n");
 6121        assert_eq!(
 6122            editor.selections.ranges(&editor.display_snapshot(cx)),
 6123            [4..4, 6..6]
 6124        );
 6125
 6126        editor.transpose(&Default::default(), window, cx);
 6127        assert_eq!(editor.text(cx), "bcaed\n");
 6128        assert_eq!(
 6129            editor.selections.ranges(&editor.display_snapshot(cx)),
 6130            [5..5, 6..6]
 6131        );
 6132
 6133        editor
 6134    });
 6135
 6136    _ = cx.add_window(|window, cx| {
 6137        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6138        editor.set_style(EditorStyle::default(), window, cx);
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_ranges([4..4])
 6141        });
 6142        editor.transpose(&Default::default(), window, cx);
 6143        assert_eq!(editor.text(cx), "🏀🍐✋");
 6144        assert_eq!(
 6145            editor.selections.ranges(&editor.display_snapshot(cx)),
 6146            [8..8]
 6147        );
 6148
 6149        editor.transpose(&Default::default(), window, cx);
 6150        assert_eq!(editor.text(cx), "🏀✋🍐");
 6151        assert_eq!(
 6152            editor.selections.ranges(&editor.display_snapshot(cx)),
 6153            [11..11]
 6154        );
 6155
 6156        editor.transpose(&Default::default(), window, cx);
 6157        assert_eq!(editor.text(cx), "🏀🍐✋");
 6158        assert_eq!(
 6159            editor.selections.ranges(&editor.display_snapshot(cx)),
 6160            [11..11]
 6161        );
 6162
 6163        editor
 6164    });
 6165}
 6166
 6167#[gpui::test]
 6168async fn test_rewrap(cx: &mut TestAppContext) {
 6169    init_test(cx, |settings| {
 6170        settings.languages.0.extend([
 6171            (
 6172                "Markdown".into(),
 6173                LanguageSettingsContent {
 6174                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6175                    preferred_line_length: Some(40),
 6176                    ..Default::default()
 6177                },
 6178            ),
 6179            (
 6180                "Plain Text".into(),
 6181                LanguageSettingsContent {
 6182                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6183                    preferred_line_length: Some(40),
 6184                    ..Default::default()
 6185                },
 6186            ),
 6187            (
 6188                "C++".into(),
 6189                LanguageSettingsContent {
 6190                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6191                    preferred_line_length: Some(40),
 6192                    ..Default::default()
 6193                },
 6194            ),
 6195            (
 6196                "Python".into(),
 6197                LanguageSettingsContent {
 6198                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6199                    preferred_line_length: Some(40),
 6200                    ..Default::default()
 6201                },
 6202            ),
 6203            (
 6204                "Rust".into(),
 6205                LanguageSettingsContent {
 6206                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6207                    preferred_line_length: Some(40),
 6208                    ..Default::default()
 6209                },
 6210            ),
 6211        ])
 6212    });
 6213
 6214    let mut cx = EditorTestContext::new(cx).await;
 6215
 6216    let cpp_language = Arc::new(Language::new(
 6217        LanguageConfig {
 6218            name: "C++".into(),
 6219            line_comments: vec!["// ".into()],
 6220            ..LanguageConfig::default()
 6221        },
 6222        None,
 6223    ));
 6224    let python_language = Arc::new(Language::new(
 6225        LanguageConfig {
 6226            name: "Python".into(),
 6227            line_comments: vec!["# ".into()],
 6228            ..LanguageConfig::default()
 6229        },
 6230        None,
 6231    ));
 6232    let markdown_language = Arc::new(Language::new(
 6233        LanguageConfig {
 6234            name: "Markdown".into(),
 6235            rewrap_prefixes: vec![
 6236                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6237                regex::Regex::new("[-*+]\\s+").unwrap(),
 6238            ],
 6239            ..LanguageConfig::default()
 6240        },
 6241        None,
 6242    ));
 6243    let rust_language = Arc::new(
 6244        Language::new(
 6245            LanguageConfig {
 6246                name: "Rust".into(),
 6247                line_comments: vec!["// ".into(), "/// ".into()],
 6248                ..LanguageConfig::default()
 6249            },
 6250            Some(tree_sitter_rust::LANGUAGE.into()),
 6251        )
 6252        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6253        .unwrap(),
 6254    );
 6255
 6256    let plaintext_language = Arc::new(Language::new(
 6257        LanguageConfig {
 6258            name: "Plain Text".into(),
 6259            ..LanguageConfig::default()
 6260        },
 6261        None,
 6262    ));
 6263
 6264    // Test basic rewrapping of a long line with a cursor
 6265    assert_rewrap(
 6266        indoc! {"
 6267            // ˇThis is a long comment that needs to be wrapped.
 6268        "},
 6269        indoc! {"
 6270            // ˇThis is a long comment that needs to
 6271            // be wrapped.
 6272        "},
 6273        cpp_language.clone(),
 6274        &mut cx,
 6275    );
 6276
 6277    // Test rewrapping a full selection
 6278    assert_rewrap(
 6279        indoc! {"
 6280            «// This selected long comment needs to be wrapped.ˇ»"
 6281        },
 6282        indoc! {"
 6283            «// This selected long comment needs to
 6284            // be wrapped.ˇ»"
 6285        },
 6286        cpp_language.clone(),
 6287        &mut cx,
 6288    );
 6289
 6290    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6291    assert_rewrap(
 6292        indoc! {"
 6293            // ˇThis is the first line.
 6294            // Thisˇ is the second line.
 6295            // This is the thirdˇ line, all part of one paragraph.
 6296         "},
 6297        indoc! {"
 6298            // ˇThis is the first line. Thisˇ is the
 6299            // second line. This is the thirdˇ line,
 6300            // all part of one paragraph.
 6301         "},
 6302        cpp_language.clone(),
 6303        &mut cx,
 6304    );
 6305
 6306    // Test multiple cursors in different paragraphs trigger separate rewraps
 6307    assert_rewrap(
 6308        indoc! {"
 6309            // ˇThis is the first paragraph, first line.
 6310            // ˇThis is the first paragraph, second line.
 6311
 6312            // ˇThis is the second paragraph, first line.
 6313            // ˇThis is the second paragraph, second line.
 6314        "},
 6315        indoc! {"
 6316            // ˇThis is the first paragraph, first
 6317            // line. ˇThis is the first paragraph,
 6318            // second line.
 6319
 6320            // ˇThis is the second paragraph, first
 6321            // line. ˇThis is the second paragraph,
 6322            // second line.
 6323        "},
 6324        cpp_language.clone(),
 6325        &mut cx,
 6326    );
 6327
 6328    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6329    assert_rewrap(
 6330        indoc! {"
 6331            «// A regular long long comment to be wrapped.
 6332            /// A documentation long comment to be wrapped.ˇ»
 6333          "},
 6334        indoc! {"
 6335            «// A regular long long comment to be
 6336            // wrapped.
 6337            /// A documentation long comment to be
 6338            /// wrapped.ˇ»
 6339          "},
 6340        rust_language.clone(),
 6341        &mut cx,
 6342    );
 6343
 6344    // Test that change in indentation level trigger seperate rewraps
 6345    assert_rewrap(
 6346        indoc! {"
 6347            fn foo() {
 6348                «// This is a long comment at the base indent.
 6349                    // This is a long comment at the next indent.ˇ»
 6350            }
 6351        "},
 6352        indoc! {"
 6353            fn foo() {
 6354                «// This is a long comment at the
 6355                // base indent.
 6356                    // This is a long comment at the
 6357                    // next indent.ˇ»
 6358            }
 6359        "},
 6360        rust_language.clone(),
 6361        &mut cx,
 6362    );
 6363
 6364    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6365    assert_rewrap(
 6366        indoc! {"
 6367            # ˇThis is a long comment using a pound sign.
 6368        "},
 6369        indoc! {"
 6370            # ˇThis is a long comment using a pound
 6371            # sign.
 6372        "},
 6373        python_language,
 6374        &mut cx,
 6375    );
 6376
 6377    // Test rewrapping only affects comments, not code even when selected
 6378    assert_rewrap(
 6379        indoc! {"
 6380            «/// This doc comment is long and should be wrapped.
 6381            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6382        "},
 6383        indoc! {"
 6384            «/// This doc comment is long and should
 6385            /// be wrapped.
 6386            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6387        "},
 6388        rust_language.clone(),
 6389        &mut cx,
 6390    );
 6391
 6392    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6393    assert_rewrap(
 6394        indoc! {"
 6395            # Header
 6396
 6397            A long long long line of markdown text to wrap.ˇ
 6398         "},
 6399        indoc! {"
 6400            # Header
 6401
 6402            A long long long line of markdown text
 6403            to wrap.ˇ
 6404         "},
 6405        markdown_language.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6410    assert_rewrap(
 6411        indoc! {"
 6412            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6413            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6414            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6415        "},
 6416        indoc! {"
 6417            «1. This is a numbered list item that is
 6418               very long and needs to be wrapped
 6419               properly.
 6420            2. This is a numbered list item that is
 6421               very long and needs to be wrapped
 6422               properly.
 6423            - This is an unordered list item that is
 6424              also very long and should not merge
 6425              with the numbered item.ˇ»
 6426        "},
 6427        markdown_language.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6432    assert_rewrap(
 6433        indoc! {"
 6434            «1. This is a numbered list item that is
 6435            very long and needs to be wrapped
 6436            properly.
 6437            2. This is a numbered list item that is
 6438            very long and needs to be wrapped
 6439            properly.
 6440            - This is an unordered list item that is
 6441            also very long and should not merge with
 6442            the numbered item.ˇ»
 6443        "},
 6444        indoc! {"
 6445            «1. This is a numbered list item that is
 6446               very long and needs to be wrapped
 6447               properly.
 6448            2. This is a numbered list item that is
 6449               very long and needs to be wrapped
 6450               properly.
 6451            - This is an unordered list item that is
 6452              also very long and should not merge
 6453              with the numbered item.ˇ»
 6454        "},
 6455        markdown_language.clone(),
 6456        &mut cx,
 6457    );
 6458
 6459    // Test that rewrapping maintain indents even when they already exists.
 6460    assert_rewrap(
 6461        indoc! {"
 6462            «1. This is a numbered list
 6463               item that is very long and needs to be wrapped properly.
 6464            2. This is a numbered list
 6465               item that is very long and needs to be wrapped properly.
 6466            - This is an unordered list item that is also very long and
 6467              should not merge with the numbered item.ˇ»
 6468        "},
 6469        indoc! {"
 6470            «1. This is a numbered list item that is
 6471               very long and needs to be wrapped
 6472               properly.
 6473            2. This is a numbered list item that is
 6474               very long and needs to be wrapped
 6475               properly.
 6476            - This is an unordered list item that is
 6477              also very long and should not merge
 6478              with the numbered item.ˇ»
 6479        "},
 6480        markdown_language,
 6481        &mut cx,
 6482    );
 6483
 6484    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6485    assert_rewrap(
 6486        indoc! {"
 6487            ˇThis is a very long line of plain text that will be wrapped.
 6488        "},
 6489        indoc! {"
 6490            ˇThis is a very long line of plain text
 6491            that will be wrapped.
 6492        "},
 6493        plaintext_language.clone(),
 6494        &mut cx,
 6495    );
 6496
 6497    // Test that non-commented code acts as a paragraph boundary within a selection
 6498    assert_rewrap(
 6499        indoc! {"
 6500               «// This is the first long comment block to be wrapped.
 6501               fn my_func(a: u32);
 6502               // This is the second long comment block to be wrapped.ˇ»
 6503           "},
 6504        indoc! {"
 6505               «// This is the first long comment block
 6506               // to be wrapped.
 6507               fn my_func(a: u32);
 6508               // This is the second long comment block
 6509               // to be wrapped.ˇ»
 6510           "},
 6511        rust_language,
 6512        &mut cx,
 6513    );
 6514
 6515    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6516    assert_rewrap(
 6517        indoc! {"
 6518            «ˇThis is a very long line that will be wrapped.
 6519
 6520            This is another paragraph in the same selection.»
 6521
 6522            «\tThis is a very long indented line that will be wrapped.ˇ»
 6523         "},
 6524        indoc! {"
 6525            «ˇThis is a very long line that will be
 6526            wrapped.
 6527
 6528            This is another paragraph in the same
 6529            selection.»
 6530
 6531            «\tThis is a very long indented line
 6532            \tthat will be wrapped.ˇ»
 6533         "},
 6534        plaintext_language,
 6535        &mut cx,
 6536    );
 6537
 6538    // Test that an empty comment line acts as a paragraph boundary
 6539    assert_rewrap(
 6540        indoc! {"
 6541            // ˇThis is a long comment that will be wrapped.
 6542            //
 6543            // And this is another long comment that will also be wrapped.ˇ
 6544         "},
 6545        indoc! {"
 6546            // ˇThis is a long comment that will be
 6547            // wrapped.
 6548            //
 6549            // And this is another long comment that
 6550            // will also be wrapped.ˇ
 6551         "},
 6552        cpp_language,
 6553        &mut cx,
 6554    );
 6555
 6556    #[track_caller]
 6557    fn assert_rewrap(
 6558        unwrapped_text: &str,
 6559        wrapped_text: &str,
 6560        language: Arc<Language>,
 6561        cx: &mut EditorTestContext,
 6562    ) {
 6563        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6564        cx.set_state(unwrapped_text);
 6565        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6566        cx.assert_editor_state(wrapped_text);
 6567    }
 6568}
 6569
 6570#[gpui::test]
 6571async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6572    init_test(cx, |settings| {
 6573        settings.languages.0.extend([(
 6574            "Rust".into(),
 6575            LanguageSettingsContent {
 6576                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6577                preferred_line_length: Some(40),
 6578                ..Default::default()
 6579            },
 6580        )])
 6581    });
 6582
 6583    let mut cx = EditorTestContext::new(cx).await;
 6584
 6585    let rust_lang = Arc::new(
 6586        Language::new(
 6587            LanguageConfig {
 6588                name: "Rust".into(),
 6589                line_comments: vec!["// ".into()],
 6590                block_comment: Some(BlockCommentConfig {
 6591                    start: "/*".into(),
 6592                    end: "*/".into(),
 6593                    prefix: "* ".into(),
 6594                    tab_size: 1,
 6595                }),
 6596                documentation_comment: Some(BlockCommentConfig {
 6597                    start: "/**".into(),
 6598                    end: "*/".into(),
 6599                    prefix: "* ".into(),
 6600                    tab_size: 1,
 6601                }),
 6602
 6603                ..LanguageConfig::default()
 6604            },
 6605            Some(tree_sitter_rust::LANGUAGE.into()),
 6606        )
 6607        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6608        .unwrap(),
 6609    );
 6610
 6611    // regular block comment
 6612    assert_rewrap(
 6613        indoc! {"
 6614            /*
 6615             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6616             */
 6617            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6618        "},
 6619        indoc! {"
 6620            /*
 6621             *ˇ Lorem ipsum dolor sit amet,
 6622             * consectetur adipiscing elit.
 6623             */
 6624            /*
 6625             *ˇ Lorem ipsum dolor sit amet,
 6626             * consectetur adipiscing elit.
 6627             */
 6628        "},
 6629        rust_lang.clone(),
 6630        &mut cx,
 6631    );
 6632
 6633    // indent is respected
 6634    assert_rewrap(
 6635        indoc! {"
 6636            {}
 6637                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6638        "},
 6639        indoc! {"
 6640            {}
 6641                /*
 6642                 *ˇ Lorem ipsum dolor sit amet,
 6643                 * consectetur adipiscing elit.
 6644                 */
 6645        "},
 6646        rust_lang.clone(),
 6647        &mut cx,
 6648    );
 6649
 6650    // short block comments with inline delimiters
 6651    assert_rewrap(
 6652        indoc! {"
 6653            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6654            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6655             */
 6656            /*
 6657             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658        "},
 6659        indoc! {"
 6660            /*
 6661             *ˇ Lorem ipsum dolor sit amet,
 6662             * consectetur adipiscing elit.
 6663             */
 6664            /*
 6665             *ˇ Lorem ipsum dolor sit amet,
 6666             * consectetur adipiscing elit.
 6667             */
 6668            /*
 6669             *ˇ Lorem ipsum dolor sit amet,
 6670             * consectetur adipiscing elit.
 6671             */
 6672        "},
 6673        rust_lang.clone(),
 6674        &mut cx,
 6675    );
 6676
 6677    // multiline block comment with inline start/end delimiters
 6678    assert_rewrap(
 6679        indoc! {"
 6680            /*ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit. */
 6682        "},
 6683        indoc! {"
 6684            /*
 6685             *ˇ Lorem ipsum dolor sit amet,
 6686             * consectetur adipiscing elit.
 6687             */
 6688        "},
 6689        rust_lang.clone(),
 6690        &mut cx,
 6691    );
 6692
 6693    // block comment rewrap still respects paragraph bounds
 6694    assert_rewrap(
 6695        indoc! {"
 6696            /*
 6697             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6698             *
 6699             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6700             */
 6701        "},
 6702        indoc! {"
 6703            /*
 6704             *ˇ Lorem ipsum dolor sit amet,
 6705             * consectetur adipiscing elit.
 6706             *
 6707             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6708             */
 6709        "},
 6710        rust_lang.clone(),
 6711        &mut cx,
 6712    );
 6713
 6714    // documentation comments
 6715    assert_rewrap(
 6716        indoc! {"
 6717            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6718            /**
 6719             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6720             */
 6721        "},
 6722        indoc! {"
 6723            /**
 6724             *ˇ Lorem ipsum dolor sit amet,
 6725             * consectetur adipiscing elit.
 6726             */
 6727            /**
 6728             *ˇ Lorem ipsum dolor sit amet,
 6729             * consectetur adipiscing elit.
 6730             */
 6731        "},
 6732        rust_lang.clone(),
 6733        &mut cx,
 6734    );
 6735
 6736    // different, adjacent comments
 6737    assert_rewrap(
 6738        indoc! {"
 6739            /**
 6740             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6741             */
 6742            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6743            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6744        "},
 6745        indoc! {"
 6746            /**
 6747             *ˇ Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit.
 6749             */
 6750            /*
 6751             *ˇ Lorem ipsum dolor sit amet,
 6752             * consectetur adipiscing elit.
 6753             */
 6754            //ˇ Lorem ipsum dolor sit amet,
 6755            // consectetur adipiscing elit.
 6756        "},
 6757        rust_lang.clone(),
 6758        &mut cx,
 6759    );
 6760
 6761    // selection w/ single short block comment
 6762    assert_rewrap(
 6763        indoc! {"
 6764            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6765        "},
 6766        indoc! {"
 6767            «/*
 6768             * Lorem ipsum dolor sit amet,
 6769             * consectetur adipiscing elit.
 6770             */ˇ»
 6771        "},
 6772        rust_lang.clone(),
 6773        &mut cx,
 6774    );
 6775
 6776    // rewrapping a single comment w/ abutting comments
 6777    assert_rewrap(
 6778        indoc! {"
 6779            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6780            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6781        "},
 6782        indoc! {"
 6783            /*
 6784             * ˇLorem ipsum dolor sit amet,
 6785             * consectetur adipiscing elit.
 6786             */
 6787            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6788        "},
 6789        rust_lang.clone(),
 6790        &mut cx,
 6791    );
 6792
 6793    // selection w/ non-abutting short block comments
 6794    assert_rewrap(
 6795        indoc! {"
 6796            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6797
 6798            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6799        "},
 6800        indoc! {"
 6801            «/*
 6802             * Lorem ipsum dolor sit amet,
 6803             * consectetur adipiscing elit.
 6804             */
 6805
 6806            /*
 6807             * Lorem ipsum dolor sit amet,
 6808             * consectetur adipiscing elit.
 6809             */ˇ»
 6810        "},
 6811        rust_lang.clone(),
 6812        &mut cx,
 6813    );
 6814
 6815    // selection of multiline block comments
 6816    assert_rewrap(
 6817        indoc! {"
 6818            «/* Lorem ipsum dolor sit amet,
 6819             * consectetur adipiscing elit. */ˇ»
 6820        "},
 6821        indoc! {"
 6822            «/*
 6823             * Lorem ipsum dolor sit amet,
 6824             * consectetur adipiscing elit.
 6825             */ˇ»
 6826        "},
 6827        rust_lang.clone(),
 6828        &mut cx,
 6829    );
 6830
 6831    // partial selection of multiline block comments
 6832    assert_rewrap(
 6833        indoc! {"
 6834            «/* Lorem ipsum dolor sit amet,ˇ»
 6835             * consectetur adipiscing elit. */
 6836            /* Lorem ipsum dolor sit amet,
 6837             «* consectetur adipiscing elit. */ˇ»
 6838        "},
 6839        indoc! {"
 6840            «/*
 6841             * Lorem ipsum dolor sit amet,ˇ»
 6842             * consectetur adipiscing elit. */
 6843            /* Lorem ipsum dolor sit amet,
 6844             «* consectetur adipiscing elit.
 6845             */ˇ»
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // selection w/ abutting short block comments
 6852    // TODO: should not be combined; should rewrap as 2 comments
 6853    assert_rewrap(
 6854        indoc! {"
 6855            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6856            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6857        "},
 6858        // desired behavior:
 6859        // indoc! {"
 6860        //     «/*
 6861        //      * Lorem ipsum dolor sit amet,
 6862        //      * consectetur adipiscing elit.
 6863        //      */
 6864        //     /*
 6865        //      * Lorem ipsum dolor sit amet,
 6866        //      * consectetur adipiscing elit.
 6867        //      */ˇ»
 6868        // "},
 6869        // actual behaviour:
 6870        indoc! {"
 6871            «/*
 6872             * Lorem ipsum dolor sit amet,
 6873             * consectetur adipiscing elit. Lorem
 6874             * ipsum dolor sit amet, consectetur
 6875             * adipiscing elit.
 6876             */ˇ»
 6877        "},
 6878        rust_lang.clone(),
 6879        &mut cx,
 6880    );
 6881
 6882    // TODO: same as above, but with delimiters on separate line
 6883    // assert_rewrap(
 6884    //     indoc! {"
 6885    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6886    //          */
 6887    //         /*
 6888    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6889    //     "},
 6890    //     // desired:
 6891    //     // indoc! {"
 6892    //     //     «/*
 6893    //     //      * Lorem ipsum dolor sit amet,
 6894    //     //      * consectetur adipiscing elit.
 6895    //     //      */
 6896    //     //     /*
 6897    //     //      * Lorem ipsum dolor sit amet,
 6898    //     //      * consectetur adipiscing elit.
 6899    //     //      */ˇ»
 6900    //     // "},
 6901    //     // actual: (but with trailing w/s on the empty lines)
 6902    //     indoc! {"
 6903    //         «/*
 6904    //          * Lorem ipsum dolor sit amet,
 6905    //          * consectetur adipiscing elit.
 6906    //          *
 6907    //          */
 6908    //         /*
 6909    //          *
 6910    //          * Lorem ipsum dolor sit amet,
 6911    //          * consectetur adipiscing elit.
 6912    //          */ˇ»
 6913    //     "},
 6914    //     rust_lang.clone(),
 6915    //     &mut cx,
 6916    // );
 6917
 6918    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6919    assert_rewrap(
 6920        indoc! {"
 6921            /*
 6922             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6923             */
 6924            /*
 6925             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6926            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6927        "},
 6928        // desired:
 6929        // indoc! {"
 6930        //     /*
 6931        //      *ˇ Lorem ipsum dolor sit amet,
 6932        //      * consectetur adipiscing elit.
 6933        //      */
 6934        //     /*
 6935        //      *ˇ Lorem ipsum dolor sit amet,
 6936        //      * consectetur adipiscing elit.
 6937        //      */
 6938        //     /*
 6939        //      *ˇ Lorem ipsum dolor sit amet
 6940        //      */ /* consectetur adipiscing elit. */
 6941        // "},
 6942        // actual:
 6943        indoc! {"
 6944            /*
 6945             //ˇ Lorem ipsum dolor sit amet,
 6946             // consectetur adipiscing elit.
 6947             */
 6948            /*
 6949             * //ˇ Lorem ipsum dolor sit amet,
 6950             * consectetur adipiscing elit.
 6951             */
 6952            /*
 6953             *ˇ Lorem ipsum dolor sit amet */ /*
 6954             * consectetur adipiscing elit.
 6955             */
 6956        "},
 6957        rust_lang,
 6958        &mut cx,
 6959    );
 6960
 6961    #[track_caller]
 6962    fn assert_rewrap(
 6963        unwrapped_text: &str,
 6964        wrapped_text: &str,
 6965        language: Arc<Language>,
 6966        cx: &mut EditorTestContext,
 6967    ) {
 6968        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6969        cx.set_state(unwrapped_text);
 6970        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6971        cx.assert_editor_state(wrapped_text);
 6972    }
 6973}
 6974
 6975#[gpui::test]
 6976async fn test_hard_wrap(cx: &mut TestAppContext) {
 6977    init_test(cx, |_| {});
 6978    let mut cx = EditorTestContext::new(cx).await;
 6979
 6980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6981    cx.update_editor(|editor, _, cx| {
 6982        editor.set_hard_wrap(Some(14), cx);
 6983    });
 6984
 6985    cx.set_state(indoc!(
 6986        "
 6987        one two three ˇ
 6988        "
 6989    ));
 6990    cx.simulate_input("four");
 6991    cx.run_until_parked();
 6992
 6993    cx.assert_editor_state(indoc!(
 6994        "
 6995        one two three
 6996        fourˇ
 6997        "
 6998    ));
 6999
 7000    cx.update_editor(|editor, window, cx| {
 7001        editor.newline(&Default::default(), window, cx);
 7002    });
 7003    cx.run_until_parked();
 7004    cx.assert_editor_state(indoc!(
 7005        "
 7006        one two three
 7007        four
 7008        ˇ
 7009        "
 7010    ));
 7011
 7012    cx.simulate_input("five");
 7013    cx.run_until_parked();
 7014    cx.assert_editor_state(indoc!(
 7015        "
 7016        one two three
 7017        four
 7018        fiveˇ
 7019        "
 7020    ));
 7021
 7022    cx.update_editor(|editor, window, cx| {
 7023        editor.newline(&Default::default(), window, cx);
 7024    });
 7025    cx.run_until_parked();
 7026    cx.simulate_input("# ");
 7027    cx.run_until_parked();
 7028    cx.assert_editor_state(indoc!(
 7029        "
 7030        one two three
 7031        four
 7032        five
 7033        # ˇ
 7034        "
 7035    ));
 7036
 7037    cx.update_editor(|editor, window, cx| {
 7038        editor.newline(&Default::default(), window, cx);
 7039    });
 7040    cx.run_until_parked();
 7041    cx.assert_editor_state(indoc!(
 7042        "
 7043        one two three
 7044        four
 7045        five
 7046        #\x20
 7047 7048        "
 7049    ));
 7050
 7051    cx.simulate_input(" 6");
 7052    cx.run_until_parked();
 7053    cx.assert_editor_state(indoc!(
 7054        "
 7055        one two three
 7056        four
 7057        five
 7058        #
 7059        # 6ˇ
 7060        "
 7061    ));
 7062}
 7063
 7064#[gpui::test]
 7065async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7066    init_test(cx, |_| {});
 7067
 7068    let mut cx = EditorTestContext::new(cx).await;
 7069
 7070    cx.set_state(indoc! {"The quick brownˇ"});
 7071    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7072    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7073
 7074    cx.set_state(indoc! {"The emacs foxˇ"});
 7075    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7076    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7077
 7078    cx.set_state(indoc! {"
 7079        The quick« brownˇ»
 7080        fox jumps overˇ
 7081        the lazy dog"});
 7082    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7083    cx.assert_editor_state(indoc! {"
 7084        The quickˇ
 7085        ˇthe lazy dog"});
 7086
 7087    cx.set_state(indoc! {"
 7088        The quick« brownˇ»
 7089        fox jumps overˇ
 7090        the lazy dog"});
 7091    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7092    cx.assert_editor_state(indoc! {"
 7093        The quickˇ
 7094        fox jumps overˇthe lazy dog"});
 7095
 7096    cx.set_state(indoc! {"
 7097        The quick« brownˇ»
 7098        fox jumps overˇ
 7099        the lazy dog"});
 7100    cx.update_editor(|e, window, cx| {
 7101        e.cut_to_end_of_line(
 7102            &CutToEndOfLine {
 7103                stop_at_newlines: true,
 7104            },
 7105            window,
 7106            cx,
 7107        )
 7108    });
 7109    cx.assert_editor_state(indoc! {"
 7110        The quickˇ
 7111        fox jumps overˇ
 7112        the lazy dog"});
 7113
 7114    cx.set_state(indoc! {"
 7115        The quick« brownˇ»
 7116        fox jumps overˇ
 7117        the lazy dog"});
 7118    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7119    cx.assert_editor_state(indoc! {"
 7120        The quickˇ
 7121        fox jumps overˇthe lazy dog"});
 7122}
 7123
 7124#[gpui::test]
 7125async fn test_clipboard(cx: &mut TestAppContext) {
 7126    init_test(cx, |_| {});
 7127
 7128    let mut cx = EditorTestContext::new(cx).await;
 7129
 7130    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7131    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7132    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7133
 7134    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7135    cx.set_state("two ˇfour ˇsix ˇ");
 7136    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7137    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7138
 7139    // Paste again but with only two cursors. Since the number of cursors doesn't
 7140    // match the number of slices in the clipboard, the entire clipboard text
 7141    // is pasted at each cursor.
 7142    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7143    cx.update_editor(|e, window, cx| {
 7144        e.handle_input("( ", window, cx);
 7145        e.paste(&Paste, window, cx);
 7146        e.handle_input(") ", window, cx);
 7147    });
 7148    cx.assert_editor_state(
 7149        &([
 7150            "( one✅ ",
 7151            "three ",
 7152            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7153            "three ",
 7154            "five ) ˇ",
 7155        ]
 7156        .join("\n")),
 7157    );
 7158
 7159    // Cut with three selections, one of which is full-line.
 7160    cx.set_state(indoc! {"
 7161        1«2ˇ»3
 7162        4ˇ567
 7163        «8ˇ»9"});
 7164    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7165    cx.assert_editor_state(indoc! {"
 7166        1ˇ3
 7167        ˇ9"});
 7168
 7169    // Paste with three selections, noticing how the copied selection that was full-line
 7170    // gets inserted before the second cursor.
 7171    cx.set_state(indoc! {"
 7172        1ˇ3
 7173 7174        «oˇ»ne"});
 7175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7176    cx.assert_editor_state(indoc! {"
 7177        12ˇ3
 7178        4567
 7179 7180        8ˇne"});
 7181
 7182    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7183    cx.set_state(indoc! {"
 7184        The quick brown
 7185        fox juˇmps over
 7186        the lazy dog"});
 7187    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7188    assert_eq!(
 7189        cx.read_from_clipboard()
 7190            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7191        Some("fox jumps over\n".to_string())
 7192    );
 7193
 7194    // Paste with three selections, noticing how the copied full-line selection is inserted
 7195    // before the empty selections but replaces the selection that is non-empty.
 7196    cx.set_state(indoc! {"
 7197        Tˇhe quick brown
 7198        «foˇ»x jumps over
 7199        tˇhe lazy dog"});
 7200    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7201    cx.assert_editor_state(indoc! {"
 7202        fox jumps over
 7203        Tˇhe quick brown
 7204        fox jumps over
 7205        ˇx jumps over
 7206        fox jumps over
 7207        tˇhe lazy dog"});
 7208}
 7209
 7210#[gpui::test]
 7211async fn test_copy_trim(cx: &mut TestAppContext) {
 7212    init_test(cx, |_| {});
 7213
 7214    let mut cx = EditorTestContext::new(cx).await;
 7215    cx.set_state(
 7216        r#"            «for selection in selections.iter() {
 7217            let mut start = selection.start;
 7218            let mut end = selection.end;
 7219            let is_entire_line = selection.is_empty();
 7220            if is_entire_line {
 7221                start = Point::new(start.row, 0);ˇ»
 7222                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7223            }
 7224        "#,
 7225    );
 7226    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7227    assert_eq!(
 7228        cx.read_from_clipboard()
 7229            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7230        Some(
 7231            "for selection in selections.iter() {
 7232            let mut start = selection.start;
 7233            let mut end = selection.end;
 7234            let is_entire_line = selection.is_empty();
 7235            if is_entire_line {
 7236                start = Point::new(start.row, 0);"
 7237                .to_string()
 7238        ),
 7239        "Regular copying preserves all indentation selected",
 7240    );
 7241    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7242    assert_eq!(
 7243        cx.read_from_clipboard()
 7244            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7245        Some(
 7246            "for selection in selections.iter() {
 7247let mut start = selection.start;
 7248let mut end = selection.end;
 7249let is_entire_line = selection.is_empty();
 7250if is_entire_line {
 7251    start = Point::new(start.row, 0);"
 7252                .to_string()
 7253        ),
 7254        "Copying with stripping should strip all leading whitespaces"
 7255    );
 7256
 7257    cx.set_state(
 7258        r#"       «     for selection in selections.iter() {
 7259            let mut start = selection.start;
 7260            let mut end = selection.end;
 7261            let is_entire_line = selection.is_empty();
 7262            if is_entire_line {
 7263                start = Point::new(start.row, 0);ˇ»
 7264                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7265            }
 7266        "#,
 7267    );
 7268    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7269    assert_eq!(
 7270        cx.read_from_clipboard()
 7271            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7272        Some(
 7273            "     for selection in selections.iter() {
 7274            let mut start = selection.start;
 7275            let mut end = selection.end;
 7276            let is_entire_line = selection.is_empty();
 7277            if is_entire_line {
 7278                start = Point::new(start.row, 0);"
 7279                .to_string()
 7280        ),
 7281        "Regular copying preserves all indentation selected",
 7282    );
 7283    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7284    assert_eq!(
 7285        cx.read_from_clipboard()
 7286            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7287        Some(
 7288            "for selection in selections.iter() {
 7289let mut start = selection.start;
 7290let mut end = selection.end;
 7291let is_entire_line = selection.is_empty();
 7292if is_entire_line {
 7293    start = Point::new(start.row, 0);"
 7294                .to_string()
 7295        ),
 7296        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7297    );
 7298
 7299    cx.set_state(
 7300        r#"       «ˇ     for selection in selections.iter() {
 7301            let mut start = selection.start;
 7302            let mut end = selection.end;
 7303            let is_entire_line = selection.is_empty();
 7304            if is_entire_line {
 7305                start = Point::new(start.row, 0);»
 7306                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7307            }
 7308        "#,
 7309    );
 7310    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7311    assert_eq!(
 7312        cx.read_from_clipboard()
 7313            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7314        Some(
 7315            "     for selection in selections.iter() {
 7316            let mut start = selection.start;
 7317            let mut end = selection.end;
 7318            let is_entire_line = selection.is_empty();
 7319            if is_entire_line {
 7320                start = Point::new(start.row, 0);"
 7321                .to_string()
 7322        ),
 7323        "Regular copying for reverse selection works the same",
 7324    );
 7325    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7326    assert_eq!(
 7327        cx.read_from_clipboard()
 7328            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7329        Some(
 7330            "for selection in selections.iter() {
 7331let mut start = selection.start;
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);"
 7336                .to_string()
 7337        ),
 7338        "Copying with stripping for reverse selection works the same"
 7339    );
 7340
 7341    cx.set_state(
 7342        r#"            for selection «in selections.iter() {
 7343            let mut start = selection.start;
 7344            let mut end = selection.end;
 7345            let is_entire_line = selection.is_empty();
 7346            if is_entire_line {
 7347                start = Point::new(start.row, 0);ˇ»
 7348                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7349            }
 7350        "#,
 7351    );
 7352    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7353    assert_eq!(
 7354        cx.read_from_clipboard()
 7355            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7356        Some(
 7357            "in selections.iter() {
 7358            let mut start = selection.start;
 7359            let mut end = selection.end;
 7360            let is_entire_line = selection.is_empty();
 7361            if is_entire_line {
 7362                start = Point::new(start.row, 0);"
 7363                .to_string()
 7364        ),
 7365        "When selecting past the indent, the copying works as usual",
 7366    );
 7367    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7368    assert_eq!(
 7369        cx.read_from_clipboard()
 7370            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7371        Some(
 7372            "in selections.iter() {
 7373            let mut start = selection.start;
 7374            let mut end = selection.end;
 7375            let is_entire_line = selection.is_empty();
 7376            if is_entire_line {
 7377                start = Point::new(start.row, 0);"
 7378                .to_string()
 7379        ),
 7380        "When selecting past the indent, nothing is trimmed"
 7381    );
 7382
 7383    cx.set_state(
 7384        r#"            «for selection in selections.iter() {
 7385            let mut start = selection.start;
 7386
 7387            let mut end = selection.end;
 7388            let is_entire_line = selection.is_empty();
 7389            if is_entire_line {
 7390                start = Point::new(start.row, 0);
 7391ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7392            }
 7393        "#,
 7394    );
 7395    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7396    assert_eq!(
 7397        cx.read_from_clipboard()
 7398            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7399        Some(
 7400            "for selection in selections.iter() {
 7401let mut start = selection.start;
 7402
 7403let mut end = selection.end;
 7404let is_entire_line = selection.is_empty();
 7405if is_entire_line {
 7406    start = Point::new(start.row, 0);
 7407"
 7408            .to_string()
 7409        ),
 7410        "Copying with stripping should ignore empty lines"
 7411    );
 7412}
 7413
 7414#[gpui::test]
 7415async fn test_paste_multiline(cx: &mut TestAppContext) {
 7416    init_test(cx, |_| {});
 7417
 7418    let mut cx = EditorTestContext::new(cx).await;
 7419    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7420
 7421    // Cut an indented block, without the leading whitespace.
 7422    cx.set_state(indoc! {"
 7423        const a: B = (
 7424            c(),
 7425            «d(
 7426                e,
 7427                f
 7428            )ˇ»
 7429        );
 7430    "});
 7431    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7432    cx.assert_editor_state(indoc! {"
 7433        const a: B = (
 7434            c(),
 7435            ˇ
 7436        );
 7437    "});
 7438
 7439    // Paste it at the same position.
 7440    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7441    cx.assert_editor_state(indoc! {"
 7442        const a: B = (
 7443            c(),
 7444            d(
 7445                e,
 7446                f
 7447 7448        );
 7449    "});
 7450
 7451    // Paste it at a line with a lower indent level.
 7452    cx.set_state(indoc! {"
 7453        ˇ
 7454        const a: B = (
 7455            c(),
 7456        );
 7457    "});
 7458    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7459    cx.assert_editor_state(indoc! {"
 7460        d(
 7461            e,
 7462            f
 7463 7464        const a: B = (
 7465            c(),
 7466        );
 7467    "});
 7468
 7469    // Cut an indented block, with the leading whitespace.
 7470    cx.set_state(indoc! {"
 7471        const a: B = (
 7472            c(),
 7473        «    d(
 7474                e,
 7475                f
 7476            )
 7477        ˇ»);
 7478    "});
 7479    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7480    cx.assert_editor_state(indoc! {"
 7481        const a: B = (
 7482            c(),
 7483        ˇ);
 7484    "});
 7485
 7486    // Paste it at the same position.
 7487    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7488    cx.assert_editor_state(indoc! {"
 7489        const a: B = (
 7490            c(),
 7491            d(
 7492                e,
 7493                f
 7494            )
 7495        ˇ);
 7496    "});
 7497
 7498    // Paste it at a line with a higher indent level.
 7499    cx.set_state(indoc! {"
 7500        const a: B = (
 7501            c(),
 7502            d(
 7503                e,
 7504 7505            )
 7506        );
 7507    "});
 7508    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7509    cx.assert_editor_state(indoc! {"
 7510        const a: B = (
 7511            c(),
 7512            d(
 7513                e,
 7514                f    d(
 7515                    e,
 7516                    f
 7517                )
 7518        ˇ
 7519            )
 7520        );
 7521    "});
 7522
 7523    // Copy an indented block, starting mid-line
 7524    cx.set_state(indoc! {"
 7525        const a: B = (
 7526            c(),
 7527            somethin«g(
 7528                e,
 7529                f
 7530            )ˇ»
 7531        );
 7532    "});
 7533    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7534
 7535    // Paste it on a line with a lower indent level
 7536    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7537    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7538    cx.assert_editor_state(indoc! {"
 7539        const a: B = (
 7540            c(),
 7541            something(
 7542                e,
 7543                f
 7544            )
 7545        );
 7546        g(
 7547            e,
 7548            f
 7549"});
 7550}
 7551
 7552#[gpui::test]
 7553async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7554    init_test(cx, |_| {});
 7555
 7556    cx.write_to_clipboard(ClipboardItem::new_string(
 7557        "    d(\n        e\n    );\n".into(),
 7558    ));
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7562
 7563    cx.set_state(indoc! {"
 7564        fn a() {
 7565            b();
 7566            if c() {
 7567                ˇ
 7568            }
 7569        }
 7570    "});
 7571
 7572    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7573    cx.assert_editor_state(indoc! {"
 7574        fn a() {
 7575            b();
 7576            if c() {
 7577                d(
 7578                    e
 7579                );
 7580        ˇ
 7581            }
 7582        }
 7583    "});
 7584
 7585    cx.set_state(indoc! {"
 7586        fn a() {
 7587            b();
 7588            ˇ
 7589        }
 7590    "});
 7591
 7592    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7593    cx.assert_editor_state(indoc! {"
 7594        fn a() {
 7595            b();
 7596            d(
 7597                e
 7598            );
 7599        ˇ
 7600        }
 7601    "});
 7602}
 7603
 7604#[gpui::test]
 7605fn test_select_all(cx: &mut TestAppContext) {
 7606    init_test(cx, |_| {});
 7607
 7608    let editor = cx.add_window(|window, cx| {
 7609        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7610        build_editor(buffer, window, cx)
 7611    });
 7612    _ = editor.update(cx, |editor, window, cx| {
 7613        editor.select_all(&SelectAll, window, cx);
 7614        assert_eq!(
 7615            editor.selections.display_ranges(cx),
 7616            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7617        );
 7618    });
 7619}
 7620
 7621#[gpui::test]
 7622fn test_select_line(cx: &mut TestAppContext) {
 7623    init_test(cx, |_| {});
 7624
 7625    let editor = cx.add_window(|window, cx| {
 7626        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7627        build_editor(buffer, window, cx)
 7628    });
 7629    _ = editor.update(cx, |editor, window, cx| {
 7630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7631            s.select_display_ranges([
 7632                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7634                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7635                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7636            ])
 7637        });
 7638        editor.select_line(&SelectLine, window, cx);
 7639        assert_eq!(
 7640            editor.selections.display_ranges(cx),
 7641            vec![
 7642                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7643                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7644            ]
 7645        );
 7646    });
 7647
 7648    _ = editor.update(cx, |editor, window, cx| {
 7649        editor.select_line(&SelectLine, window, cx);
 7650        assert_eq!(
 7651            editor.selections.display_ranges(cx),
 7652            vec![
 7653                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7654                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7655            ]
 7656        );
 7657    });
 7658
 7659    _ = editor.update(cx, |editor, window, cx| {
 7660        editor.select_line(&SelectLine, window, cx);
 7661        assert_eq!(
 7662            editor.selections.display_ranges(cx),
 7663            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7664        );
 7665    });
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671    let mut cx = EditorTestContext::new(cx).await;
 7672
 7673    #[track_caller]
 7674    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7675        cx.set_state(initial_state);
 7676        cx.update_editor(|e, window, cx| {
 7677            e.split_selection_into_lines(&Default::default(), window, cx)
 7678        });
 7679        cx.assert_editor_state(expected_state);
 7680    }
 7681
 7682    // Selection starts and ends at the middle of lines, left-to-right
 7683    test(
 7684        &mut cx,
 7685        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7686        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7687    );
 7688    // Same thing, right-to-left
 7689    test(
 7690        &mut cx,
 7691        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7692        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7693    );
 7694
 7695    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7696    test(
 7697        &mut cx,
 7698        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7699        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7700    );
 7701    // Same thing, right-to-left
 7702    test(
 7703        &mut cx,
 7704        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7705        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7706    );
 7707
 7708    // Whole buffer, left-to-right, last line ends with newline
 7709    test(
 7710        &mut cx,
 7711        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7712        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7713    );
 7714    // Same thing, right-to-left
 7715    test(
 7716        &mut cx,
 7717        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7718        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7719    );
 7720
 7721    // Starts at the end of a line, ends at the start of another
 7722    test(
 7723        &mut cx,
 7724        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7725        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7726    );
 7727}
 7728
 7729#[gpui::test]
 7730async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7731    init_test(cx, |_| {});
 7732
 7733    let editor = cx.add_window(|window, cx| {
 7734        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7735        build_editor(buffer, window, cx)
 7736    });
 7737
 7738    // setup
 7739    _ = editor.update(cx, |editor, window, cx| {
 7740        editor.fold_creases(
 7741            vec![
 7742                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7743                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7744                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7745            ],
 7746            true,
 7747            window,
 7748            cx,
 7749        );
 7750        assert_eq!(
 7751            editor.display_text(cx),
 7752            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7753        );
 7754    });
 7755
 7756    _ = editor.update(cx, |editor, window, cx| {
 7757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7758            s.select_display_ranges([
 7759                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7760                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7761                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7762                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7763            ])
 7764        });
 7765        editor.split_selection_into_lines(&Default::default(), window, cx);
 7766        assert_eq!(
 7767            editor.display_text(cx),
 7768            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7769        );
 7770    });
 7771    EditorTestContext::for_editor(editor, cx)
 7772        .await
 7773        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7774
 7775    _ = editor.update(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7779            ])
 7780        });
 7781        editor.split_selection_into_lines(&Default::default(), window, cx);
 7782        assert_eq!(
 7783            editor.display_text(cx),
 7784            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7785        );
 7786        assert_eq!(
 7787            editor.selections.display_ranges(cx),
 7788            [
 7789                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7790                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7791                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7792                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7793                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7794                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7795                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7796            ]
 7797        );
 7798    });
 7799    EditorTestContext::for_editor(editor, cx)
 7800        .await
 7801        .assert_editor_state(
 7802            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7803        );
 7804}
 7805
 7806#[gpui::test]
 7807async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7808    init_test(cx, |_| {});
 7809
 7810    let mut cx = EditorTestContext::new(cx).await;
 7811
 7812    cx.set_state(indoc!(
 7813        r#"abc
 7814           defˇghi
 7815
 7816           jk
 7817           nlmo
 7818           "#
 7819    ));
 7820
 7821    cx.update_editor(|editor, window, cx| {
 7822        editor.add_selection_above(&Default::default(), window, cx);
 7823    });
 7824
 7825    cx.assert_editor_state(indoc!(
 7826        r#"abcˇ
 7827           defˇghi
 7828
 7829           jk
 7830           nlmo
 7831           "#
 7832    ));
 7833
 7834    cx.update_editor(|editor, window, cx| {
 7835        editor.add_selection_above(&Default::default(), window, cx);
 7836    });
 7837
 7838    cx.assert_editor_state(indoc!(
 7839        r#"abcˇ
 7840            defˇghi
 7841
 7842            jk
 7843            nlmo
 7844            "#
 7845    ));
 7846
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.add_selection_below(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_editor_state(indoc!(
 7852        r#"abc
 7853           defˇghi
 7854
 7855           jk
 7856           nlmo
 7857           "#
 7858    ));
 7859
 7860    cx.update_editor(|editor, window, cx| {
 7861        editor.undo_selection(&Default::default(), window, cx);
 7862    });
 7863
 7864    cx.assert_editor_state(indoc!(
 7865        r#"abcˇ
 7866           defˇghi
 7867
 7868           jk
 7869           nlmo
 7870           "#
 7871    ));
 7872
 7873    cx.update_editor(|editor, window, cx| {
 7874        editor.redo_selection(&Default::default(), window, cx);
 7875    });
 7876
 7877    cx.assert_editor_state(indoc!(
 7878        r#"abc
 7879           defˇghi
 7880
 7881           jk
 7882           nlmo
 7883           "#
 7884    ));
 7885
 7886    cx.update_editor(|editor, window, cx| {
 7887        editor.add_selection_below(&Default::default(), window, cx);
 7888    });
 7889
 7890    cx.assert_editor_state(indoc!(
 7891        r#"abc
 7892           defˇghi
 7893           ˇ
 7894           jk
 7895           nlmo
 7896           "#
 7897    ));
 7898
 7899    cx.update_editor(|editor, window, cx| {
 7900        editor.add_selection_below(&Default::default(), window, cx);
 7901    });
 7902
 7903    cx.assert_editor_state(indoc!(
 7904        r#"abc
 7905           defˇghi
 7906           ˇ
 7907           jkˇ
 7908           nlmo
 7909           "#
 7910    ));
 7911
 7912    cx.update_editor(|editor, window, cx| {
 7913        editor.add_selection_below(&Default::default(), window, cx);
 7914    });
 7915
 7916    cx.assert_editor_state(indoc!(
 7917        r#"abc
 7918           defˇghi
 7919           ˇ
 7920           jkˇ
 7921           nlmˇo
 7922           "#
 7923    ));
 7924
 7925    cx.update_editor(|editor, window, cx| {
 7926        editor.add_selection_below(&Default::default(), window, cx);
 7927    });
 7928
 7929    cx.assert_editor_state(indoc!(
 7930        r#"abc
 7931           defˇghi
 7932           ˇ
 7933           jkˇ
 7934           nlmˇo
 7935           ˇ"#
 7936    ));
 7937
 7938    // change selections
 7939    cx.set_state(indoc!(
 7940        r#"abc
 7941           def«ˇg»hi
 7942
 7943           jk
 7944           nlmo
 7945           "#
 7946    ));
 7947
 7948    cx.update_editor(|editor, window, cx| {
 7949        editor.add_selection_below(&Default::default(), window, cx);
 7950    });
 7951
 7952    cx.assert_editor_state(indoc!(
 7953        r#"abc
 7954           def«ˇg»hi
 7955
 7956           jk
 7957           nlm«ˇo»
 7958           "#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_below(&Default::default(), window, cx);
 7963    });
 7964
 7965    cx.assert_editor_state(indoc!(
 7966        r#"abc
 7967           def«ˇg»hi
 7968
 7969           jk
 7970           nlm«ˇo»
 7971           "#
 7972    ));
 7973
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.add_selection_above(&Default::default(), window, cx);
 7976    });
 7977
 7978    cx.assert_editor_state(indoc!(
 7979        r#"abc
 7980           def«ˇg»hi
 7981
 7982           jk
 7983           nlmo
 7984           "#
 7985    ));
 7986
 7987    cx.update_editor(|editor, window, cx| {
 7988        editor.add_selection_above(&Default::default(), window, cx);
 7989    });
 7990
 7991    cx.assert_editor_state(indoc!(
 7992        r#"abc
 7993           def«ˇg»hi
 7994
 7995           jk
 7996           nlmo
 7997           "#
 7998    ));
 7999
 8000    // Change selections again
 8001    cx.set_state(indoc!(
 8002        r#"a«bc
 8003           defgˇ»hi
 8004
 8005           jk
 8006           nlmo
 8007           "#
 8008    ));
 8009
 8010    cx.update_editor(|editor, window, cx| {
 8011        editor.add_selection_below(&Default::default(), window, cx);
 8012    });
 8013
 8014    cx.assert_editor_state(indoc!(
 8015        r#"a«bcˇ»
 8016           d«efgˇ»hi
 8017
 8018           j«kˇ»
 8019           nlmo
 8020           "#
 8021    ));
 8022
 8023    cx.update_editor(|editor, window, cx| {
 8024        editor.add_selection_below(&Default::default(), window, cx);
 8025    });
 8026    cx.assert_editor_state(indoc!(
 8027        r#"a«bcˇ»
 8028           d«efgˇ»hi
 8029
 8030           j«kˇ»
 8031           n«lmoˇ»
 8032           "#
 8033    ));
 8034    cx.update_editor(|editor, window, cx| {
 8035        editor.add_selection_above(&Default::default(), window, cx);
 8036    });
 8037
 8038    cx.assert_editor_state(indoc!(
 8039        r#"a«bcˇ»
 8040           d«efgˇ»hi
 8041
 8042           j«kˇ»
 8043           nlmo
 8044           "#
 8045    ));
 8046
 8047    // Change selections again
 8048    cx.set_state(indoc!(
 8049        r#"abc
 8050           d«ˇefghi
 8051
 8052           jk
 8053           nlm»o
 8054           "#
 8055    ));
 8056
 8057    cx.update_editor(|editor, window, cx| {
 8058        editor.add_selection_above(&Default::default(), window, cx);
 8059    });
 8060
 8061    cx.assert_editor_state(indoc!(
 8062        r#"a«ˇbc»
 8063           d«ˇef»ghi
 8064
 8065           j«ˇk»
 8066           n«ˇlm»o
 8067           "#
 8068    ));
 8069
 8070    cx.update_editor(|editor, window, cx| {
 8071        editor.add_selection_below(&Default::default(), window, cx);
 8072    });
 8073
 8074    cx.assert_editor_state(indoc!(
 8075        r#"abc
 8076           d«ˇef»ghi
 8077
 8078           j«ˇk»
 8079           n«ˇlm»o
 8080           "#
 8081    ));
 8082}
 8083
 8084#[gpui::test]
 8085async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8086    init_test(cx, |_| {});
 8087    let mut cx = EditorTestContext::new(cx).await;
 8088
 8089    cx.set_state(indoc!(
 8090        r#"line onˇe
 8091           liˇne two
 8092           line three
 8093           line four"#
 8094    ));
 8095
 8096    cx.update_editor(|editor, window, cx| {
 8097        editor.add_selection_below(&Default::default(), window, cx);
 8098    });
 8099
 8100    // test multiple cursors expand in the same direction
 8101    cx.assert_editor_state(indoc!(
 8102        r#"line onˇe
 8103           liˇne twˇo
 8104           liˇne three
 8105           line four"#
 8106    ));
 8107
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.add_selection_below(&Default::default(), window, cx);
 8110    });
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_below(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple cursors expand below overflow
 8117    cx.assert_editor_state(indoc!(
 8118        r#"line onˇe
 8119           liˇne twˇo
 8120           liˇne thˇree
 8121           liˇne foˇur"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple cursors retrieves back correctly
 8129    cx.assert_editor_state(indoc!(
 8130        r#"line onˇe
 8131           liˇne twˇo
 8132           liˇne thˇree
 8133           line four"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_above(&Default::default(), window, cx);
 8138    });
 8139
 8140    cx.update_editor(|editor, window, cx| {
 8141        editor.add_selection_above(&Default::default(), window, cx);
 8142    });
 8143
 8144    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8145    cx.assert_editor_state(indoc!(
 8146        r#"liˇne onˇe
 8147           liˇne two
 8148           line three
 8149           line four"#
 8150    ));
 8151
 8152    cx.update_editor(|editor, window, cx| {
 8153        editor.undo_selection(&Default::default(), window, cx);
 8154    });
 8155
 8156    // test undo
 8157    cx.assert_editor_state(indoc!(
 8158        r#"line onˇe
 8159           liˇne twˇo
 8160           line three
 8161           line four"#
 8162    ));
 8163
 8164    cx.update_editor(|editor, window, cx| {
 8165        editor.redo_selection(&Default::default(), window, cx);
 8166    });
 8167
 8168    // test redo
 8169    cx.assert_editor_state(indoc!(
 8170        r#"liˇne onˇe
 8171           liˇne two
 8172           line three
 8173           line four"#
 8174    ));
 8175
 8176    cx.set_state(indoc!(
 8177        r#"abcd
 8178           ef«ghˇ»
 8179           ijkl
 8180           «mˇ»nop"#
 8181    ));
 8182
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.add_selection_above(&Default::default(), window, cx);
 8185    });
 8186
 8187    // test multiple selections expand in the same direction
 8188    cx.assert_editor_state(indoc!(
 8189        r#"ab«cdˇ»
 8190           ef«ghˇ»
 8191           «iˇ»jkl
 8192           «mˇ»nop"#
 8193    ));
 8194
 8195    cx.update_editor(|editor, window, cx| {
 8196        editor.add_selection_above(&Default::default(), window, cx);
 8197    });
 8198
 8199    // test multiple selection upward overflow
 8200    cx.assert_editor_state(indoc!(
 8201        r#"ab«cdˇ»
 8202           «eˇ»f«ghˇ»
 8203           «iˇ»jkl
 8204           «mˇ»nop"#
 8205    ));
 8206
 8207    cx.update_editor(|editor, window, cx| {
 8208        editor.add_selection_below(&Default::default(), window, cx);
 8209    });
 8210
 8211    // test multiple selection retrieves back correctly
 8212    cx.assert_editor_state(indoc!(
 8213        r#"abcd
 8214           ef«ghˇ»
 8215           «iˇ»jkl
 8216           «mˇ»nop"#
 8217    ));
 8218
 8219    cx.update_editor(|editor, window, cx| {
 8220        editor.add_selection_below(&Default::default(), window, cx);
 8221    });
 8222
 8223    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8224    cx.assert_editor_state(indoc!(
 8225        r#"abcd
 8226           ef«ghˇ»
 8227           ij«klˇ»
 8228           «mˇ»nop"#
 8229    ));
 8230
 8231    cx.update_editor(|editor, window, cx| {
 8232        editor.undo_selection(&Default::default(), window, cx);
 8233    });
 8234
 8235    // test undo
 8236    cx.assert_editor_state(indoc!(
 8237        r#"abcd
 8238           ef«ghˇ»
 8239           «iˇ»jkl
 8240           «mˇ»nop"#
 8241    ));
 8242
 8243    cx.update_editor(|editor, window, cx| {
 8244        editor.redo_selection(&Default::default(), window, cx);
 8245    });
 8246
 8247    // test redo
 8248    cx.assert_editor_state(indoc!(
 8249        r#"abcd
 8250           ef«ghˇ»
 8251           ij«klˇ»
 8252           «mˇ»nop"#
 8253    ));
 8254}
 8255
 8256#[gpui::test]
 8257async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8258    init_test(cx, |_| {});
 8259    let mut cx = EditorTestContext::new(cx).await;
 8260
 8261    cx.set_state(indoc!(
 8262        r#"line onˇe
 8263           liˇne two
 8264           line three
 8265           line four"#
 8266    ));
 8267
 8268    cx.update_editor(|editor, window, cx| {
 8269        editor.add_selection_below(&Default::default(), window, cx);
 8270        editor.add_selection_below(&Default::default(), window, cx);
 8271        editor.add_selection_below(&Default::default(), window, cx);
 8272    });
 8273
 8274    // initial state with two multi cursor groups
 8275    cx.assert_editor_state(indoc!(
 8276        r#"line onˇe
 8277           liˇne twˇo
 8278           liˇne thˇree
 8279           liˇne foˇur"#
 8280    ));
 8281
 8282    // add single cursor in middle - simulate opt click
 8283    cx.update_editor(|editor, window, cx| {
 8284        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8285        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8286        editor.end_selection(window, cx);
 8287    });
 8288
 8289    cx.assert_editor_state(indoc!(
 8290        r#"line onˇe
 8291           liˇne twˇo
 8292           liˇneˇ thˇree
 8293           liˇne foˇur"#
 8294    ));
 8295
 8296    cx.update_editor(|editor, window, cx| {
 8297        editor.add_selection_above(&Default::default(), window, cx);
 8298    });
 8299
 8300    // test new added selection expands above and existing selection shrinks
 8301    cx.assert_editor_state(indoc!(
 8302        r#"line onˇe
 8303           liˇneˇ twˇo
 8304           liˇneˇ thˇree
 8305           line four"#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_above(&Default::default(), window, cx);
 8310    });
 8311
 8312    // test new added selection expands above and existing selection shrinks
 8313    cx.assert_editor_state(indoc!(
 8314        r#"lineˇ onˇe
 8315           liˇneˇ twˇo
 8316           lineˇ three
 8317           line four"#
 8318    ));
 8319
 8320    // intial state with two selection groups
 8321    cx.set_state(indoc!(
 8322        r#"abcd
 8323           ef«ghˇ»
 8324           ijkl
 8325           «mˇ»nop"#
 8326    ));
 8327
 8328    cx.update_editor(|editor, window, cx| {
 8329        editor.add_selection_above(&Default::default(), window, cx);
 8330        editor.add_selection_above(&Default::default(), window, cx);
 8331    });
 8332
 8333    cx.assert_editor_state(indoc!(
 8334        r#"ab«cdˇ»
 8335           «eˇ»f«ghˇ»
 8336           «iˇ»jkl
 8337           «mˇ»nop"#
 8338    ));
 8339
 8340    // add single selection in middle - simulate opt drag
 8341    cx.update_editor(|editor, window, cx| {
 8342        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8343        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8344        editor.update_selection(
 8345            DisplayPoint::new(DisplayRow(2), 4),
 8346            0,
 8347            gpui::Point::<f32>::default(),
 8348            window,
 8349            cx,
 8350        );
 8351        editor.end_selection(window, cx);
 8352    });
 8353
 8354    cx.assert_editor_state(indoc!(
 8355        r#"ab«cdˇ»
 8356           «eˇ»f«ghˇ»
 8357           «iˇ»jk«lˇ»
 8358           «mˇ»nop"#
 8359    ));
 8360
 8361    cx.update_editor(|editor, window, cx| {
 8362        editor.add_selection_below(&Default::default(), window, cx);
 8363    });
 8364
 8365    // test new added selection expands below, others shrinks from above
 8366    cx.assert_editor_state(indoc!(
 8367        r#"abcd
 8368           ef«ghˇ»
 8369           «iˇ»jk«lˇ»
 8370           «mˇ»no«pˇ»"#
 8371    ));
 8372}
 8373
 8374#[gpui::test]
 8375async fn test_select_next(cx: &mut TestAppContext) {
 8376    init_test(cx, |_| {});
 8377
 8378    let mut cx = EditorTestContext::new(cx).await;
 8379    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8384
 8385    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8386        .unwrap();
 8387    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8388
 8389    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8390    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8391
 8392    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8393    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8394
 8395    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8396        .unwrap();
 8397    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8398
 8399    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8400        .unwrap();
 8401    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8402
 8403    // Test selection direction should be preserved
 8404    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8405
 8406    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8407        .unwrap();
 8408    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8409}
 8410
 8411#[gpui::test]
 8412async fn test_select_all_matches(cx: &mut TestAppContext) {
 8413    init_test(cx, |_| {});
 8414
 8415    let mut cx = EditorTestContext::new(cx).await;
 8416
 8417    // Test caret-only selections
 8418    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8419    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8422
 8423    // Test left-to-right selections
 8424    cx.set_state("abc\n«abcˇ»\nabc");
 8425    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8426        .unwrap();
 8427    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8428
 8429    // Test right-to-left selections
 8430    cx.set_state("abc\n«ˇabc»\nabc");
 8431    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8434
 8435    // Test selecting whitespace with caret selection
 8436    cx.set_state("abc\nˇ   abc\nabc");
 8437    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8438        .unwrap();
 8439    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8440
 8441    // Test selecting whitespace with left-to-right selection
 8442    cx.set_state("abc\n«ˇ  »abc\nabc");
 8443    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8444        .unwrap();
 8445    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8446
 8447    // Test no matches with right-to-left selection
 8448    cx.set_state("abc\n«  ˇ»abc\nabc");
 8449    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8452
 8453    // Test with a single word and clip_at_line_ends=true (#29823)
 8454    cx.set_state("aˇbc");
 8455    cx.update_editor(|e, window, cx| {
 8456        e.set_clip_at_line_ends(true, cx);
 8457        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8458        e.set_clip_at_line_ends(false, cx);
 8459    });
 8460    cx.assert_editor_state("«abcˇ»");
 8461}
 8462
 8463#[gpui::test]
 8464async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8465    init_test(cx, |_| {});
 8466
 8467    let mut cx = EditorTestContext::new(cx).await;
 8468
 8469    let large_body_1 = "\nd".repeat(200);
 8470    let large_body_2 = "\ne".repeat(200);
 8471
 8472    cx.set_state(&format!(
 8473        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8474    ));
 8475    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8476        let scroll_position = editor.scroll_position(cx);
 8477        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8478        scroll_position
 8479    });
 8480
 8481    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8482        .unwrap();
 8483    cx.assert_editor_state(&format!(
 8484        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8485    ));
 8486    let scroll_position_after_selection =
 8487        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8488    assert_eq!(
 8489        initial_scroll_position, scroll_position_after_selection,
 8490        "Scroll position should not change after selecting all matches"
 8491    );
 8492}
 8493
 8494#[gpui::test]
 8495async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8496    init_test(cx, |_| {});
 8497
 8498    let mut cx = EditorLspTestContext::new_rust(
 8499        lsp::ServerCapabilities {
 8500            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8501            ..Default::default()
 8502        },
 8503        cx,
 8504    )
 8505    .await;
 8506
 8507    cx.set_state(indoc! {"
 8508        line 1
 8509        line 2
 8510        linˇe 3
 8511        line 4
 8512        line 5
 8513    "});
 8514
 8515    // Make an edit
 8516    cx.update_editor(|editor, window, cx| {
 8517        editor.handle_input("X", window, cx);
 8518    });
 8519
 8520    // Move cursor to a different position
 8521    cx.update_editor(|editor, window, cx| {
 8522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8523            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8524        });
 8525    });
 8526
 8527    cx.assert_editor_state(indoc! {"
 8528        line 1
 8529        line 2
 8530        linXe 3
 8531        line 4
 8532        liˇne 5
 8533    "});
 8534
 8535    cx.lsp
 8536        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8537            Ok(Some(vec![lsp::TextEdit::new(
 8538                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8539                "PREFIX ".to_string(),
 8540            )]))
 8541        });
 8542
 8543    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8544        .unwrap()
 8545        .await
 8546        .unwrap();
 8547
 8548    cx.assert_editor_state(indoc! {"
 8549        PREFIX line 1
 8550        line 2
 8551        linXe 3
 8552        line 4
 8553        liˇne 5
 8554    "});
 8555
 8556    // Undo formatting
 8557    cx.update_editor(|editor, window, cx| {
 8558        editor.undo(&Default::default(), window, cx);
 8559    });
 8560
 8561    // Verify cursor moved back to position after edit
 8562    cx.assert_editor_state(indoc! {"
 8563        line 1
 8564        line 2
 8565        linXˇe 3
 8566        line 4
 8567        line 5
 8568    "});
 8569}
 8570
 8571#[gpui::test]
 8572async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8573    init_test(cx, |_| {});
 8574
 8575    let mut cx = EditorTestContext::new(cx).await;
 8576
 8577    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8580    });
 8581
 8582    cx.set_state(indoc! {"
 8583        line 1
 8584        line 2
 8585        linˇe 3
 8586        line 4
 8587        line 5
 8588        line 6
 8589        line 7
 8590        line 8
 8591        line 9
 8592        line 10
 8593    "});
 8594
 8595    let snapshot = cx.buffer_snapshot();
 8596    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8597
 8598    cx.update(|_, cx| {
 8599        provider.update(cx, |provider, _| {
 8600            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8601                id: None,
 8602                edits: vec![(edit_position..edit_position, "X".into())],
 8603                edit_preview: None,
 8604            }))
 8605        })
 8606    });
 8607
 8608    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8611    });
 8612
 8613    cx.assert_editor_state(indoc! {"
 8614        line 1
 8615        line 2
 8616        lineXˇ 3
 8617        line 4
 8618        line 5
 8619        line 6
 8620        line 7
 8621        line 8
 8622        line 9
 8623        line 10
 8624    "});
 8625
 8626    cx.update_editor(|editor, window, cx| {
 8627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8628            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8629        });
 8630    });
 8631
 8632    cx.assert_editor_state(indoc! {"
 8633        line 1
 8634        line 2
 8635        lineX 3
 8636        line 4
 8637        line 5
 8638        line 6
 8639        line 7
 8640        line 8
 8641        line 9
 8642        liˇne 10
 8643    "});
 8644
 8645    cx.update_editor(|editor, window, cx| {
 8646        editor.undo(&Default::default(), window, cx);
 8647    });
 8648
 8649    cx.assert_editor_state(indoc! {"
 8650        line 1
 8651        line 2
 8652        lineˇ 3
 8653        line 4
 8654        line 5
 8655        line 6
 8656        line 7
 8657        line 8
 8658        line 9
 8659        line 10
 8660    "});
 8661}
 8662
 8663#[gpui::test]
 8664async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8665    init_test(cx, |_| {});
 8666
 8667    let mut cx = EditorTestContext::new(cx).await;
 8668    cx.set_state(
 8669        r#"let foo = 2;
 8670lˇet foo = 2;
 8671let fooˇ = 2;
 8672let foo = 2;
 8673let foo = ˇ2;"#,
 8674    );
 8675
 8676    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8677        .unwrap();
 8678    cx.assert_editor_state(
 8679        r#"let foo = 2;
 8680«letˇ» foo = 2;
 8681let «fooˇ» = 2;
 8682let foo = 2;
 8683let foo = «2ˇ»;"#,
 8684    );
 8685
 8686    // noop for multiple selections with different contents
 8687    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8688        .unwrap();
 8689    cx.assert_editor_state(
 8690        r#"let foo = 2;
 8691«letˇ» foo = 2;
 8692let «fooˇ» = 2;
 8693let foo = 2;
 8694let foo = «2ˇ»;"#,
 8695    );
 8696
 8697    // Test last selection direction should be preserved
 8698    cx.set_state(
 8699        r#"let foo = 2;
 8700let foo = 2;
 8701let «fooˇ» = 2;
 8702let «ˇfoo» = 2;
 8703let foo = 2;"#,
 8704    );
 8705
 8706    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8707        .unwrap();
 8708    cx.assert_editor_state(
 8709        r#"let foo = 2;
 8710let foo = 2;
 8711let «fooˇ» = 2;
 8712let «ˇfoo» = 2;
 8713let «ˇfoo» = 2;"#,
 8714    );
 8715}
 8716
 8717#[gpui::test]
 8718async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8719    init_test(cx, |_| {});
 8720
 8721    let mut cx =
 8722        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8723
 8724    cx.assert_editor_state(indoc! {"
 8725        ˇbbb
 8726        ccc
 8727
 8728        bbb
 8729        ccc
 8730        "});
 8731    cx.dispatch_action(SelectPrevious::default());
 8732    cx.assert_editor_state(indoc! {"
 8733                «bbbˇ»
 8734                ccc
 8735
 8736                bbb
 8737                ccc
 8738                "});
 8739    cx.dispatch_action(SelectPrevious::default());
 8740    cx.assert_editor_state(indoc! {"
 8741                «bbbˇ»
 8742                ccc
 8743
 8744                «bbbˇ»
 8745                ccc
 8746                "});
 8747}
 8748
 8749#[gpui::test]
 8750async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8751    init_test(cx, |_| {});
 8752
 8753    let mut cx = EditorTestContext::new(cx).await;
 8754    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8755
 8756    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8757        .unwrap();
 8758    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8759
 8760    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8761        .unwrap();
 8762    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8763
 8764    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8765    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8766
 8767    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 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(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8775        .unwrap();
 8776    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8777}
 8778
 8779#[gpui::test]
 8780async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8781    init_test(cx, |_| {});
 8782
 8783    let mut cx = EditorTestContext::new(cx).await;
 8784    cx.set_state("");
 8785
 8786    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8787        .unwrap();
 8788    cx.assert_editor_state("«aˇ»");
 8789    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8790        .unwrap();
 8791    cx.assert_editor_state("«aˇ»");
 8792}
 8793
 8794#[gpui::test]
 8795async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8796    init_test(cx, |_| {});
 8797
 8798    let mut cx = EditorTestContext::new(cx).await;
 8799    cx.set_state(
 8800        r#"let foo = 2;
 8801lˇet foo = 2;
 8802let fooˇ = 2;
 8803let foo = 2;
 8804let foo = ˇ2;"#,
 8805    );
 8806
 8807    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8808        .unwrap();
 8809    cx.assert_editor_state(
 8810        r#"let foo = 2;
 8811«letˇ» foo = 2;
 8812let «fooˇ» = 2;
 8813let foo = 2;
 8814let foo = «2ˇ»;"#,
 8815    );
 8816
 8817    // noop for multiple selections with different contents
 8818    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8819        .unwrap();
 8820    cx.assert_editor_state(
 8821        r#"let foo = 2;
 8822«letˇ» foo = 2;
 8823let «fooˇ» = 2;
 8824let foo = 2;
 8825let foo = «2ˇ»;"#,
 8826    );
 8827}
 8828
 8829#[gpui::test]
 8830async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8831    init_test(cx, |_| {});
 8832
 8833    let mut cx = EditorTestContext::new(cx).await;
 8834    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8835
 8836    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8837        .unwrap();
 8838    // selection direction is preserved
 8839    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8840
 8841    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8842        .unwrap();
 8843    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8844
 8845    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8846    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8847
 8848    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8849    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8850
 8851    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8854
 8855    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8858}
 8859
 8860#[gpui::test]
 8861async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8862    init_test(cx, |_| {});
 8863
 8864    let language = Arc::new(Language::new(
 8865        LanguageConfig::default(),
 8866        Some(tree_sitter_rust::LANGUAGE.into()),
 8867    ));
 8868
 8869    let text = r#"
 8870        use mod1::mod2::{mod3, mod4};
 8871
 8872        fn fn_1(param1: bool, param2: &str) {
 8873            let var1 = "text";
 8874        }
 8875    "#
 8876    .unindent();
 8877
 8878    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8879    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8880    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8881
 8882    editor
 8883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8884        .await;
 8885
 8886    editor.update_in(cx, |editor, window, cx| {
 8887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8888            s.select_display_ranges([
 8889                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8890                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8891                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8892            ]);
 8893        });
 8894        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8895    });
 8896    editor.update(cx, |editor, cx| {
 8897        assert_text_with_selections(
 8898            editor,
 8899            indoc! {r#"
 8900                use mod1::mod2::{mod3, «mod4ˇ»};
 8901
 8902                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8903                    let var1 = "«ˇtext»";
 8904                }
 8905            "#},
 8906            cx,
 8907        );
 8908    });
 8909
 8910    editor.update_in(cx, |editor, window, cx| {
 8911        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8912    });
 8913    editor.update(cx, |editor, cx| {
 8914        assert_text_with_selections(
 8915            editor,
 8916            indoc! {r#"
 8917                use mod1::mod2::«{mod3, mod4}ˇ»;
 8918
 8919                «ˇfn fn_1(param1: bool, param2: &str) {
 8920                    let var1 = "text";
 8921 8922            "#},
 8923            cx,
 8924        );
 8925    });
 8926
 8927    editor.update_in(cx, |editor, window, cx| {
 8928        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8929    });
 8930    assert_eq!(
 8931        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8932        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8933    );
 8934
 8935    // Trying to expand the selected syntax node one more time has no effect.
 8936    editor.update_in(cx, |editor, window, cx| {
 8937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8938    });
 8939    assert_eq!(
 8940        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8941        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8942    );
 8943
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8946    });
 8947    editor.update(cx, |editor, cx| {
 8948        assert_text_with_selections(
 8949            editor,
 8950            indoc! {r#"
 8951                use mod1::mod2::«{mod3, mod4}ˇ»;
 8952
 8953                «ˇfn fn_1(param1: bool, param2: &str) {
 8954                    let var1 = "text";
 8955 8956            "#},
 8957            cx,
 8958        );
 8959    });
 8960
 8961    editor.update_in(cx, |editor, window, cx| {
 8962        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8963    });
 8964    editor.update(cx, |editor, cx| {
 8965        assert_text_with_selections(
 8966            editor,
 8967            indoc! {r#"
 8968                use mod1::mod2::{mod3, «mod4ˇ»};
 8969
 8970                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8971                    let var1 = "«ˇtext»";
 8972                }
 8973            "#},
 8974            cx,
 8975        );
 8976    });
 8977
 8978    editor.update_in(cx, |editor, window, cx| {
 8979        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8980    });
 8981    editor.update(cx, |editor, cx| {
 8982        assert_text_with_selections(
 8983            editor,
 8984            indoc! {r#"
 8985                use mod1::mod2::{mod3, moˇd4};
 8986
 8987                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8988                    let var1 = "teˇxt";
 8989                }
 8990            "#},
 8991            cx,
 8992        );
 8993    });
 8994
 8995    // Trying to shrink the selected syntax node one more time has no effect.
 8996    editor.update_in(cx, |editor, window, cx| {
 8997        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8998    });
 8999    editor.update_in(cx, |editor, _, cx| {
 9000        assert_text_with_selections(
 9001            editor,
 9002            indoc! {r#"
 9003                use mod1::mod2::{mod3, moˇd4};
 9004
 9005                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9006                    let var1 = "teˇxt";
 9007                }
 9008            "#},
 9009            cx,
 9010        );
 9011    });
 9012
 9013    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9014    // a fold.
 9015    editor.update_in(cx, |editor, window, cx| {
 9016        editor.fold_creases(
 9017            vec![
 9018                Crease::simple(
 9019                    Point::new(0, 21)..Point::new(0, 24),
 9020                    FoldPlaceholder::test(),
 9021                ),
 9022                Crease::simple(
 9023                    Point::new(3, 20)..Point::new(3, 22),
 9024                    FoldPlaceholder::test(),
 9025                ),
 9026            ],
 9027            true,
 9028            window,
 9029            cx,
 9030        );
 9031        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9032    });
 9033    editor.update(cx, |editor, cx| {
 9034        assert_text_with_selections(
 9035            editor,
 9036            indoc! {r#"
 9037                use mod1::mod2::«{mod3, mod4}ˇ»;
 9038
 9039                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9040                    let var1 = "«ˇtext»";
 9041                }
 9042            "#},
 9043            cx,
 9044        );
 9045    });
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let language = Arc::new(Language::new(
 9053        LanguageConfig::default(),
 9054        Some(tree_sitter_rust::LANGUAGE.into()),
 9055    ));
 9056
 9057    let text = "let a = 2;";
 9058
 9059    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9060    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9061    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9062
 9063    editor
 9064        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9065        .await;
 9066
 9067    // Test case 1: Cursor at end of word
 9068    editor.update_in(cx, |editor, window, cx| {
 9069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9070            s.select_display_ranges([
 9071                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9072            ]);
 9073        });
 9074    });
 9075    editor.update(cx, |editor, cx| {
 9076        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9077    });
 9078    editor.update_in(cx, |editor, window, cx| {
 9079        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9080    });
 9081    editor.update(cx, |editor, cx| {
 9082        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9083    });
 9084    editor.update_in(cx, |editor, window, cx| {
 9085        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9086    });
 9087    editor.update(cx, |editor, cx| {
 9088        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9089    });
 9090
 9091    // Test case 2: Cursor at end of statement
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update(cx, |editor, cx| {
 9100        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9101    });
 9102    editor.update_in(cx, |editor, window, cx| {
 9103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9104    });
 9105    editor.update(cx, |editor, cx| {
 9106        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9107    });
 9108}
 9109
 9110#[gpui::test]
 9111async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9112    init_test(cx, |_| {});
 9113
 9114    let language = Arc::new(Language::new(
 9115        LanguageConfig {
 9116            name: "JavaScript".into(),
 9117            ..Default::default()
 9118        },
 9119        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9120    ));
 9121
 9122    let text = r#"
 9123        let a = {
 9124            key: "value",
 9125        };
 9126    "#
 9127    .unindent();
 9128
 9129    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9130    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9131    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9132
 9133    editor
 9134        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9135        .await;
 9136
 9137    // Test case 1: Cursor after '{'
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9140            s.select_display_ranges([
 9141                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9142            ]);
 9143        });
 9144    });
 9145    editor.update(cx, |editor, cx| {
 9146        assert_text_with_selections(
 9147            editor,
 9148            indoc! {r#"
 9149                let a = {ˇ
 9150                    key: "value",
 9151                };
 9152            "#},
 9153            cx,
 9154        );
 9155    });
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9158    });
 9159    editor.update(cx, |editor, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                let a = «ˇ{
 9164                    key: "value",
 9165                }»;
 9166            "#},
 9167            cx,
 9168        );
 9169    });
 9170
 9171    // Test case 2: Cursor after ':'
 9172    editor.update_in(cx, |editor, window, cx| {
 9173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9174            s.select_display_ranges([
 9175                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9176            ]);
 9177        });
 9178    });
 9179    editor.update(cx, |editor, cx| {
 9180        assert_text_with_selections(
 9181            editor,
 9182            indoc! {r#"
 9183                let a = {
 9184                    key:ˇ "value",
 9185                };
 9186            "#},
 9187            cx,
 9188        );
 9189    });
 9190    editor.update_in(cx, |editor, window, cx| {
 9191        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9192    });
 9193    editor.update(cx, |editor, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                let a = {
 9198                    «ˇkey: "value"»,
 9199                };
 9200            "#},
 9201            cx,
 9202        );
 9203    });
 9204    editor.update_in(cx, |editor, window, cx| {
 9205        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9206    });
 9207    editor.update(cx, |editor, cx| {
 9208        assert_text_with_selections(
 9209            editor,
 9210            indoc! {r#"
 9211                let a = «ˇ{
 9212                    key: "value",
 9213                }»;
 9214            "#},
 9215            cx,
 9216        );
 9217    });
 9218
 9219    // Test case 3: Cursor after ','
 9220    editor.update_in(cx, |editor, window, cx| {
 9221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9222            s.select_display_ranges([
 9223                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9224            ]);
 9225        });
 9226    });
 9227    editor.update(cx, |editor, cx| {
 9228        assert_text_with_selections(
 9229            editor,
 9230            indoc! {r#"
 9231                let a = {
 9232                    key: "value",ˇ
 9233                };
 9234            "#},
 9235            cx,
 9236        );
 9237    });
 9238    editor.update_in(cx, |editor, window, cx| {
 9239        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9240    });
 9241    editor.update(cx, |editor, cx| {
 9242        assert_text_with_selections(
 9243            editor,
 9244            indoc! {r#"
 9245                let a = «ˇ{
 9246                    key: "value",
 9247                }»;
 9248            "#},
 9249            cx,
 9250        );
 9251    });
 9252
 9253    // Test case 4: Cursor after ';'
 9254    editor.update_in(cx, |editor, window, cx| {
 9255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9256            s.select_display_ranges([
 9257                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9258            ]);
 9259        });
 9260    });
 9261    editor.update(cx, |editor, cx| {
 9262        assert_text_with_selections(
 9263            editor,
 9264            indoc! {r#"
 9265                let a = {
 9266                    key: "value",
 9267                };ˇ
 9268            "#},
 9269            cx,
 9270        );
 9271    });
 9272    editor.update_in(cx, |editor, window, cx| {
 9273        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9274    });
 9275    editor.update(cx, |editor, cx| {
 9276        assert_text_with_selections(
 9277            editor,
 9278            indoc! {r#"
 9279                «ˇlet a = {
 9280                    key: "value",
 9281                };
 9282                »"#},
 9283            cx,
 9284        );
 9285    });
 9286}
 9287
 9288#[gpui::test]
 9289async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9290    init_test(cx, |_| {});
 9291
 9292    let language = Arc::new(Language::new(
 9293        LanguageConfig::default(),
 9294        Some(tree_sitter_rust::LANGUAGE.into()),
 9295    ));
 9296
 9297    let text = r#"
 9298        use mod1::mod2::{mod3, mod4};
 9299
 9300        fn fn_1(param1: bool, param2: &str) {
 9301            let var1 = "hello world";
 9302        }
 9303    "#
 9304    .unindent();
 9305
 9306    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9307    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9308    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9309
 9310    editor
 9311        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9312        .await;
 9313
 9314    // Test 1: Cursor on a letter of a string word
 9315    editor.update_in(cx, |editor, window, cx| {
 9316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9317            s.select_display_ranges([
 9318                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9319            ]);
 9320        });
 9321    });
 9322    editor.update_in(cx, |editor, window, cx| {
 9323        assert_text_with_selections(
 9324            editor,
 9325            indoc! {r#"
 9326                use mod1::mod2::{mod3, mod4};
 9327
 9328                fn fn_1(param1: bool, param2: &str) {
 9329                    let var1 = "hˇello world";
 9330                }
 9331            "#},
 9332            cx,
 9333        );
 9334        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9335        assert_text_with_selections(
 9336            editor,
 9337            indoc! {r#"
 9338                use mod1::mod2::{mod3, mod4};
 9339
 9340                fn fn_1(param1: bool, param2: &str) {
 9341                    let var1 = "«ˇhello» world";
 9342                }
 9343            "#},
 9344            cx,
 9345        );
 9346    });
 9347
 9348    // Test 2: Partial selection within a word
 9349    editor.update_in(cx, |editor, window, cx| {
 9350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9351            s.select_display_ranges([
 9352                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9353            ]);
 9354        });
 9355    });
 9356    editor.update_in(cx, |editor, window, cx| {
 9357        assert_text_with_selections(
 9358            editor,
 9359            indoc! {r#"
 9360                use mod1::mod2::{mod3, mod4};
 9361
 9362                fn fn_1(param1: bool, param2: &str) {
 9363                    let var1 = "h«elˇ»lo world";
 9364                }
 9365            "#},
 9366            cx,
 9367        );
 9368        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9369        assert_text_with_selections(
 9370            editor,
 9371            indoc! {r#"
 9372                use mod1::mod2::{mod3, mod4};
 9373
 9374                fn fn_1(param1: bool, param2: &str) {
 9375                    let var1 = "«ˇhello» world";
 9376                }
 9377            "#},
 9378            cx,
 9379        );
 9380    });
 9381
 9382    // Test 3: Complete word already selected
 9383    editor.update_in(cx, |editor, window, cx| {
 9384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9385            s.select_display_ranges([
 9386                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9387            ]);
 9388        });
 9389    });
 9390    editor.update_in(cx, |editor, window, cx| {
 9391        assert_text_with_selections(
 9392            editor,
 9393            indoc! {r#"
 9394                use mod1::mod2::{mod3, mod4};
 9395
 9396                fn fn_1(param1: bool, param2: &str) {
 9397                    let var1 = "«helloˇ» world";
 9398                }
 9399            "#},
 9400            cx,
 9401        );
 9402        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9403        assert_text_with_selections(
 9404            editor,
 9405            indoc! {r#"
 9406                use mod1::mod2::{mod3, mod4};
 9407
 9408                fn fn_1(param1: bool, param2: &str) {
 9409                    let var1 = "«hello worldˇ»";
 9410                }
 9411            "#},
 9412            cx,
 9413        );
 9414    });
 9415
 9416    // Test 4: Selection spanning across words
 9417    editor.update_in(cx, |editor, window, cx| {
 9418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9419            s.select_display_ranges([
 9420                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9421            ]);
 9422        });
 9423    });
 9424    editor.update_in(cx, |editor, window, cx| {
 9425        assert_text_with_selections(
 9426            editor,
 9427            indoc! {r#"
 9428                use mod1::mod2::{mod3, mod4};
 9429
 9430                fn fn_1(param1: bool, param2: &str) {
 9431                    let var1 = "hel«lo woˇ»rld";
 9432                }
 9433            "#},
 9434            cx,
 9435        );
 9436        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9437        assert_text_with_selections(
 9438            editor,
 9439            indoc! {r#"
 9440                use mod1::mod2::{mod3, mod4};
 9441
 9442                fn fn_1(param1: bool, param2: &str) {
 9443                    let var1 = "«ˇhello world»";
 9444                }
 9445            "#},
 9446            cx,
 9447        );
 9448    });
 9449
 9450    // Test 5: Expansion beyond string
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9453        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, mod4};
 9458
 9459                fn fn_1(param1: bool, param2: &str) {
 9460                    «ˇlet var1 = "hello world";»
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465    });
 9466}
 9467
 9468#[gpui::test]
 9469async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9470    init_test(cx, |_| {});
 9471
 9472    let mut cx = EditorTestContext::new(cx).await;
 9473
 9474    let language = Arc::new(Language::new(
 9475        LanguageConfig::default(),
 9476        Some(tree_sitter_rust::LANGUAGE.into()),
 9477    ));
 9478
 9479    cx.update_buffer(|buffer, cx| {
 9480        buffer.set_language(Some(language), cx);
 9481    });
 9482
 9483    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9484    cx.update_editor(|editor, window, cx| {
 9485        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9486    });
 9487
 9488    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9489
 9490    cx.set_state(indoc! { r#"fn a() {
 9491          // what
 9492          // a
 9493          // ˇlong
 9494          // method
 9495          // I
 9496          // sure
 9497          // hope
 9498          // it
 9499          // works
 9500    }"# });
 9501
 9502    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9503    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9504    cx.update(|_, cx| {
 9505        multi_buffer.update(cx, |multi_buffer, cx| {
 9506            multi_buffer.set_excerpts_for_path(
 9507                PathKey::for_buffer(&buffer, cx),
 9508                buffer,
 9509                [Point::new(1, 0)..Point::new(1, 0)],
 9510                3,
 9511                cx,
 9512            );
 9513        });
 9514    });
 9515
 9516    let editor2 = cx.new_window_entity(|window, cx| {
 9517        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9518    });
 9519
 9520    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9521    cx.update_editor(|editor, window, cx| {
 9522        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9523            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9524        })
 9525    });
 9526
 9527    cx.assert_editor_state(indoc! { "
 9528        fn a() {
 9529              // what
 9530              // a
 9531        ˇ      // long
 9532              // method"});
 9533
 9534    cx.update_editor(|editor, window, cx| {
 9535        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9536    });
 9537
 9538    // Although we could potentially make the action work when the syntax node
 9539    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9540    // did. Maybe we could also expand the excerpt to contain the range?
 9541    cx.assert_editor_state(indoc! { "
 9542        fn a() {
 9543              // what
 9544              // a
 9545        ˇ      // long
 9546              // method"});
 9547}
 9548
 9549#[gpui::test]
 9550async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9551    init_test(cx, |_| {});
 9552
 9553    let base_text = r#"
 9554        impl A {
 9555            // this is an uncommitted comment
 9556
 9557            fn b() {
 9558                c();
 9559            }
 9560
 9561            // this is another uncommitted comment
 9562
 9563            fn d() {
 9564                // e
 9565                // f
 9566            }
 9567        }
 9568
 9569        fn g() {
 9570            // h
 9571        }
 9572    "#
 9573    .unindent();
 9574
 9575    let text = r#"
 9576        ˇimpl A {
 9577
 9578            fn b() {
 9579                c();
 9580            }
 9581
 9582            fn d() {
 9583                // e
 9584                // f
 9585            }
 9586        }
 9587
 9588        fn g() {
 9589            // h
 9590        }
 9591    "#
 9592    .unindent();
 9593
 9594    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9595    cx.set_state(&text);
 9596    cx.set_head_text(&base_text);
 9597    cx.update_editor(|editor, window, cx| {
 9598        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9599    });
 9600
 9601    cx.assert_state_with_diff(
 9602        "
 9603        ˇimpl A {
 9604      -     // this is an uncommitted comment
 9605
 9606            fn b() {
 9607                c();
 9608            }
 9609
 9610      -     // this is another uncommitted comment
 9611      -
 9612            fn d() {
 9613                // e
 9614                // f
 9615            }
 9616        }
 9617
 9618        fn g() {
 9619            // h
 9620        }
 9621    "
 9622        .unindent(),
 9623    );
 9624
 9625    let expected_display_text = "
 9626        impl A {
 9627            // this is an uncommitted comment
 9628
 9629            fn b() {
 9630 9631            }
 9632
 9633            // this is another uncommitted comment
 9634
 9635            fn d() {
 9636 9637            }
 9638        }
 9639
 9640        fn g() {
 9641 9642        }
 9643        "
 9644    .unindent();
 9645
 9646    cx.update_editor(|editor, window, cx| {
 9647        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9648        assert_eq!(editor.display_text(cx), expected_display_text);
 9649    });
 9650}
 9651
 9652#[gpui::test]
 9653async fn test_autoindent(cx: &mut TestAppContext) {
 9654    init_test(cx, |_| {});
 9655
 9656    let language = Arc::new(
 9657        Language::new(
 9658            LanguageConfig {
 9659                brackets: BracketPairConfig {
 9660                    pairs: vec![
 9661                        BracketPair {
 9662                            start: "{".to_string(),
 9663                            end: "}".to_string(),
 9664                            close: false,
 9665                            surround: false,
 9666                            newline: true,
 9667                        },
 9668                        BracketPair {
 9669                            start: "(".to_string(),
 9670                            end: ")".to_string(),
 9671                            close: false,
 9672                            surround: false,
 9673                            newline: true,
 9674                        },
 9675                    ],
 9676                    ..Default::default()
 9677                },
 9678                ..Default::default()
 9679            },
 9680            Some(tree_sitter_rust::LANGUAGE.into()),
 9681        )
 9682        .with_indents_query(
 9683            r#"
 9684                (_ "(" ")" @end) @indent
 9685                (_ "{" "}" @end) @indent
 9686            "#,
 9687        )
 9688        .unwrap(),
 9689    );
 9690
 9691    let text = "fn a() {}";
 9692
 9693    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9694    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9695    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9696    editor
 9697        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9698        .await;
 9699
 9700    editor.update_in(cx, |editor, window, cx| {
 9701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9702            s.select_ranges([5..5, 8..8, 9..9])
 9703        });
 9704        editor.newline(&Newline, window, cx);
 9705        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9706        assert_eq!(
 9707            editor.selections.ranges(&editor.display_snapshot(cx)),
 9708            &[
 9709                Point::new(1, 4)..Point::new(1, 4),
 9710                Point::new(3, 4)..Point::new(3, 4),
 9711                Point::new(5, 0)..Point::new(5, 0)
 9712            ]
 9713        );
 9714    });
 9715}
 9716
 9717#[gpui::test]
 9718async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9719    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9720
 9721    let language = Arc::new(
 9722        Language::new(
 9723            LanguageConfig {
 9724                brackets: BracketPairConfig {
 9725                    pairs: vec![
 9726                        BracketPair {
 9727                            start: "{".to_string(),
 9728                            end: "}".to_string(),
 9729                            close: false,
 9730                            surround: false,
 9731                            newline: true,
 9732                        },
 9733                        BracketPair {
 9734                            start: "(".to_string(),
 9735                            end: ")".to_string(),
 9736                            close: false,
 9737                            surround: false,
 9738                            newline: true,
 9739                        },
 9740                    ],
 9741                    ..Default::default()
 9742                },
 9743                ..Default::default()
 9744            },
 9745            Some(tree_sitter_rust::LANGUAGE.into()),
 9746        )
 9747        .with_indents_query(
 9748            r#"
 9749                (_ "(" ")" @end) @indent
 9750                (_ "{" "}" @end) @indent
 9751            "#,
 9752        )
 9753        .unwrap(),
 9754    );
 9755
 9756    let text = "fn a() {}";
 9757
 9758    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9759    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9760    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9761    editor
 9762        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9763        .await;
 9764
 9765    editor.update_in(cx, |editor, window, cx| {
 9766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9767            s.select_ranges([5..5, 8..8, 9..9])
 9768        });
 9769        editor.newline(&Newline, window, cx);
 9770        assert_eq!(
 9771            editor.text(cx),
 9772            indoc!(
 9773                "
 9774                fn a(
 9775
 9776                ) {
 9777
 9778                }
 9779                "
 9780            )
 9781        );
 9782        assert_eq!(
 9783            editor.selections.ranges(&editor.display_snapshot(cx)),
 9784            &[
 9785                Point::new(1, 0)..Point::new(1, 0),
 9786                Point::new(3, 0)..Point::new(3, 0),
 9787                Point::new(5, 0)..Point::new(5, 0)
 9788            ]
 9789        );
 9790    });
 9791}
 9792
 9793#[gpui::test]
 9794async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9795    init_test(cx, |settings| {
 9796        settings.defaults.auto_indent = Some(true);
 9797        settings.languages.0.insert(
 9798            "python".into(),
 9799            LanguageSettingsContent {
 9800                auto_indent: Some(false),
 9801                ..Default::default()
 9802            },
 9803        );
 9804    });
 9805
 9806    let mut cx = EditorTestContext::new(cx).await;
 9807
 9808    let injected_language = Arc::new(
 9809        Language::new(
 9810            LanguageConfig {
 9811                brackets: BracketPairConfig {
 9812                    pairs: vec![
 9813                        BracketPair {
 9814                            start: "{".to_string(),
 9815                            end: "}".to_string(),
 9816                            close: false,
 9817                            surround: false,
 9818                            newline: true,
 9819                        },
 9820                        BracketPair {
 9821                            start: "(".to_string(),
 9822                            end: ")".to_string(),
 9823                            close: true,
 9824                            surround: false,
 9825                            newline: true,
 9826                        },
 9827                    ],
 9828                    ..Default::default()
 9829                },
 9830                name: "python".into(),
 9831                ..Default::default()
 9832            },
 9833            Some(tree_sitter_python::LANGUAGE.into()),
 9834        )
 9835        .with_indents_query(
 9836            r#"
 9837                (_ "(" ")" @end) @indent
 9838                (_ "{" "}" @end) @indent
 9839            "#,
 9840        )
 9841        .unwrap(),
 9842    );
 9843
 9844    let language = Arc::new(
 9845        Language::new(
 9846            LanguageConfig {
 9847                brackets: BracketPairConfig {
 9848                    pairs: vec![
 9849                        BracketPair {
 9850                            start: "{".to_string(),
 9851                            end: "}".to_string(),
 9852                            close: false,
 9853                            surround: false,
 9854                            newline: true,
 9855                        },
 9856                        BracketPair {
 9857                            start: "(".to_string(),
 9858                            end: ")".to_string(),
 9859                            close: true,
 9860                            surround: false,
 9861                            newline: true,
 9862                        },
 9863                    ],
 9864                    ..Default::default()
 9865                },
 9866                name: LanguageName::new("rust"),
 9867                ..Default::default()
 9868            },
 9869            Some(tree_sitter_rust::LANGUAGE.into()),
 9870        )
 9871        .with_indents_query(
 9872            r#"
 9873                (_ "(" ")" @end) @indent
 9874                (_ "{" "}" @end) @indent
 9875            "#,
 9876        )
 9877        .unwrap()
 9878        .with_injection_query(
 9879            r#"
 9880            (macro_invocation
 9881                macro: (identifier) @_macro_name
 9882                (token_tree) @injection.content
 9883                (#set! injection.language "python"))
 9884           "#,
 9885        )
 9886        .unwrap(),
 9887    );
 9888
 9889    cx.language_registry().add(injected_language);
 9890    cx.language_registry().add(language.clone());
 9891
 9892    cx.update_buffer(|buffer, cx| {
 9893        buffer.set_language(Some(language), cx);
 9894    });
 9895
 9896    cx.set_state(r#"struct A {ˇ}"#);
 9897
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.newline(&Default::default(), window, cx);
 9900    });
 9901
 9902    cx.assert_editor_state(indoc!(
 9903        "struct A {
 9904            ˇ
 9905        }"
 9906    ));
 9907
 9908    cx.set_state(r#"select_biased!(ˇ)"#);
 9909
 9910    cx.update_editor(|editor, window, cx| {
 9911        editor.newline(&Default::default(), window, cx);
 9912        editor.handle_input("def ", window, cx);
 9913        editor.handle_input("(", window, cx);
 9914        editor.newline(&Default::default(), window, cx);
 9915        editor.handle_input("a", window, cx);
 9916    });
 9917
 9918    cx.assert_editor_state(indoc!(
 9919        "select_biased!(
 9920        def (
 9921 9922        )
 9923        )"
 9924    ));
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9929    init_test(cx, |_| {});
 9930
 9931    {
 9932        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9933        cx.set_state(indoc! {"
 9934            impl A {
 9935
 9936                fn b() {}
 9937
 9938            «fn c() {
 9939
 9940            }ˇ»
 9941            }
 9942        "});
 9943
 9944        cx.update_editor(|editor, window, cx| {
 9945            editor.autoindent(&Default::default(), window, cx);
 9946        });
 9947
 9948        cx.assert_editor_state(indoc! {"
 9949            impl A {
 9950
 9951                fn b() {}
 9952
 9953                «fn c() {
 9954
 9955                }ˇ»
 9956            }
 9957        "});
 9958    }
 9959
 9960    {
 9961        let mut cx = EditorTestContext::new_multibuffer(
 9962            cx,
 9963            [indoc! { "
 9964                impl A {
 9965                «
 9966                // a
 9967                fn b(){}
 9968                »
 9969                «
 9970                    }
 9971                    fn c(){}
 9972                »
 9973            "}],
 9974        );
 9975
 9976        let buffer = cx.update_editor(|editor, _, cx| {
 9977            let buffer = editor.buffer().update(cx, |buffer, _| {
 9978                buffer.all_buffers().iter().next().unwrap().clone()
 9979            });
 9980            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9981            buffer
 9982        });
 9983
 9984        cx.run_until_parked();
 9985        cx.update_editor(|editor, window, cx| {
 9986            editor.select_all(&Default::default(), window, cx);
 9987            editor.autoindent(&Default::default(), window, cx)
 9988        });
 9989        cx.run_until_parked();
 9990
 9991        cx.update(|_, cx| {
 9992            assert_eq!(
 9993                buffer.read(cx).text(),
 9994                indoc! { "
 9995                    impl A {
 9996
 9997                        // a
 9998                        fn b(){}
 9999
10000
10001                    }
10002                    fn c(){}
10003
10004                " }
10005            )
10006        });
10007    }
10008}
10009
10010#[gpui::test]
10011async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10012    init_test(cx, |_| {});
10013
10014    let mut cx = EditorTestContext::new(cx).await;
10015
10016    let language = Arc::new(Language::new(
10017        LanguageConfig {
10018            brackets: BracketPairConfig {
10019                pairs: vec![
10020                    BracketPair {
10021                        start: "{".to_string(),
10022                        end: "}".to_string(),
10023                        close: true,
10024                        surround: true,
10025                        newline: true,
10026                    },
10027                    BracketPair {
10028                        start: "(".to_string(),
10029                        end: ")".to_string(),
10030                        close: true,
10031                        surround: true,
10032                        newline: true,
10033                    },
10034                    BracketPair {
10035                        start: "/*".to_string(),
10036                        end: " */".to_string(),
10037                        close: true,
10038                        surround: true,
10039                        newline: true,
10040                    },
10041                    BracketPair {
10042                        start: "[".to_string(),
10043                        end: "]".to_string(),
10044                        close: false,
10045                        surround: false,
10046                        newline: true,
10047                    },
10048                    BracketPair {
10049                        start: "\"".to_string(),
10050                        end: "\"".to_string(),
10051                        close: true,
10052                        surround: true,
10053                        newline: false,
10054                    },
10055                    BracketPair {
10056                        start: "<".to_string(),
10057                        end: ">".to_string(),
10058                        close: false,
10059                        surround: true,
10060                        newline: true,
10061                    },
10062                ],
10063                ..Default::default()
10064            },
10065            autoclose_before: "})]".to_string(),
10066            ..Default::default()
10067        },
10068        Some(tree_sitter_rust::LANGUAGE.into()),
10069    ));
10070
10071    cx.language_registry().add(language.clone());
10072    cx.update_buffer(|buffer, cx| {
10073        buffer.set_language(Some(language), cx);
10074    });
10075
10076    cx.set_state(
10077        &r#"
10078            🏀ˇ
10079            εˇ
10080            ❤️ˇ
10081        "#
10082        .unindent(),
10083    );
10084
10085    // autoclose multiple nested brackets at multiple cursors
10086    cx.update_editor(|editor, window, cx| {
10087        editor.handle_input("{", window, cx);
10088        editor.handle_input("{", window, cx);
10089        editor.handle_input("{", window, cx);
10090    });
10091    cx.assert_editor_state(
10092        &"
10093            🏀{{{ˇ}}}
10094            ε{{{ˇ}}}
10095            ❤️{{{ˇ}}}
10096        "
10097        .unindent(),
10098    );
10099
10100    // insert a different closing bracket
10101    cx.update_editor(|editor, window, cx| {
10102        editor.handle_input(")", window, cx);
10103    });
10104    cx.assert_editor_state(
10105        &"
10106            🏀{{{)ˇ}}}
10107            ε{{{)ˇ}}}
10108            ❤️{{{)ˇ}}}
10109        "
10110        .unindent(),
10111    );
10112
10113    // skip over the auto-closed brackets when typing a closing bracket
10114    cx.update_editor(|editor, window, cx| {
10115        editor.move_right(&MoveRight, window, cx);
10116        editor.handle_input("}", window, cx);
10117        editor.handle_input("}", window, cx);
10118        editor.handle_input("}", window, cx);
10119    });
10120    cx.assert_editor_state(
10121        &"
10122            🏀{{{)}}}}ˇ
10123            ε{{{)}}}}ˇ
10124            ❤️{{{)}}}}ˇ
10125        "
10126        .unindent(),
10127    );
10128
10129    // autoclose multi-character pairs
10130    cx.set_state(
10131        &"
10132            ˇ
10133            ˇ
10134        "
10135        .unindent(),
10136    );
10137    cx.update_editor(|editor, window, cx| {
10138        editor.handle_input("/", window, cx);
10139        editor.handle_input("*", window, cx);
10140    });
10141    cx.assert_editor_state(
10142        &"
10143            /*ˇ */
10144            /*ˇ */
10145        "
10146        .unindent(),
10147    );
10148
10149    // one cursor autocloses a multi-character pair, one cursor
10150    // does not autoclose.
10151    cx.set_state(
10152        &"
1015310154            ˇ
10155        "
10156        .unindent(),
10157    );
10158    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10159    cx.assert_editor_state(
10160        &"
10161            /*ˇ */
1016210163        "
10164        .unindent(),
10165    );
10166
10167    // Don't autoclose if the next character isn't whitespace and isn't
10168    // listed in the language's "autoclose_before" section.
10169    cx.set_state("ˇa b");
10170    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10171    cx.assert_editor_state("{ˇa b");
10172
10173    // Don't autoclose if `close` is false for the bracket pair
10174    cx.set_state("ˇ");
10175    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10176    cx.assert_editor_state("");
10177
10178    // Surround with brackets if text is selected
10179    cx.set_state("«aˇ» b");
10180    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10181    cx.assert_editor_state("{«aˇ»} b");
10182
10183    // Autoclose when not immediately after a word character
10184    cx.set_state("a ˇ");
10185    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10186    cx.assert_editor_state("a \"ˇ\"");
10187
10188    // Autoclose pair where the start and end characters are the same
10189    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10190    cx.assert_editor_state("a \"\"ˇ");
10191
10192    // Don't autoclose when immediately after a word character
10193    cx.set_state("");
10194    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10195    cx.assert_editor_state("a\"ˇ");
10196
10197    // Do autoclose when after a non-word character
10198    cx.set_state("");
10199    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10200    cx.assert_editor_state("{\"ˇ\"");
10201
10202    // Non identical pairs autoclose regardless of preceding character
10203    cx.set_state("");
10204    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10205    cx.assert_editor_state("a{ˇ}");
10206
10207    // Don't autoclose pair if autoclose is disabled
10208    cx.set_state("ˇ");
10209    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10210    cx.assert_editor_state("");
10211
10212    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10213    cx.set_state("«aˇ» b");
10214    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10215    cx.assert_editor_state("<«aˇ»> b");
10216}
10217
10218#[gpui::test]
10219async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10220    init_test(cx, |settings| {
10221        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10222    });
10223
10224    let mut cx = EditorTestContext::new(cx).await;
10225
10226    let language = Arc::new(Language::new(
10227        LanguageConfig {
10228            brackets: BracketPairConfig {
10229                pairs: vec![
10230                    BracketPair {
10231                        start: "{".to_string(),
10232                        end: "}".to_string(),
10233                        close: true,
10234                        surround: true,
10235                        newline: true,
10236                    },
10237                    BracketPair {
10238                        start: "(".to_string(),
10239                        end: ")".to_string(),
10240                        close: true,
10241                        surround: true,
10242                        newline: true,
10243                    },
10244                    BracketPair {
10245                        start: "[".to_string(),
10246                        end: "]".to_string(),
10247                        close: false,
10248                        surround: false,
10249                        newline: true,
10250                    },
10251                ],
10252                ..Default::default()
10253            },
10254            autoclose_before: "})]".to_string(),
10255            ..Default::default()
10256        },
10257        Some(tree_sitter_rust::LANGUAGE.into()),
10258    ));
10259
10260    cx.language_registry().add(language.clone());
10261    cx.update_buffer(|buffer, cx| {
10262        buffer.set_language(Some(language), cx);
10263    });
10264
10265    cx.set_state(
10266        &"
10267            ˇ
10268            ˇ
10269            ˇ
10270        "
10271        .unindent(),
10272    );
10273
10274    // ensure only matching closing brackets are skipped over
10275    cx.update_editor(|editor, window, cx| {
10276        editor.handle_input("}", window, cx);
10277        editor.move_left(&MoveLeft, window, cx);
10278        editor.handle_input(")", window, cx);
10279        editor.move_left(&MoveLeft, window, cx);
10280    });
10281    cx.assert_editor_state(
10282        &"
10283            ˇ)}
10284            ˇ)}
10285            ˇ)}
10286        "
10287        .unindent(),
10288    );
10289
10290    // skip-over closing brackets at multiple cursors
10291    cx.update_editor(|editor, window, cx| {
10292        editor.handle_input(")", window, cx);
10293        editor.handle_input("}", window, cx);
10294    });
10295    cx.assert_editor_state(
10296        &"
10297            )}ˇ
10298            )}ˇ
10299            )}ˇ
10300        "
10301        .unindent(),
10302    );
10303
10304    // ignore non-close brackets
10305    cx.update_editor(|editor, window, cx| {
10306        editor.handle_input("]", window, cx);
10307        editor.move_left(&MoveLeft, window, cx);
10308        editor.handle_input("]", window, cx);
10309    });
10310    cx.assert_editor_state(
10311        &"
10312            )}]ˇ]
10313            )}]ˇ]
10314            )}]ˇ]
10315        "
10316        .unindent(),
10317    );
10318}
10319
10320#[gpui::test]
10321async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10322    init_test(cx, |_| {});
10323
10324    let mut cx = EditorTestContext::new(cx).await;
10325
10326    let html_language = Arc::new(
10327        Language::new(
10328            LanguageConfig {
10329                name: "HTML".into(),
10330                brackets: BracketPairConfig {
10331                    pairs: vec![
10332                        BracketPair {
10333                            start: "<".into(),
10334                            end: ">".into(),
10335                            close: true,
10336                            ..Default::default()
10337                        },
10338                        BracketPair {
10339                            start: "{".into(),
10340                            end: "}".into(),
10341                            close: true,
10342                            ..Default::default()
10343                        },
10344                        BracketPair {
10345                            start: "(".into(),
10346                            end: ")".into(),
10347                            close: true,
10348                            ..Default::default()
10349                        },
10350                    ],
10351                    ..Default::default()
10352                },
10353                autoclose_before: "})]>".into(),
10354                ..Default::default()
10355            },
10356            Some(tree_sitter_html::LANGUAGE.into()),
10357        )
10358        .with_injection_query(
10359            r#"
10360            (script_element
10361                (raw_text) @injection.content
10362                (#set! injection.language "javascript"))
10363            "#,
10364        )
10365        .unwrap(),
10366    );
10367
10368    let javascript_language = Arc::new(Language::new(
10369        LanguageConfig {
10370            name: "JavaScript".into(),
10371            brackets: BracketPairConfig {
10372                pairs: vec![
10373                    BracketPair {
10374                        start: "/*".into(),
10375                        end: " */".into(),
10376                        close: true,
10377                        ..Default::default()
10378                    },
10379                    BracketPair {
10380                        start: "{".into(),
10381                        end: "}".into(),
10382                        close: true,
10383                        ..Default::default()
10384                    },
10385                    BracketPair {
10386                        start: "(".into(),
10387                        end: ")".into(),
10388                        close: true,
10389                        ..Default::default()
10390                    },
10391                ],
10392                ..Default::default()
10393            },
10394            autoclose_before: "})]>".into(),
10395            ..Default::default()
10396        },
10397        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10398    ));
10399
10400    cx.language_registry().add(html_language.clone());
10401    cx.language_registry().add(javascript_language);
10402    cx.executor().run_until_parked();
10403
10404    cx.update_buffer(|buffer, cx| {
10405        buffer.set_language(Some(html_language), cx);
10406    });
10407
10408    cx.set_state(
10409        &r#"
10410            <body>ˇ
10411                <script>
10412                    var x = 1;ˇ
10413                </script>
10414            </body>ˇ
10415        "#
10416        .unindent(),
10417    );
10418
10419    // Precondition: different languages are active at different locations.
10420    cx.update_editor(|editor, window, cx| {
10421        let snapshot = editor.snapshot(window, cx);
10422        let cursors = editor
10423            .selections
10424            .ranges::<usize>(&editor.display_snapshot(cx));
10425        let languages = cursors
10426            .iter()
10427            .map(|c| snapshot.language_at(c.start).unwrap().name())
10428            .collect::<Vec<_>>();
10429        assert_eq!(
10430            languages,
10431            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10432        );
10433    });
10434
10435    // Angle brackets autoclose in HTML, but not JavaScript.
10436    cx.update_editor(|editor, window, cx| {
10437        editor.handle_input("<", window, cx);
10438        editor.handle_input("a", window, cx);
10439    });
10440    cx.assert_editor_state(
10441        &r#"
10442            <body><aˇ>
10443                <script>
10444                    var x = 1;<aˇ
10445                </script>
10446            </body><aˇ>
10447        "#
10448        .unindent(),
10449    );
10450
10451    // Curly braces and parens autoclose in both HTML and JavaScript.
10452    cx.update_editor(|editor, window, cx| {
10453        editor.handle_input(" b=", window, cx);
10454        editor.handle_input("{", window, cx);
10455        editor.handle_input("c", window, cx);
10456        editor.handle_input("(", window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body><a b={c(ˇ)}>
10461                <script>
10462                    var x = 1;<a b={c(ˇ)}
10463                </script>
10464            </body><a b={c(ˇ)}>
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Brackets that were already autoclosed are skipped.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input(")", window, cx);
10472        editor.handle_input("d", window, cx);
10473        editor.handle_input("}", window, cx);
10474    });
10475    cx.assert_editor_state(
10476        &r#"
10477            <body><a b={c()d}ˇ>
10478                <script>
10479                    var x = 1;<a b={c()d}ˇ
10480                </script>
10481            </body><a b={c()d}ˇ>
10482        "#
10483        .unindent(),
10484    );
10485    cx.update_editor(|editor, window, cx| {
10486        editor.handle_input(">", window, cx);
10487    });
10488    cx.assert_editor_state(
10489        &r#"
10490            <body><a b={c()d}>ˇ
10491                <script>
10492                    var x = 1;<a b={c()d}>ˇ
10493                </script>
10494            </body><a b={c()d}>ˇ
10495        "#
10496        .unindent(),
10497    );
10498
10499    // Reset
10500    cx.set_state(
10501        &r#"
10502            <body>ˇ
10503                <script>
10504                    var x = 1;ˇ
10505                </script>
10506            </body>ˇ
10507        "#
10508        .unindent(),
10509    );
10510
10511    cx.update_editor(|editor, window, cx| {
10512        editor.handle_input("<", window, cx);
10513    });
10514    cx.assert_editor_state(
10515        &r#"
10516            <body><ˇ>
10517                <script>
10518                    var x = 1;<ˇ
10519                </script>
10520            </body><ˇ>
10521        "#
10522        .unindent(),
10523    );
10524
10525    // When backspacing, the closing angle brackets are removed.
10526    cx.update_editor(|editor, window, cx| {
10527        editor.backspace(&Backspace, window, cx);
10528    });
10529    cx.assert_editor_state(
10530        &r#"
10531            <body>ˇ
10532                <script>
10533                    var x = 1;ˇ
10534                </script>
10535            </body>ˇ
10536        "#
10537        .unindent(),
10538    );
10539
10540    // Block comments autoclose in JavaScript, but not HTML.
10541    cx.update_editor(|editor, window, cx| {
10542        editor.handle_input("/", window, cx);
10543        editor.handle_input("*", window, cx);
10544    });
10545    cx.assert_editor_state(
10546        &r#"
10547            <body>/*ˇ
10548                <script>
10549                    var x = 1;/*ˇ */
10550                </script>
10551            </body>/*ˇ
10552        "#
10553        .unindent(),
10554    );
10555}
10556
10557#[gpui::test]
10558async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10559    init_test(cx, |_| {});
10560
10561    let mut cx = EditorTestContext::new(cx).await;
10562
10563    let rust_language = Arc::new(
10564        Language::new(
10565            LanguageConfig {
10566                name: "Rust".into(),
10567                brackets: serde_json::from_value(json!([
10568                    { "start": "{", "end": "}", "close": true, "newline": true },
10569                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10570                ]))
10571                .unwrap(),
10572                autoclose_before: "})]>".into(),
10573                ..Default::default()
10574            },
10575            Some(tree_sitter_rust::LANGUAGE.into()),
10576        )
10577        .with_override_query("(string_literal) @string")
10578        .unwrap(),
10579    );
10580
10581    cx.language_registry().add(rust_language.clone());
10582    cx.update_buffer(|buffer, cx| {
10583        buffer.set_language(Some(rust_language), cx);
10584    });
10585
10586    cx.set_state(
10587        &r#"
10588            let x = ˇ
10589        "#
10590        .unindent(),
10591    );
10592
10593    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10594    cx.update_editor(|editor, window, cx| {
10595        editor.handle_input("\"", window, cx);
10596    });
10597    cx.assert_editor_state(
10598        &r#"
10599            let x = "ˇ"
10600        "#
10601        .unindent(),
10602    );
10603
10604    // Inserting another quotation mark. The cursor moves across the existing
10605    // automatically-inserted quotation mark.
10606    cx.update_editor(|editor, window, cx| {
10607        editor.handle_input("\"", window, cx);
10608    });
10609    cx.assert_editor_state(
10610        &r#"
10611            let x = ""ˇ
10612        "#
10613        .unindent(),
10614    );
10615
10616    // Reset
10617    cx.set_state(
10618        &r#"
10619            let x = ˇ
10620        "#
10621        .unindent(),
10622    );
10623
10624    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10625    cx.update_editor(|editor, window, cx| {
10626        editor.handle_input("\"", window, cx);
10627        editor.handle_input(" ", window, cx);
10628        editor.move_left(&Default::default(), window, cx);
10629        editor.handle_input("\\", window, cx);
10630        editor.handle_input("\"", window, cx);
10631    });
10632    cx.assert_editor_state(
10633        &r#"
10634            let x = "\"ˇ "
10635        "#
10636        .unindent(),
10637    );
10638
10639    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10640    // mark. Nothing is inserted.
10641    cx.update_editor(|editor, window, cx| {
10642        editor.move_right(&Default::default(), window, cx);
10643        editor.handle_input("\"", window, cx);
10644    });
10645    cx.assert_editor_state(
10646        &r#"
10647            let x = "\" "ˇ
10648        "#
10649        .unindent(),
10650    );
10651}
10652
10653#[gpui::test]
10654async fn test_surround_with_pair(cx: &mut TestAppContext) {
10655    init_test(cx, |_| {});
10656
10657    let language = Arc::new(Language::new(
10658        LanguageConfig {
10659            brackets: BracketPairConfig {
10660                pairs: vec![
10661                    BracketPair {
10662                        start: "{".to_string(),
10663                        end: "}".to_string(),
10664                        close: true,
10665                        surround: true,
10666                        newline: true,
10667                    },
10668                    BracketPair {
10669                        start: "/* ".to_string(),
10670                        end: "*/".to_string(),
10671                        close: true,
10672                        surround: true,
10673                        ..Default::default()
10674                    },
10675                ],
10676                ..Default::default()
10677            },
10678            ..Default::default()
10679        },
10680        Some(tree_sitter_rust::LANGUAGE.into()),
10681    ));
10682
10683    let text = r#"
10684        a
10685        b
10686        c
10687    "#
10688    .unindent();
10689
10690    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10691    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10692    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10693    editor
10694        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10695        .await;
10696
10697    editor.update_in(cx, |editor, window, cx| {
10698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10699            s.select_display_ranges([
10700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10702                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10703            ])
10704        });
10705
10706        editor.handle_input("{", window, cx);
10707        editor.handle_input("{", window, cx);
10708        editor.handle_input("{", window, cx);
10709        assert_eq!(
10710            editor.text(cx),
10711            "
10712                {{{a}}}
10713                {{{b}}}
10714                {{{c}}}
10715            "
10716            .unindent()
10717        );
10718        assert_eq!(
10719            editor.selections.display_ranges(cx),
10720            [
10721                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10722                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10723                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10724            ]
10725        );
10726
10727        editor.undo(&Undo, window, cx);
10728        editor.undo(&Undo, window, cx);
10729        editor.undo(&Undo, window, cx);
10730        assert_eq!(
10731            editor.text(cx),
10732            "
10733                a
10734                b
10735                c
10736            "
10737            .unindent()
10738        );
10739        assert_eq!(
10740            editor.selections.display_ranges(cx),
10741            [
10742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10743                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10744                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10745            ]
10746        );
10747
10748        // Ensure inserting the first character of a multi-byte bracket pair
10749        // doesn't surround the selections with the bracket.
10750        editor.handle_input("/", window, cx);
10751        assert_eq!(
10752            editor.text(cx),
10753            "
10754                /
10755                /
10756                /
10757            "
10758            .unindent()
10759        );
10760        assert_eq!(
10761            editor.selections.display_ranges(cx),
10762            [
10763                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10764                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10765                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10766            ]
10767        );
10768
10769        editor.undo(&Undo, window, cx);
10770        assert_eq!(
10771            editor.text(cx),
10772            "
10773                a
10774                b
10775                c
10776            "
10777            .unindent()
10778        );
10779        assert_eq!(
10780            editor.selections.display_ranges(cx),
10781            [
10782                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10783                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10784                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10785            ]
10786        );
10787
10788        // Ensure inserting the last character of a multi-byte bracket pair
10789        // doesn't surround the selections with the bracket.
10790        editor.handle_input("*", window, cx);
10791        assert_eq!(
10792            editor.text(cx),
10793            "
10794                *
10795                *
10796                *
10797            "
10798            .unindent()
10799        );
10800        assert_eq!(
10801            editor.selections.display_ranges(cx),
10802            [
10803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10804                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10805                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10806            ]
10807        );
10808    });
10809}
10810
10811#[gpui::test]
10812async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10813    init_test(cx, |_| {});
10814
10815    let language = Arc::new(Language::new(
10816        LanguageConfig {
10817            brackets: BracketPairConfig {
10818                pairs: vec![BracketPair {
10819                    start: "{".to_string(),
10820                    end: "}".to_string(),
10821                    close: true,
10822                    surround: true,
10823                    newline: true,
10824                }],
10825                ..Default::default()
10826            },
10827            autoclose_before: "}".to_string(),
10828            ..Default::default()
10829        },
10830        Some(tree_sitter_rust::LANGUAGE.into()),
10831    ));
10832
10833    let text = r#"
10834        a
10835        b
10836        c
10837    "#
10838    .unindent();
10839
10840    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10841    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10842    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10843    editor
10844        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10845        .await;
10846
10847    editor.update_in(cx, |editor, window, cx| {
10848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10849            s.select_ranges([
10850                Point::new(0, 1)..Point::new(0, 1),
10851                Point::new(1, 1)..Point::new(1, 1),
10852                Point::new(2, 1)..Point::new(2, 1),
10853            ])
10854        });
10855
10856        editor.handle_input("{", window, cx);
10857        editor.handle_input("{", window, cx);
10858        editor.handle_input("_", window, cx);
10859        assert_eq!(
10860            editor.text(cx),
10861            "
10862                a{{_}}
10863                b{{_}}
10864                c{{_}}
10865            "
10866            .unindent()
10867        );
10868        assert_eq!(
10869            editor
10870                .selections
10871                .ranges::<Point>(&editor.display_snapshot(cx)),
10872            [
10873                Point::new(0, 4)..Point::new(0, 4),
10874                Point::new(1, 4)..Point::new(1, 4),
10875                Point::new(2, 4)..Point::new(2, 4)
10876            ]
10877        );
10878
10879        editor.backspace(&Default::default(), window, cx);
10880        editor.backspace(&Default::default(), window, cx);
10881        assert_eq!(
10882            editor.text(cx),
10883            "
10884                a{}
10885                b{}
10886                c{}
10887            "
10888            .unindent()
10889        );
10890        assert_eq!(
10891            editor
10892                .selections
10893                .ranges::<Point>(&editor.display_snapshot(cx)),
10894            [
10895                Point::new(0, 2)..Point::new(0, 2),
10896                Point::new(1, 2)..Point::new(1, 2),
10897                Point::new(2, 2)..Point::new(2, 2)
10898            ]
10899        );
10900
10901        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10902        assert_eq!(
10903            editor.text(cx),
10904            "
10905                a
10906                b
10907                c
10908            "
10909            .unindent()
10910        );
10911        assert_eq!(
10912            editor
10913                .selections
10914                .ranges::<Point>(&editor.display_snapshot(cx)),
10915            [
10916                Point::new(0, 1)..Point::new(0, 1),
10917                Point::new(1, 1)..Point::new(1, 1),
10918                Point::new(2, 1)..Point::new(2, 1)
10919            ]
10920        );
10921    });
10922}
10923
10924#[gpui::test]
10925async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10926    init_test(cx, |settings| {
10927        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10928    });
10929
10930    let mut cx = EditorTestContext::new(cx).await;
10931
10932    let language = Arc::new(Language::new(
10933        LanguageConfig {
10934            brackets: BracketPairConfig {
10935                pairs: vec![
10936                    BracketPair {
10937                        start: "{".to_string(),
10938                        end: "}".to_string(),
10939                        close: true,
10940                        surround: true,
10941                        newline: true,
10942                    },
10943                    BracketPair {
10944                        start: "(".to_string(),
10945                        end: ")".to_string(),
10946                        close: true,
10947                        surround: true,
10948                        newline: true,
10949                    },
10950                    BracketPair {
10951                        start: "[".to_string(),
10952                        end: "]".to_string(),
10953                        close: false,
10954                        surround: true,
10955                        newline: true,
10956                    },
10957                ],
10958                ..Default::default()
10959            },
10960            autoclose_before: "})]".to_string(),
10961            ..Default::default()
10962        },
10963        Some(tree_sitter_rust::LANGUAGE.into()),
10964    ));
10965
10966    cx.language_registry().add(language.clone());
10967    cx.update_buffer(|buffer, cx| {
10968        buffer.set_language(Some(language), cx);
10969    });
10970
10971    cx.set_state(
10972        &"
10973            {(ˇ)}
10974            [[ˇ]]
10975            {(ˇ)}
10976        "
10977        .unindent(),
10978    );
10979
10980    cx.update_editor(|editor, window, cx| {
10981        editor.backspace(&Default::default(), window, cx);
10982        editor.backspace(&Default::default(), window, cx);
10983    });
10984
10985    cx.assert_editor_state(
10986        &"
10987            ˇ
10988            ˇ]]
10989            ˇ
10990        "
10991        .unindent(),
10992    );
10993
10994    cx.update_editor(|editor, window, cx| {
10995        editor.handle_input("{", window, cx);
10996        editor.handle_input("{", window, cx);
10997        editor.move_right(&MoveRight, window, cx);
10998        editor.move_right(&MoveRight, window, cx);
10999        editor.move_left(&MoveLeft, window, cx);
11000        editor.move_left(&MoveLeft, window, cx);
11001        editor.backspace(&Default::default(), window, cx);
11002    });
11003
11004    cx.assert_editor_state(
11005        &"
11006            {ˇ}
11007            {ˇ}]]
11008            {ˇ}
11009        "
11010        .unindent(),
11011    );
11012
11013    cx.update_editor(|editor, window, cx| {
11014        editor.backspace(&Default::default(), window, cx);
11015    });
11016
11017    cx.assert_editor_state(
11018        &"
11019            ˇ
11020            ˇ]]
11021            ˇ
11022        "
11023        .unindent(),
11024    );
11025}
11026
11027#[gpui::test]
11028async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11029    init_test(cx, |_| {});
11030
11031    let language = Arc::new(Language::new(
11032        LanguageConfig::default(),
11033        Some(tree_sitter_rust::LANGUAGE.into()),
11034    ));
11035
11036    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11037    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11038    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11039    editor
11040        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11041        .await;
11042
11043    editor.update_in(cx, |editor, window, cx| {
11044        editor.set_auto_replace_emoji_shortcode(true);
11045
11046        editor.handle_input("Hello ", window, cx);
11047        editor.handle_input(":wave", window, cx);
11048        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11049
11050        editor.handle_input(":", window, cx);
11051        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11052
11053        editor.handle_input(" :smile", window, cx);
11054        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11055
11056        editor.handle_input(":", window, cx);
11057        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11058
11059        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11060        editor.handle_input(":wave", window, cx);
11061        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11062
11063        editor.handle_input(":", window, cx);
11064        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11065
11066        editor.handle_input(":1", window, cx);
11067        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11068
11069        editor.handle_input(":", window, cx);
11070        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11071
11072        // Ensure shortcode does not get replaced when it is part of a word
11073        editor.handle_input(" Test:wave", window, cx);
11074        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11075
11076        editor.handle_input(":", window, cx);
11077        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11078
11079        editor.set_auto_replace_emoji_shortcode(false);
11080
11081        // Ensure shortcode does not get replaced when auto replace is off
11082        editor.handle_input(" :wave", window, cx);
11083        assert_eq!(
11084            editor.text(cx),
11085            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11086        );
11087
11088        editor.handle_input(":", window, cx);
11089        assert_eq!(
11090            editor.text(cx),
11091            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11092        );
11093    });
11094}
11095
11096#[gpui::test]
11097async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11098    init_test(cx, |_| {});
11099
11100    let (text, insertion_ranges) = marked_text_ranges(
11101        indoc! {"
11102            ˇ
11103        "},
11104        false,
11105    );
11106
11107    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11108    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11109
11110    _ = editor.update_in(cx, |editor, window, cx| {
11111        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11112
11113        editor
11114            .insert_snippet(&insertion_ranges, snippet, window, cx)
11115            .unwrap();
11116
11117        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11118            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11119            assert_eq!(editor.text(cx), expected_text);
11120            assert_eq!(
11121                editor
11122                    .selections
11123                    .ranges::<usize>(&editor.display_snapshot(cx)),
11124                selection_ranges
11125            );
11126        }
11127
11128        assert(
11129            editor,
11130            cx,
11131            indoc! {"
11132            type «» =•
11133            "},
11134        );
11135
11136        assert!(editor.context_menu_visible(), "There should be a matches");
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_snippets(cx: &mut TestAppContext) {
11142    init_test(cx, |_| {});
11143
11144    let mut cx = EditorTestContext::new(cx).await;
11145
11146    cx.set_state(indoc! {"
11147        a.ˇ b
11148        a.ˇ b
11149        a.ˇ b
11150    "});
11151
11152    cx.update_editor(|editor, window, cx| {
11153        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11154        let insertion_ranges = editor
11155            .selections
11156            .all(&editor.display_snapshot(cx))
11157            .iter()
11158            .map(|s| s.range())
11159            .collect::<Vec<_>>();
11160        editor
11161            .insert_snippet(&insertion_ranges, snippet, window, cx)
11162            .unwrap();
11163    });
11164
11165    cx.assert_editor_state(indoc! {"
11166        a.f(«oneˇ», two, «threeˇ») b
11167        a.f(«oneˇ», two, «threeˇ») b
11168        a.f(«oneˇ», two, «threeˇ») b
11169    "});
11170
11171    // Can't move earlier than the first tab stop
11172    cx.update_editor(|editor, window, cx| {
11173        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11174    });
11175    cx.assert_editor_state(indoc! {"
11176        a.f(«oneˇ», two, «threeˇ») b
11177        a.f(«oneˇ», two, «threeˇ») b
11178        a.f(«oneˇ», two, «threeˇ») b
11179    "});
11180
11181    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11182    cx.assert_editor_state(indoc! {"
11183        a.f(one, «twoˇ», three) b
11184        a.f(one, «twoˇ», three) b
11185        a.f(one, «twoˇ», three) b
11186    "});
11187
11188    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11189    cx.assert_editor_state(indoc! {"
11190        a.f(«oneˇ», two, «threeˇ») b
11191        a.f(«oneˇ», two, «threeˇ») b
11192        a.f(«oneˇ», two, «threeˇ») b
11193    "});
11194
11195    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11196    cx.assert_editor_state(indoc! {"
11197        a.f(one, «twoˇ», three) b
11198        a.f(one, «twoˇ», three) b
11199        a.f(one, «twoˇ», three) b
11200    "});
11201    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11202    cx.assert_editor_state(indoc! {"
11203        a.f(one, two, three)ˇ b
11204        a.f(one, two, three)ˇ b
11205        a.f(one, two, three)ˇ b
11206    "});
11207
11208    // As soon as the last tab stop is reached, snippet state is gone
11209    cx.update_editor(|editor, window, cx| {
11210        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11211    });
11212    cx.assert_editor_state(indoc! {"
11213        a.f(one, two, three)ˇ b
11214        a.f(one, two, three)ˇ b
11215        a.f(one, two, three)ˇ b
11216    "});
11217}
11218
11219#[gpui::test]
11220async fn test_snippet_indentation(cx: &mut TestAppContext) {
11221    init_test(cx, |_| {});
11222
11223    let mut cx = EditorTestContext::new(cx).await;
11224
11225    cx.update_editor(|editor, window, cx| {
11226        let snippet = Snippet::parse(indoc! {"
11227            /*
11228             * Multiline comment with leading indentation
11229             *
11230             * $1
11231             */
11232            $0"})
11233        .unwrap();
11234        let insertion_ranges = editor
11235            .selections
11236            .all(&editor.display_snapshot(cx))
11237            .iter()
11238            .map(|s| s.range())
11239            .collect::<Vec<_>>();
11240        editor
11241            .insert_snippet(&insertion_ranges, snippet, window, cx)
11242            .unwrap();
11243    });
11244
11245    cx.assert_editor_state(indoc! {"
11246        /*
11247         * Multiline comment with leading indentation
11248         *
11249         * ˇ
11250         */
11251    "});
11252
11253    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11254    cx.assert_editor_state(indoc! {"
11255        /*
11256         * Multiline comment with leading indentation
11257         *
11258         *•
11259         */
11260        ˇ"});
11261}
11262
11263#[gpui::test]
11264async fn test_document_format_during_save(cx: &mut TestAppContext) {
11265    init_test(cx, |_| {});
11266
11267    let fs = FakeFs::new(cx.executor());
11268    fs.insert_file(path!("/file.rs"), Default::default()).await;
11269
11270    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11271
11272    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11273    language_registry.add(rust_lang());
11274    let mut fake_servers = language_registry.register_fake_lsp(
11275        "Rust",
11276        FakeLspAdapter {
11277            capabilities: lsp::ServerCapabilities {
11278                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11279                ..Default::default()
11280            },
11281            ..Default::default()
11282        },
11283    );
11284
11285    let buffer = project
11286        .update(cx, |project, cx| {
11287            project.open_local_buffer(path!("/file.rs"), cx)
11288        })
11289        .await
11290        .unwrap();
11291
11292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11293    let (editor, cx) = cx.add_window_view(|window, cx| {
11294        build_editor_with_project(project.clone(), buffer, window, cx)
11295    });
11296    editor.update_in(cx, |editor, window, cx| {
11297        editor.set_text("one\ntwo\nthree\n", window, cx)
11298    });
11299    assert!(cx.read(|cx| editor.is_dirty(cx)));
11300
11301    cx.executor().start_waiting();
11302    let fake_server = fake_servers.next().await.unwrap();
11303
11304    {
11305        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11306            move |params, _| async move {
11307                assert_eq!(
11308                    params.text_document.uri,
11309                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11310                );
11311                assert_eq!(params.options.tab_size, 4);
11312                Ok(Some(vec![lsp::TextEdit::new(
11313                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11314                    ", ".to_string(),
11315                )]))
11316            },
11317        );
11318        let save = editor
11319            .update_in(cx, |editor, window, cx| {
11320                editor.save(
11321                    SaveOptions {
11322                        format: true,
11323                        autosave: false,
11324                    },
11325                    project.clone(),
11326                    window,
11327                    cx,
11328                )
11329            })
11330            .unwrap();
11331        cx.executor().start_waiting();
11332        save.await;
11333
11334        assert_eq!(
11335            editor.update(cx, |editor, cx| editor.text(cx)),
11336            "one, two\nthree\n"
11337        );
11338        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11339    }
11340
11341    {
11342        editor.update_in(cx, |editor, window, cx| {
11343            editor.set_text("one\ntwo\nthree\n", window, cx)
11344        });
11345        assert!(cx.read(|cx| editor.is_dirty(cx)));
11346
11347        // Ensure we can still save even if formatting hangs.
11348        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11349            move |params, _| async move {
11350                assert_eq!(
11351                    params.text_document.uri,
11352                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11353                );
11354                futures::future::pending::<()>().await;
11355                unreachable!()
11356            },
11357        );
11358        let save = editor
11359            .update_in(cx, |editor, window, cx| {
11360                editor.save(
11361                    SaveOptions {
11362                        format: true,
11363                        autosave: false,
11364                    },
11365                    project.clone(),
11366                    window,
11367                    cx,
11368                )
11369            })
11370            .unwrap();
11371        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11372        cx.executor().start_waiting();
11373        save.await;
11374        assert_eq!(
11375            editor.update(cx, |editor, cx| editor.text(cx)),
11376            "one\ntwo\nthree\n"
11377        );
11378    }
11379
11380    // Set rust language override and assert overridden tabsize is sent to language server
11381    update_test_language_settings(cx, |settings| {
11382        settings.languages.0.insert(
11383            "Rust".into(),
11384            LanguageSettingsContent {
11385                tab_size: NonZeroU32::new(8),
11386                ..Default::default()
11387            },
11388        );
11389    });
11390
11391    {
11392        editor.update_in(cx, |editor, window, cx| {
11393            editor.set_text("somehting_new\n", window, cx)
11394        });
11395        assert!(cx.read(|cx| editor.is_dirty(cx)));
11396        let _formatting_request_signal = fake_server
11397            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11398                assert_eq!(
11399                    params.text_document.uri,
11400                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11401                );
11402                assert_eq!(params.options.tab_size, 8);
11403                Ok(Some(vec![]))
11404            });
11405        let save = editor
11406            .update_in(cx, |editor, window, cx| {
11407                editor.save(
11408                    SaveOptions {
11409                        format: true,
11410                        autosave: false,
11411                    },
11412                    project.clone(),
11413                    window,
11414                    cx,
11415                )
11416            })
11417            .unwrap();
11418        cx.executor().start_waiting();
11419        save.await;
11420    }
11421}
11422
11423#[gpui::test]
11424async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11425    init_test(cx, |settings| {
11426        settings.defaults.ensure_final_newline_on_save = Some(false);
11427    });
11428
11429    let fs = FakeFs::new(cx.executor());
11430    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11431
11432    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11433
11434    let buffer = project
11435        .update(cx, |project, cx| {
11436            project.open_local_buffer(path!("/file.txt"), cx)
11437        })
11438        .await
11439        .unwrap();
11440
11441    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11442    let (editor, cx) = cx.add_window_view(|window, cx| {
11443        build_editor_with_project(project.clone(), buffer, window, cx)
11444    });
11445    editor.update_in(cx, |editor, window, cx| {
11446        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11447            s.select_ranges([0..0])
11448        });
11449    });
11450    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11451
11452    editor.update_in(cx, |editor, window, cx| {
11453        editor.handle_input("\n", window, cx)
11454    });
11455    cx.run_until_parked();
11456    save(&editor, &project, cx).await;
11457    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11458
11459    editor.update_in(cx, |editor, window, cx| {
11460        editor.undo(&Default::default(), window, cx);
11461    });
11462    save(&editor, &project, cx).await;
11463    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11464
11465    editor.update_in(cx, |editor, window, cx| {
11466        editor.redo(&Default::default(), window, cx);
11467    });
11468    cx.run_until_parked();
11469    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11470
11471    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11472        let save = editor
11473            .update_in(cx, |editor, window, cx| {
11474                editor.save(
11475                    SaveOptions {
11476                        format: true,
11477                        autosave: false,
11478                    },
11479                    project.clone(),
11480                    window,
11481                    cx,
11482                )
11483            })
11484            .unwrap();
11485        cx.executor().start_waiting();
11486        save.await;
11487        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11488    }
11489}
11490
11491#[gpui::test]
11492async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11493    init_test(cx, |_| {});
11494
11495    let cols = 4;
11496    let rows = 10;
11497    let sample_text_1 = sample_text(rows, cols, 'a');
11498    assert_eq!(
11499        sample_text_1,
11500        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11501    );
11502    let sample_text_2 = sample_text(rows, cols, 'l');
11503    assert_eq!(
11504        sample_text_2,
11505        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11506    );
11507    let sample_text_3 = sample_text(rows, cols, 'v');
11508    assert_eq!(
11509        sample_text_3,
11510        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11511    );
11512
11513    let fs = FakeFs::new(cx.executor());
11514    fs.insert_tree(
11515        path!("/a"),
11516        json!({
11517            "main.rs": sample_text_1,
11518            "other.rs": sample_text_2,
11519            "lib.rs": sample_text_3,
11520        }),
11521    )
11522    .await;
11523
11524    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11525    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11526    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11527
11528    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11529    language_registry.add(rust_lang());
11530    let mut fake_servers = language_registry.register_fake_lsp(
11531        "Rust",
11532        FakeLspAdapter {
11533            capabilities: lsp::ServerCapabilities {
11534                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11535                ..Default::default()
11536            },
11537            ..Default::default()
11538        },
11539    );
11540
11541    let worktree = project.update(cx, |project, cx| {
11542        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11543        assert_eq!(worktrees.len(), 1);
11544        worktrees.pop().unwrap()
11545    });
11546    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11547
11548    let buffer_1 = project
11549        .update(cx, |project, cx| {
11550            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11551        })
11552        .await
11553        .unwrap();
11554    let buffer_2 = project
11555        .update(cx, |project, cx| {
11556            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11557        })
11558        .await
11559        .unwrap();
11560    let buffer_3 = project
11561        .update(cx, |project, cx| {
11562            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11563        })
11564        .await
11565        .unwrap();
11566
11567    let multi_buffer = cx.new(|cx| {
11568        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11569        multi_buffer.push_excerpts(
11570            buffer_1.clone(),
11571            [
11572                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11573                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11574                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11575            ],
11576            cx,
11577        );
11578        multi_buffer.push_excerpts(
11579            buffer_2.clone(),
11580            [
11581                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11582                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11583                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11584            ],
11585            cx,
11586        );
11587        multi_buffer.push_excerpts(
11588            buffer_3.clone(),
11589            [
11590                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11591                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11592                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11593            ],
11594            cx,
11595        );
11596        multi_buffer
11597    });
11598    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11599        Editor::new(
11600            EditorMode::full(),
11601            multi_buffer,
11602            Some(project.clone()),
11603            window,
11604            cx,
11605        )
11606    });
11607
11608    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11609        editor.change_selections(
11610            SelectionEffects::scroll(Autoscroll::Next),
11611            window,
11612            cx,
11613            |s| s.select_ranges(Some(1..2)),
11614        );
11615        editor.insert("|one|two|three|", window, cx);
11616    });
11617    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11618    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11619        editor.change_selections(
11620            SelectionEffects::scroll(Autoscroll::Next),
11621            window,
11622            cx,
11623            |s| s.select_ranges(Some(60..70)),
11624        );
11625        editor.insert("|four|five|six|", window, cx);
11626    });
11627    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11628
11629    // First two buffers should be edited, but not the third one.
11630    assert_eq!(
11631        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11632        "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}",
11633    );
11634    buffer_1.update(cx, |buffer, _| {
11635        assert!(buffer.is_dirty());
11636        assert_eq!(
11637            buffer.text(),
11638            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11639        )
11640    });
11641    buffer_2.update(cx, |buffer, _| {
11642        assert!(buffer.is_dirty());
11643        assert_eq!(
11644            buffer.text(),
11645            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11646        )
11647    });
11648    buffer_3.update(cx, |buffer, _| {
11649        assert!(!buffer.is_dirty());
11650        assert_eq!(buffer.text(), sample_text_3,)
11651    });
11652    cx.executor().run_until_parked();
11653
11654    cx.executor().start_waiting();
11655    let save = multi_buffer_editor
11656        .update_in(cx, |editor, window, cx| {
11657            editor.save(
11658                SaveOptions {
11659                    format: true,
11660                    autosave: false,
11661                },
11662                project.clone(),
11663                window,
11664                cx,
11665            )
11666        })
11667        .unwrap();
11668
11669    let fake_server = fake_servers.next().await.unwrap();
11670    fake_server
11671        .server
11672        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11673            Ok(Some(vec![lsp::TextEdit::new(
11674                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11675                format!("[{} formatted]", params.text_document.uri),
11676            )]))
11677        })
11678        .detach();
11679    save.await;
11680
11681    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11682    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11683    assert_eq!(
11684        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11685        uri!(
11686            "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}"
11687        ),
11688    );
11689    buffer_1.update(cx, |buffer, _| {
11690        assert!(!buffer.is_dirty());
11691        assert_eq!(
11692            buffer.text(),
11693            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11694        )
11695    });
11696    buffer_2.update(cx, |buffer, _| {
11697        assert!(!buffer.is_dirty());
11698        assert_eq!(
11699            buffer.text(),
11700            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11701        )
11702    });
11703    buffer_3.update(cx, |buffer, _| {
11704        assert!(!buffer.is_dirty());
11705        assert_eq!(buffer.text(), sample_text_3,)
11706    });
11707}
11708
11709#[gpui::test]
11710async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11711    init_test(cx, |_| {});
11712
11713    let fs = FakeFs::new(cx.executor());
11714    fs.insert_tree(
11715        path!("/dir"),
11716        json!({
11717            "file1.rs": "fn main() { println!(\"hello\"); }",
11718            "file2.rs": "fn test() { println!(\"test\"); }",
11719            "file3.rs": "fn other() { println!(\"other\"); }\n",
11720        }),
11721    )
11722    .await;
11723
11724    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11725    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11726    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11727
11728    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11729    language_registry.add(rust_lang());
11730
11731    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11732    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11733
11734    // Open three buffers
11735    let buffer_1 = project
11736        .update(cx, |project, cx| {
11737            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11738        })
11739        .await
11740        .unwrap();
11741    let buffer_2 = project
11742        .update(cx, |project, cx| {
11743            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11744        })
11745        .await
11746        .unwrap();
11747    let buffer_3 = project
11748        .update(cx, |project, cx| {
11749            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11750        })
11751        .await
11752        .unwrap();
11753
11754    // Create a multi-buffer with all three buffers
11755    let multi_buffer = cx.new(|cx| {
11756        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11757        multi_buffer.push_excerpts(
11758            buffer_1.clone(),
11759            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11760            cx,
11761        );
11762        multi_buffer.push_excerpts(
11763            buffer_2.clone(),
11764            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11765            cx,
11766        );
11767        multi_buffer.push_excerpts(
11768            buffer_3.clone(),
11769            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11770            cx,
11771        );
11772        multi_buffer
11773    });
11774
11775    let editor = cx.new_window_entity(|window, cx| {
11776        Editor::new(
11777            EditorMode::full(),
11778            multi_buffer,
11779            Some(project.clone()),
11780            window,
11781            cx,
11782        )
11783    });
11784
11785    // Edit only the first buffer
11786    editor.update_in(cx, |editor, window, cx| {
11787        editor.change_selections(
11788            SelectionEffects::scroll(Autoscroll::Next),
11789            window,
11790            cx,
11791            |s| s.select_ranges(Some(10..10)),
11792        );
11793        editor.insert("// edited", window, cx);
11794    });
11795
11796    // Verify that only buffer 1 is dirty
11797    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11798    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11799    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11800
11801    // Get write counts after file creation (files were created with initial content)
11802    // We expect each file to have been written once during creation
11803    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11804    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11805    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11806
11807    // Perform autosave
11808    let save_task = editor.update_in(cx, |editor, window, cx| {
11809        editor.save(
11810            SaveOptions {
11811                format: true,
11812                autosave: true,
11813            },
11814            project.clone(),
11815            window,
11816            cx,
11817        )
11818    });
11819    save_task.await.unwrap();
11820
11821    // Only the dirty buffer should have been saved
11822    assert_eq!(
11823        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11824        1,
11825        "Buffer 1 was dirty, so it should have been written once during autosave"
11826    );
11827    assert_eq!(
11828        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11829        0,
11830        "Buffer 2 was clean, so it should not have been written during autosave"
11831    );
11832    assert_eq!(
11833        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11834        0,
11835        "Buffer 3 was clean, so it should not have been written during autosave"
11836    );
11837
11838    // Verify buffer states after autosave
11839    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11840    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11841    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11842
11843    // Now perform a manual save (format = true)
11844    let save_task = editor.update_in(cx, |editor, window, cx| {
11845        editor.save(
11846            SaveOptions {
11847                format: true,
11848                autosave: false,
11849            },
11850            project.clone(),
11851            window,
11852            cx,
11853        )
11854    });
11855    save_task.await.unwrap();
11856
11857    // During manual save, clean buffers don't get written to disk
11858    // They just get did_save called for language server notifications
11859    assert_eq!(
11860        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11861        1,
11862        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11863    );
11864    assert_eq!(
11865        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11866        0,
11867        "Buffer 2 should not have been written at all"
11868    );
11869    assert_eq!(
11870        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11871        0,
11872        "Buffer 3 should not have been written at all"
11873    );
11874}
11875
11876async fn setup_range_format_test(
11877    cx: &mut TestAppContext,
11878) -> (
11879    Entity<Project>,
11880    Entity<Editor>,
11881    &mut gpui::VisualTestContext,
11882    lsp::FakeLanguageServer,
11883) {
11884    init_test(cx, |_| {});
11885
11886    let fs = FakeFs::new(cx.executor());
11887    fs.insert_file(path!("/file.rs"), Default::default()).await;
11888
11889    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11890
11891    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11892    language_registry.add(rust_lang());
11893    let mut fake_servers = language_registry.register_fake_lsp(
11894        "Rust",
11895        FakeLspAdapter {
11896            capabilities: lsp::ServerCapabilities {
11897                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11898                ..lsp::ServerCapabilities::default()
11899            },
11900            ..FakeLspAdapter::default()
11901        },
11902    );
11903
11904    let buffer = project
11905        .update(cx, |project, cx| {
11906            project.open_local_buffer(path!("/file.rs"), cx)
11907        })
11908        .await
11909        .unwrap();
11910
11911    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11912    let (editor, cx) = cx.add_window_view(|window, cx| {
11913        build_editor_with_project(project.clone(), buffer, window, cx)
11914    });
11915
11916    cx.executor().start_waiting();
11917    let fake_server = fake_servers.next().await.unwrap();
11918
11919    (project, editor, cx, fake_server)
11920}
11921
11922#[gpui::test]
11923async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11924    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11925
11926    editor.update_in(cx, |editor, window, cx| {
11927        editor.set_text("one\ntwo\nthree\n", window, cx)
11928    });
11929    assert!(cx.read(|cx| editor.is_dirty(cx)));
11930
11931    let save = editor
11932        .update_in(cx, |editor, window, cx| {
11933            editor.save(
11934                SaveOptions {
11935                    format: true,
11936                    autosave: false,
11937                },
11938                project.clone(),
11939                window,
11940                cx,
11941            )
11942        })
11943        .unwrap();
11944    fake_server
11945        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11946            assert_eq!(
11947                params.text_document.uri,
11948                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11949            );
11950            assert_eq!(params.options.tab_size, 4);
11951            Ok(Some(vec![lsp::TextEdit::new(
11952                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11953                ", ".to_string(),
11954            )]))
11955        })
11956        .next()
11957        .await;
11958    cx.executor().start_waiting();
11959    save.await;
11960    assert_eq!(
11961        editor.update(cx, |editor, cx| editor.text(cx)),
11962        "one, two\nthree\n"
11963    );
11964    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11965}
11966
11967#[gpui::test]
11968async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11969    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11970
11971    editor.update_in(cx, |editor, window, cx| {
11972        editor.set_text("one\ntwo\nthree\n", window, cx)
11973    });
11974    assert!(cx.read(|cx| editor.is_dirty(cx)));
11975
11976    // Test that save still works when formatting hangs
11977    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11978        move |params, _| async move {
11979            assert_eq!(
11980                params.text_document.uri,
11981                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11982            );
11983            futures::future::pending::<()>().await;
11984            unreachable!()
11985        },
11986    );
11987    let save = editor
11988        .update_in(cx, |editor, window, cx| {
11989            editor.save(
11990                SaveOptions {
11991                    format: true,
11992                    autosave: false,
11993                },
11994                project.clone(),
11995                window,
11996                cx,
11997            )
11998        })
11999        .unwrap();
12000    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12001    cx.executor().start_waiting();
12002    save.await;
12003    assert_eq!(
12004        editor.update(cx, |editor, cx| editor.text(cx)),
12005        "one\ntwo\nthree\n"
12006    );
12007    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12008}
12009
12010#[gpui::test]
12011async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12012    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12013
12014    // Buffer starts clean, no formatting should be requested
12015    let save = editor
12016        .update_in(cx, |editor, window, cx| {
12017            editor.save(
12018                SaveOptions {
12019                    format: false,
12020                    autosave: false,
12021                },
12022                project.clone(),
12023                window,
12024                cx,
12025            )
12026        })
12027        .unwrap();
12028    let _pending_format_request = fake_server
12029        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12030            panic!("Should not be invoked");
12031        })
12032        .next();
12033    cx.executor().start_waiting();
12034    save.await;
12035    cx.run_until_parked();
12036}
12037
12038#[gpui::test]
12039async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12040    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12041
12042    // Set Rust language override and assert overridden tabsize is sent to language server
12043    update_test_language_settings(cx, |settings| {
12044        settings.languages.0.insert(
12045            "Rust".into(),
12046            LanguageSettingsContent {
12047                tab_size: NonZeroU32::new(8),
12048                ..Default::default()
12049            },
12050        );
12051    });
12052
12053    editor.update_in(cx, |editor, window, cx| {
12054        editor.set_text("something_new\n", window, cx)
12055    });
12056    assert!(cx.read(|cx| editor.is_dirty(cx)));
12057    let save = editor
12058        .update_in(cx, |editor, window, cx| {
12059            editor.save(
12060                SaveOptions {
12061                    format: true,
12062                    autosave: false,
12063                },
12064                project.clone(),
12065                window,
12066                cx,
12067            )
12068        })
12069        .unwrap();
12070    fake_server
12071        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12072            assert_eq!(
12073                params.text_document.uri,
12074                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12075            );
12076            assert_eq!(params.options.tab_size, 8);
12077            Ok(Some(Vec::new()))
12078        })
12079        .next()
12080        .await;
12081    save.await;
12082}
12083
12084#[gpui::test]
12085async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12086    init_test(cx, |settings| {
12087        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12088            settings::LanguageServerFormatterSpecifier::Current,
12089        )))
12090    });
12091
12092    let fs = FakeFs::new(cx.executor());
12093    fs.insert_file(path!("/file.rs"), Default::default()).await;
12094
12095    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12096
12097    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12098    language_registry.add(Arc::new(Language::new(
12099        LanguageConfig {
12100            name: "Rust".into(),
12101            matcher: LanguageMatcher {
12102                path_suffixes: vec!["rs".to_string()],
12103                ..Default::default()
12104            },
12105            ..LanguageConfig::default()
12106        },
12107        Some(tree_sitter_rust::LANGUAGE.into()),
12108    )));
12109    update_test_language_settings(cx, |settings| {
12110        // Enable Prettier formatting for the same buffer, and ensure
12111        // LSP is called instead of Prettier.
12112        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12113    });
12114    let mut fake_servers = language_registry.register_fake_lsp(
12115        "Rust",
12116        FakeLspAdapter {
12117            capabilities: lsp::ServerCapabilities {
12118                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12119                ..Default::default()
12120            },
12121            ..Default::default()
12122        },
12123    );
12124
12125    let buffer = project
12126        .update(cx, |project, cx| {
12127            project.open_local_buffer(path!("/file.rs"), cx)
12128        })
12129        .await
12130        .unwrap();
12131
12132    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12133    let (editor, cx) = cx.add_window_view(|window, cx| {
12134        build_editor_with_project(project.clone(), buffer, window, cx)
12135    });
12136    editor.update_in(cx, |editor, window, cx| {
12137        editor.set_text("one\ntwo\nthree\n", window, cx)
12138    });
12139
12140    cx.executor().start_waiting();
12141    let fake_server = fake_servers.next().await.unwrap();
12142
12143    let format = editor
12144        .update_in(cx, |editor, window, cx| {
12145            editor.perform_format(
12146                project.clone(),
12147                FormatTrigger::Manual,
12148                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12149                window,
12150                cx,
12151            )
12152        })
12153        .unwrap();
12154    fake_server
12155        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12156            assert_eq!(
12157                params.text_document.uri,
12158                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12159            );
12160            assert_eq!(params.options.tab_size, 4);
12161            Ok(Some(vec![lsp::TextEdit::new(
12162                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12163                ", ".to_string(),
12164            )]))
12165        })
12166        .next()
12167        .await;
12168    cx.executor().start_waiting();
12169    format.await;
12170    assert_eq!(
12171        editor.update(cx, |editor, cx| editor.text(cx)),
12172        "one, two\nthree\n"
12173    );
12174
12175    editor.update_in(cx, |editor, window, cx| {
12176        editor.set_text("one\ntwo\nthree\n", window, cx)
12177    });
12178    // Ensure we don't lock if formatting hangs.
12179    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12180        move |params, _| async move {
12181            assert_eq!(
12182                params.text_document.uri,
12183                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12184            );
12185            futures::future::pending::<()>().await;
12186            unreachable!()
12187        },
12188    );
12189    let format = editor
12190        .update_in(cx, |editor, window, cx| {
12191            editor.perform_format(
12192                project,
12193                FormatTrigger::Manual,
12194                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12195                window,
12196                cx,
12197            )
12198        })
12199        .unwrap();
12200    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12201    cx.executor().start_waiting();
12202    format.await;
12203    assert_eq!(
12204        editor.update(cx, |editor, cx| editor.text(cx)),
12205        "one\ntwo\nthree\n"
12206    );
12207}
12208
12209#[gpui::test]
12210async fn test_multiple_formatters(cx: &mut TestAppContext) {
12211    init_test(cx, |settings| {
12212        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12213        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12214            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12215            Formatter::CodeAction("code-action-1".into()),
12216            Formatter::CodeAction("code-action-2".into()),
12217        ]))
12218    });
12219
12220    let fs = FakeFs::new(cx.executor());
12221    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12222        .await;
12223
12224    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12225    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12226    language_registry.add(rust_lang());
12227
12228    let mut fake_servers = language_registry.register_fake_lsp(
12229        "Rust",
12230        FakeLspAdapter {
12231            capabilities: lsp::ServerCapabilities {
12232                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12233                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12234                    commands: vec!["the-command-for-code-action-1".into()],
12235                    ..Default::default()
12236                }),
12237                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12238                ..Default::default()
12239            },
12240            ..Default::default()
12241        },
12242    );
12243
12244    let buffer = project
12245        .update(cx, |project, cx| {
12246            project.open_local_buffer(path!("/file.rs"), cx)
12247        })
12248        .await
12249        .unwrap();
12250
12251    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12252    let (editor, cx) = cx.add_window_view(|window, cx| {
12253        build_editor_with_project(project.clone(), buffer, window, cx)
12254    });
12255
12256    cx.executor().start_waiting();
12257
12258    let fake_server = fake_servers.next().await.unwrap();
12259    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12260        move |_params, _| async move {
12261            Ok(Some(vec![lsp::TextEdit::new(
12262                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12263                "applied-formatting\n".to_string(),
12264            )]))
12265        },
12266    );
12267    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12268        move |params, _| async move {
12269            let requested_code_actions = params.context.only.expect("Expected code action request");
12270            assert_eq!(requested_code_actions.len(), 1);
12271
12272            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12273            let code_action = match requested_code_actions[0].as_str() {
12274                "code-action-1" => lsp::CodeAction {
12275                    kind: Some("code-action-1".into()),
12276                    edit: Some(lsp::WorkspaceEdit::new(
12277                        [(
12278                            uri,
12279                            vec![lsp::TextEdit::new(
12280                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12281                                "applied-code-action-1-edit\n".to_string(),
12282                            )],
12283                        )]
12284                        .into_iter()
12285                        .collect(),
12286                    )),
12287                    command: Some(lsp::Command {
12288                        command: "the-command-for-code-action-1".into(),
12289                        ..Default::default()
12290                    }),
12291                    ..Default::default()
12292                },
12293                "code-action-2" => lsp::CodeAction {
12294                    kind: Some("code-action-2".into()),
12295                    edit: Some(lsp::WorkspaceEdit::new(
12296                        [(
12297                            uri,
12298                            vec![lsp::TextEdit::new(
12299                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12300                                "applied-code-action-2-edit\n".to_string(),
12301                            )],
12302                        )]
12303                        .into_iter()
12304                        .collect(),
12305                    )),
12306                    ..Default::default()
12307                },
12308                req => panic!("Unexpected code action request: {:?}", req),
12309            };
12310            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12311                code_action,
12312            )]))
12313        },
12314    );
12315
12316    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12317        move |params, _| async move { Ok(params) }
12318    });
12319
12320    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12321    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12322        let fake = fake_server.clone();
12323        let lock = command_lock.clone();
12324        move |params, _| {
12325            assert_eq!(params.command, "the-command-for-code-action-1");
12326            let fake = fake.clone();
12327            let lock = lock.clone();
12328            async move {
12329                lock.lock().await;
12330                fake.server
12331                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12332                        label: None,
12333                        edit: lsp::WorkspaceEdit {
12334                            changes: Some(
12335                                [(
12336                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12337                                    vec![lsp::TextEdit {
12338                                        range: lsp::Range::new(
12339                                            lsp::Position::new(0, 0),
12340                                            lsp::Position::new(0, 0),
12341                                        ),
12342                                        new_text: "applied-code-action-1-command\n".into(),
12343                                    }],
12344                                )]
12345                                .into_iter()
12346                                .collect(),
12347                            ),
12348                            ..Default::default()
12349                        },
12350                    })
12351                    .await
12352                    .into_response()
12353                    .unwrap();
12354                Ok(Some(json!(null)))
12355            }
12356        }
12357    });
12358
12359    cx.executor().start_waiting();
12360    editor
12361        .update_in(cx, |editor, window, cx| {
12362            editor.perform_format(
12363                project.clone(),
12364                FormatTrigger::Manual,
12365                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12366                window,
12367                cx,
12368            )
12369        })
12370        .unwrap()
12371        .await;
12372    editor.update(cx, |editor, cx| {
12373        assert_eq!(
12374            editor.text(cx),
12375            r#"
12376                applied-code-action-2-edit
12377                applied-code-action-1-command
12378                applied-code-action-1-edit
12379                applied-formatting
12380                one
12381                two
12382                three
12383            "#
12384            .unindent()
12385        );
12386    });
12387
12388    editor.update_in(cx, |editor, window, cx| {
12389        editor.undo(&Default::default(), window, cx);
12390        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12391    });
12392
12393    // Perform a manual edit while waiting for an LSP command
12394    // that's being run as part of a formatting code action.
12395    let lock_guard = command_lock.lock().await;
12396    let format = editor
12397        .update_in(cx, |editor, window, cx| {
12398            editor.perform_format(
12399                project.clone(),
12400                FormatTrigger::Manual,
12401                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12402                window,
12403                cx,
12404            )
12405        })
12406        .unwrap();
12407    cx.run_until_parked();
12408    editor.update(cx, |editor, cx| {
12409        assert_eq!(
12410            editor.text(cx),
12411            r#"
12412                applied-code-action-1-edit
12413                applied-formatting
12414                one
12415                two
12416                three
12417            "#
12418            .unindent()
12419        );
12420
12421        editor.buffer.update(cx, |buffer, cx| {
12422            let ix = buffer.len(cx);
12423            buffer.edit([(ix..ix, "edited\n")], None, cx);
12424        });
12425    });
12426
12427    // Allow the LSP command to proceed. Because the buffer was edited,
12428    // the second code action will not be run.
12429    drop(lock_guard);
12430    format.await;
12431    editor.update_in(cx, |editor, window, cx| {
12432        assert_eq!(
12433            editor.text(cx),
12434            r#"
12435                applied-code-action-1-command
12436                applied-code-action-1-edit
12437                applied-formatting
12438                one
12439                two
12440                three
12441                edited
12442            "#
12443            .unindent()
12444        );
12445
12446        // The manual edit is undone first, because it is the last thing the user did
12447        // (even though the command completed afterwards).
12448        editor.undo(&Default::default(), window, cx);
12449        assert_eq!(
12450            editor.text(cx),
12451            r#"
12452                applied-code-action-1-command
12453                applied-code-action-1-edit
12454                applied-formatting
12455                one
12456                two
12457                three
12458            "#
12459            .unindent()
12460        );
12461
12462        // All the formatting (including the command, which completed after the manual edit)
12463        // is undone together.
12464        editor.undo(&Default::default(), window, cx);
12465        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12466    });
12467}
12468
12469#[gpui::test]
12470async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12471    init_test(cx, |settings| {
12472        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12473            settings::LanguageServerFormatterSpecifier::Current,
12474        )]))
12475    });
12476
12477    let fs = FakeFs::new(cx.executor());
12478    fs.insert_file(path!("/file.ts"), Default::default()).await;
12479
12480    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12481
12482    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12483    language_registry.add(Arc::new(Language::new(
12484        LanguageConfig {
12485            name: "TypeScript".into(),
12486            matcher: LanguageMatcher {
12487                path_suffixes: vec!["ts".to_string()],
12488                ..Default::default()
12489            },
12490            ..LanguageConfig::default()
12491        },
12492        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12493    )));
12494    update_test_language_settings(cx, |settings| {
12495        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12496    });
12497    let mut fake_servers = language_registry.register_fake_lsp(
12498        "TypeScript",
12499        FakeLspAdapter {
12500            capabilities: lsp::ServerCapabilities {
12501                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12502                ..Default::default()
12503            },
12504            ..Default::default()
12505        },
12506    );
12507
12508    let buffer = project
12509        .update(cx, |project, cx| {
12510            project.open_local_buffer(path!("/file.ts"), cx)
12511        })
12512        .await
12513        .unwrap();
12514
12515    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12516    let (editor, cx) = cx.add_window_view(|window, cx| {
12517        build_editor_with_project(project.clone(), buffer, window, cx)
12518    });
12519    editor.update_in(cx, |editor, window, cx| {
12520        editor.set_text(
12521            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12522            window,
12523            cx,
12524        )
12525    });
12526
12527    cx.executor().start_waiting();
12528    let fake_server = fake_servers.next().await.unwrap();
12529
12530    let format = editor
12531        .update_in(cx, |editor, window, cx| {
12532            editor.perform_code_action_kind(
12533                project.clone(),
12534                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12535                window,
12536                cx,
12537            )
12538        })
12539        .unwrap();
12540    fake_server
12541        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12542            assert_eq!(
12543                params.text_document.uri,
12544                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12545            );
12546            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12547                lsp::CodeAction {
12548                    title: "Organize Imports".to_string(),
12549                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12550                    edit: Some(lsp::WorkspaceEdit {
12551                        changes: Some(
12552                            [(
12553                                params.text_document.uri.clone(),
12554                                vec![lsp::TextEdit::new(
12555                                    lsp::Range::new(
12556                                        lsp::Position::new(1, 0),
12557                                        lsp::Position::new(2, 0),
12558                                    ),
12559                                    "".to_string(),
12560                                )],
12561                            )]
12562                            .into_iter()
12563                            .collect(),
12564                        ),
12565                        ..Default::default()
12566                    }),
12567                    ..Default::default()
12568                },
12569            )]))
12570        })
12571        .next()
12572        .await;
12573    cx.executor().start_waiting();
12574    format.await;
12575    assert_eq!(
12576        editor.update(cx, |editor, cx| editor.text(cx)),
12577        "import { a } from 'module';\n\nconst x = a;\n"
12578    );
12579
12580    editor.update_in(cx, |editor, window, cx| {
12581        editor.set_text(
12582            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12583            window,
12584            cx,
12585        )
12586    });
12587    // Ensure we don't lock if code action hangs.
12588    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12589        move |params, _| async move {
12590            assert_eq!(
12591                params.text_document.uri,
12592                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12593            );
12594            futures::future::pending::<()>().await;
12595            unreachable!()
12596        },
12597    );
12598    let format = editor
12599        .update_in(cx, |editor, window, cx| {
12600            editor.perform_code_action_kind(
12601                project,
12602                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12603                window,
12604                cx,
12605            )
12606        })
12607        .unwrap();
12608    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12609    cx.executor().start_waiting();
12610    format.await;
12611    assert_eq!(
12612        editor.update(cx, |editor, cx| editor.text(cx)),
12613        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12614    );
12615}
12616
12617#[gpui::test]
12618async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12619    init_test(cx, |_| {});
12620
12621    let mut cx = EditorLspTestContext::new_rust(
12622        lsp::ServerCapabilities {
12623            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12624            ..Default::default()
12625        },
12626        cx,
12627    )
12628    .await;
12629
12630    cx.set_state(indoc! {"
12631        one.twoˇ
12632    "});
12633
12634    // The format request takes a long time. When it completes, it inserts
12635    // a newline and an indent before the `.`
12636    cx.lsp
12637        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12638            let executor = cx.background_executor().clone();
12639            async move {
12640                executor.timer(Duration::from_millis(100)).await;
12641                Ok(Some(vec![lsp::TextEdit {
12642                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12643                    new_text: "\n    ".into(),
12644                }]))
12645            }
12646        });
12647
12648    // Submit a format request.
12649    let format_1 = cx
12650        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12651        .unwrap();
12652    cx.executor().run_until_parked();
12653
12654    // Submit a second format request.
12655    let format_2 = cx
12656        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12657        .unwrap();
12658    cx.executor().run_until_parked();
12659
12660    // Wait for both format requests to complete
12661    cx.executor().advance_clock(Duration::from_millis(200));
12662    cx.executor().start_waiting();
12663    format_1.await.unwrap();
12664    cx.executor().start_waiting();
12665    format_2.await.unwrap();
12666
12667    // The formatting edits only happens once.
12668    cx.assert_editor_state(indoc! {"
12669        one
12670            .twoˇ
12671    "});
12672}
12673
12674#[gpui::test]
12675async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12676    init_test(cx, |settings| {
12677        settings.defaults.formatter = Some(FormatterList::default())
12678    });
12679
12680    let mut cx = EditorLspTestContext::new_rust(
12681        lsp::ServerCapabilities {
12682            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12683            ..Default::default()
12684        },
12685        cx,
12686    )
12687    .await;
12688
12689    // Record which buffer changes have been sent to the language server
12690    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12691    cx.lsp
12692        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12693            let buffer_changes = buffer_changes.clone();
12694            move |params, _| {
12695                buffer_changes.lock().extend(
12696                    params
12697                        .content_changes
12698                        .into_iter()
12699                        .map(|e| (e.range.unwrap(), e.text)),
12700                );
12701            }
12702        });
12703    // Handle formatting requests to the language server.
12704    cx.lsp
12705        .set_request_handler::<lsp::request::Formatting, _, _>({
12706            let buffer_changes = buffer_changes.clone();
12707            move |_, _| {
12708                let buffer_changes = buffer_changes.clone();
12709                // Insert blank lines between each line of the buffer.
12710                async move {
12711                    // When formatting is requested, trailing whitespace has already been stripped,
12712                    // and the trailing newline has already been added.
12713                    assert_eq!(
12714                        &buffer_changes.lock()[1..],
12715                        &[
12716                            (
12717                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12718                                "".into()
12719                            ),
12720                            (
12721                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12722                                "".into()
12723                            ),
12724                            (
12725                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12726                                "\n".into()
12727                            ),
12728                        ]
12729                    );
12730
12731                    Ok(Some(vec![
12732                        lsp::TextEdit {
12733                            range: lsp::Range::new(
12734                                lsp::Position::new(1, 0),
12735                                lsp::Position::new(1, 0),
12736                            ),
12737                            new_text: "\n".into(),
12738                        },
12739                        lsp::TextEdit {
12740                            range: lsp::Range::new(
12741                                lsp::Position::new(2, 0),
12742                                lsp::Position::new(2, 0),
12743                            ),
12744                            new_text: "\n".into(),
12745                        },
12746                    ]))
12747                }
12748            }
12749        });
12750
12751    // Set up a buffer white some trailing whitespace and no trailing newline.
12752    cx.set_state(
12753        &[
12754            "one ",   //
12755            "twoˇ",   //
12756            "three ", //
12757            "four",   //
12758        ]
12759        .join("\n"),
12760    );
12761    cx.run_until_parked();
12762
12763    // Submit a format request.
12764    let format = cx
12765        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12766        .unwrap();
12767
12768    cx.run_until_parked();
12769    // After formatting the buffer, the trailing whitespace is stripped,
12770    // a newline is appended, and the edits provided by the language server
12771    // have been applied.
12772    format.await.unwrap();
12773
12774    cx.assert_editor_state(
12775        &[
12776            "one",   //
12777            "",      //
12778            "twoˇ",  //
12779            "",      //
12780            "three", //
12781            "four",  //
12782            "",      //
12783        ]
12784        .join("\n"),
12785    );
12786
12787    // Undoing the formatting undoes the trailing whitespace removal, the
12788    // trailing newline, and the LSP edits.
12789    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12790    cx.assert_editor_state(
12791        &[
12792            "one ",   //
12793            "twoˇ",   //
12794            "three ", //
12795            "four",   //
12796        ]
12797        .join("\n"),
12798    );
12799}
12800
12801#[gpui::test]
12802async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12803    cx: &mut TestAppContext,
12804) {
12805    init_test(cx, |_| {});
12806
12807    cx.update(|cx| {
12808        cx.update_global::<SettingsStore, _>(|settings, cx| {
12809            settings.update_user_settings(cx, |settings| {
12810                settings.editor.auto_signature_help = Some(true);
12811            });
12812        });
12813    });
12814
12815    let mut cx = EditorLspTestContext::new_rust(
12816        lsp::ServerCapabilities {
12817            signature_help_provider: Some(lsp::SignatureHelpOptions {
12818                ..Default::default()
12819            }),
12820            ..Default::default()
12821        },
12822        cx,
12823    )
12824    .await;
12825
12826    let language = Language::new(
12827        LanguageConfig {
12828            name: "Rust".into(),
12829            brackets: BracketPairConfig {
12830                pairs: vec![
12831                    BracketPair {
12832                        start: "{".to_string(),
12833                        end: "}".to_string(),
12834                        close: true,
12835                        surround: true,
12836                        newline: true,
12837                    },
12838                    BracketPair {
12839                        start: "(".to_string(),
12840                        end: ")".to_string(),
12841                        close: true,
12842                        surround: true,
12843                        newline: true,
12844                    },
12845                    BracketPair {
12846                        start: "/*".to_string(),
12847                        end: " */".to_string(),
12848                        close: true,
12849                        surround: true,
12850                        newline: true,
12851                    },
12852                    BracketPair {
12853                        start: "[".to_string(),
12854                        end: "]".to_string(),
12855                        close: false,
12856                        surround: false,
12857                        newline: true,
12858                    },
12859                    BracketPair {
12860                        start: "\"".to_string(),
12861                        end: "\"".to_string(),
12862                        close: true,
12863                        surround: true,
12864                        newline: false,
12865                    },
12866                    BracketPair {
12867                        start: "<".to_string(),
12868                        end: ">".to_string(),
12869                        close: false,
12870                        surround: true,
12871                        newline: true,
12872                    },
12873                ],
12874                ..Default::default()
12875            },
12876            autoclose_before: "})]".to_string(),
12877            ..Default::default()
12878        },
12879        Some(tree_sitter_rust::LANGUAGE.into()),
12880    );
12881    let language = Arc::new(language);
12882
12883    cx.language_registry().add(language.clone());
12884    cx.update_buffer(|buffer, cx| {
12885        buffer.set_language(Some(language), cx);
12886    });
12887
12888    cx.set_state(
12889        &r#"
12890            fn main() {
12891                sampleˇ
12892            }
12893        "#
12894        .unindent(),
12895    );
12896
12897    cx.update_editor(|editor, window, cx| {
12898        editor.handle_input("(", window, cx);
12899    });
12900    cx.assert_editor_state(
12901        &"
12902            fn main() {
12903                sample(ˇ)
12904            }
12905        "
12906        .unindent(),
12907    );
12908
12909    let mocked_response = lsp::SignatureHelp {
12910        signatures: vec![lsp::SignatureInformation {
12911            label: "fn sample(param1: u8, param2: u8)".to_string(),
12912            documentation: None,
12913            parameters: Some(vec![
12914                lsp::ParameterInformation {
12915                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12916                    documentation: None,
12917                },
12918                lsp::ParameterInformation {
12919                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12920                    documentation: None,
12921                },
12922            ]),
12923            active_parameter: None,
12924        }],
12925        active_signature: Some(0),
12926        active_parameter: Some(0),
12927    };
12928    handle_signature_help_request(&mut cx, mocked_response).await;
12929
12930    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12931        .await;
12932
12933    cx.editor(|editor, _, _| {
12934        let signature_help_state = editor.signature_help_state.popover().cloned();
12935        let signature = signature_help_state.unwrap();
12936        assert_eq!(
12937            signature.signatures[signature.current_signature].label,
12938            "fn sample(param1: u8, param2: u8)"
12939        );
12940    });
12941}
12942
12943#[gpui::test]
12944async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12945    init_test(cx, |_| {});
12946
12947    cx.update(|cx| {
12948        cx.update_global::<SettingsStore, _>(|settings, cx| {
12949            settings.update_user_settings(cx, |settings| {
12950                settings.editor.auto_signature_help = Some(false);
12951                settings.editor.show_signature_help_after_edits = Some(false);
12952            });
12953        });
12954    });
12955
12956    let mut cx = EditorLspTestContext::new_rust(
12957        lsp::ServerCapabilities {
12958            signature_help_provider: Some(lsp::SignatureHelpOptions {
12959                ..Default::default()
12960            }),
12961            ..Default::default()
12962        },
12963        cx,
12964    )
12965    .await;
12966
12967    let language = Language::new(
12968        LanguageConfig {
12969            name: "Rust".into(),
12970            brackets: BracketPairConfig {
12971                pairs: vec![
12972                    BracketPair {
12973                        start: "{".to_string(),
12974                        end: "}".to_string(),
12975                        close: true,
12976                        surround: true,
12977                        newline: true,
12978                    },
12979                    BracketPair {
12980                        start: "(".to_string(),
12981                        end: ")".to_string(),
12982                        close: true,
12983                        surround: true,
12984                        newline: true,
12985                    },
12986                    BracketPair {
12987                        start: "/*".to_string(),
12988                        end: " */".to_string(),
12989                        close: true,
12990                        surround: true,
12991                        newline: true,
12992                    },
12993                    BracketPair {
12994                        start: "[".to_string(),
12995                        end: "]".to_string(),
12996                        close: false,
12997                        surround: false,
12998                        newline: true,
12999                    },
13000                    BracketPair {
13001                        start: "\"".to_string(),
13002                        end: "\"".to_string(),
13003                        close: true,
13004                        surround: true,
13005                        newline: false,
13006                    },
13007                    BracketPair {
13008                        start: "<".to_string(),
13009                        end: ">".to_string(),
13010                        close: false,
13011                        surround: true,
13012                        newline: true,
13013                    },
13014                ],
13015                ..Default::default()
13016            },
13017            autoclose_before: "})]".to_string(),
13018            ..Default::default()
13019        },
13020        Some(tree_sitter_rust::LANGUAGE.into()),
13021    );
13022    let language = Arc::new(language);
13023
13024    cx.language_registry().add(language.clone());
13025    cx.update_buffer(|buffer, cx| {
13026        buffer.set_language(Some(language), cx);
13027    });
13028
13029    // Ensure that signature_help is not called when no signature help is enabled.
13030    cx.set_state(
13031        &r#"
13032            fn main() {
13033                sampleˇ
13034            }
13035        "#
13036        .unindent(),
13037    );
13038    cx.update_editor(|editor, window, cx| {
13039        editor.handle_input("(", window, cx);
13040    });
13041    cx.assert_editor_state(
13042        &"
13043            fn main() {
13044                sample(ˇ)
13045            }
13046        "
13047        .unindent(),
13048    );
13049    cx.editor(|editor, _, _| {
13050        assert!(editor.signature_help_state.task().is_none());
13051    });
13052
13053    let mocked_response = lsp::SignatureHelp {
13054        signatures: vec![lsp::SignatureInformation {
13055            label: "fn sample(param1: u8, param2: u8)".to_string(),
13056            documentation: None,
13057            parameters: Some(vec![
13058                lsp::ParameterInformation {
13059                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13060                    documentation: None,
13061                },
13062                lsp::ParameterInformation {
13063                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13064                    documentation: None,
13065                },
13066            ]),
13067            active_parameter: None,
13068        }],
13069        active_signature: Some(0),
13070        active_parameter: Some(0),
13071    };
13072
13073    // Ensure that signature_help is called when enabled afte edits
13074    cx.update(|_, cx| {
13075        cx.update_global::<SettingsStore, _>(|settings, cx| {
13076            settings.update_user_settings(cx, |settings| {
13077                settings.editor.auto_signature_help = Some(false);
13078                settings.editor.show_signature_help_after_edits = Some(true);
13079            });
13080        });
13081    });
13082    cx.set_state(
13083        &r#"
13084            fn main() {
13085                sampleˇ
13086            }
13087        "#
13088        .unindent(),
13089    );
13090    cx.update_editor(|editor, window, cx| {
13091        editor.handle_input("(", window, cx);
13092    });
13093    cx.assert_editor_state(
13094        &"
13095            fn main() {
13096                sample(ˇ)
13097            }
13098        "
13099        .unindent(),
13100    );
13101    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13102    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13103        .await;
13104    cx.update_editor(|editor, _, _| {
13105        let signature_help_state = editor.signature_help_state.popover().cloned();
13106        assert!(signature_help_state.is_some());
13107        let signature = signature_help_state.unwrap();
13108        assert_eq!(
13109            signature.signatures[signature.current_signature].label,
13110            "fn sample(param1: u8, param2: u8)"
13111        );
13112        editor.signature_help_state = SignatureHelpState::default();
13113    });
13114
13115    // Ensure that signature_help is called when auto signature help override is enabled
13116    cx.update(|_, cx| {
13117        cx.update_global::<SettingsStore, _>(|settings, cx| {
13118            settings.update_user_settings(cx, |settings| {
13119                settings.editor.auto_signature_help = Some(true);
13120                settings.editor.show_signature_help_after_edits = Some(false);
13121            });
13122        });
13123    });
13124    cx.set_state(
13125        &r#"
13126            fn main() {
13127                sampleˇ
13128            }
13129        "#
13130        .unindent(),
13131    );
13132    cx.update_editor(|editor, window, cx| {
13133        editor.handle_input("(", window, cx);
13134    });
13135    cx.assert_editor_state(
13136        &"
13137            fn main() {
13138                sample(ˇ)
13139            }
13140        "
13141        .unindent(),
13142    );
13143    handle_signature_help_request(&mut cx, mocked_response).await;
13144    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13145        .await;
13146    cx.editor(|editor, _, _| {
13147        let signature_help_state = editor.signature_help_state.popover().cloned();
13148        assert!(signature_help_state.is_some());
13149        let signature = signature_help_state.unwrap();
13150        assert_eq!(
13151            signature.signatures[signature.current_signature].label,
13152            "fn sample(param1: u8, param2: u8)"
13153        );
13154    });
13155}
13156
13157#[gpui::test]
13158async fn test_signature_help(cx: &mut TestAppContext) {
13159    init_test(cx, |_| {});
13160    cx.update(|cx| {
13161        cx.update_global::<SettingsStore, _>(|settings, cx| {
13162            settings.update_user_settings(cx, |settings| {
13163                settings.editor.auto_signature_help = Some(true);
13164            });
13165        });
13166    });
13167
13168    let mut cx = EditorLspTestContext::new_rust(
13169        lsp::ServerCapabilities {
13170            signature_help_provider: Some(lsp::SignatureHelpOptions {
13171                ..Default::default()
13172            }),
13173            ..Default::default()
13174        },
13175        cx,
13176    )
13177    .await;
13178
13179    // A test that directly calls `show_signature_help`
13180    cx.update_editor(|editor, window, cx| {
13181        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13182    });
13183
13184    let mocked_response = lsp::SignatureHelp {
13185        signatures: vec![lsp::SignatureInformation {
13186            label: "fn sample(param1: u8, param2: u8)".to_string(),
13187            documentation: None,
13188            parameters: Some(vec![
13189                lsp::ParameterInformation {
13190                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13191                    documentation: None,
13192                },
13193                lsp::ParameterInformation {
13194                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13195                    documentation: None,
13196                },
13197            ]),
13198            active_parameter: None,
13199        }],
13200        active_signature: Some(0),
13201        active_parameter: Some(0),
13202    };
13203    handle_signature_help_request(&mut cx, mocked_response).await;
13204
13205    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13206        .await;
13207
13208    cx.editor(|editor, _, _| {
13209        let signature_help_state = editor.signature_help_state.popover().cloned();
13210        assert!(signature_help_state.is_some());
13211        let signature = signature_help_state.unwrap();
13212        assert_eq!(
13213            signature.signatures[signature.current_signature].label,
13214            "fn sample(param1: u8, param2: u8)"
13215        );
13216    });
13217
13218    // When exiting outside from inside the brackets, `signature_help` is closed.
13219    cx.set_state(indoc! {"
13220        fn main() {
13221            sample(ˇ);
13222        }
13223
13224        fn sample(param1: u8, param2: u8) {}
13225    "});
13226
13227    cx.update_editor(|editor, window, cx| {
13228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13229            s.select_ranges([0..0])
13230        });
13231    });
13232
13233    let mocked_response = lsp::SignatureHelp {
13234        signatures: Vec::new(),
13235        active_signature: None,
13236        active_parameter: None,
13237    };
13238    handle_signature_help_request(&mut cx, mocked_response).await;
13239
13240    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13241        .await;
13242
13243    cx.editor(|editor, _, _| {
13244        assert!(!editor.signature_help_state.is_shown());
13245    });
13246
13247    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13248    cx.set_state(indoc! {"
13249        fn main() {
13250            sample(ˇ);
13251        }
13252
13253        fn sample(param1: u8, param2: u8) {}
13254    "});
13255
13256    let mocked_response = lsp::SignatureHelp {
13257        signatures: vec![lsp::SignatureInformation {
13258            label: "fn sample(param1: u8, param2: u8)".to_string(),
13259            documentation: None,
13260            parameters: Some(vec![
13261                lsp::ParameterInformation {
13262                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13263                    documentation: None,
13264                },
13265                lsp::ParameterInformation {
13266                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13267                    documentation: None,
13268                },
13269            ]),
13270            active_parameter: None,
13271        }],
13272        active_signature: Some(0),
13273        active_parameter: Some(0),
13274    };
13275    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13276    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13277        .await;
13278    cx.editor(|editor, _, _| {
13279        assert!(editor.signature_help_state.is_shown());
13280    });
13281
13282    // Restore the popover with more parameter input
13283    cx.set_state(indoc! {"
13284        fn main() {
13285            sample(param1, param2ˇ);
13286        }
13287
13288        fn sample(param1: u8, param2: u8) {}
13289    "});
13290
13291    let mocked_response = lsp::SignatureHelp {
13292        signatures: vec![lsp::SignatureInformation {
13293            label: "fn sample(param1: u8, param2: u8)".to_string(),
13294            documentation: None,
13295            parameters: Some(vec![
13296                lsp::ParameterInformation {
13297                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13298                    documentation: None,
13299                },
13300                lsp::ParameterInformation {
13301                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13302                    documentation: None,
13303                },
13304            ]),
13305            active_parameter: None,
13306        }],
13307        active_signature: Some(0),
13308        active_parameter: Some(1),
13309    };
13310    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13311    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13312        .await;
13313
13314    // When selecting a range, the popover is gone.
13315    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13316    cx.update_editor(|editor, window, cx| {
13317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13318            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13319        })
13320    });
13321    cx.assert_editor_state(indoc! {"
13322        fn main() {
13323            sample(param1, «ˇparam2»);
13324        }
13325
13326        fn sample(param1: u8, param2: u8) {}
13327    "});
13328    cx.editor(|editor, _, _| {
13329        assert!(!editor.signature_help_state.is_shown());
13330    });
13331
13332    // When unselecting again, the popover is back if within the brackets.
13333    cx.update_editor(|editor, window, cx| {
13334        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13335            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13336        })
13337    });
13338    cx.assert_editor_state(indoc! {"
13339        fn main() {
13340            sample(param1, ˇparam2);
13341        }
13342
13343        fn sample(param1: u8, param2: u8) {}
13344    "});
13345    handle_signature_help_request(&mut cx, mocked_response).await;
13346    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13347        .await;
13348    cx.editor(|editor, _, _| {
13349        assert!(editor.signature_help_state.is_shown());
13350    });
13351
13352    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13353    cx.update_editor(|editor, window, cx| {
13354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13355            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13356            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13357        })
13358    });
13359    cx.assert_editor_state(indoc! {"
13360        fn main() {
13361            sample(param1, ˇparam2);
13362        }
13363
13364        fn sample(param1: u8, param2: u8) {}
13365    "});
13366
13367    let mocked_response = lsp::SignatureHelp {
13368        signatures: vec![lsp::SignatureInformation {
13369            label: "fn sample(param1: u8, param2: u8)".to_string(),
13370            documentation: None,
13371            parameters: Some(vec![
13372                lsp::ParameterInformation {
13373                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13374                    documentation: None,
13375                },
13376                lsp::ParameterInformation {
13377                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13378                    documentation: None,
13379                },
13380            ]),
13381            active_parameter: None,
13382        }],
13383        active_signature: Some(0),
13384        active_parameter: Some(1),
13385    };
13386    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13387    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13388        .await;
13389    cx.update_editor(|editor, _, cx| {
13390        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13391    });
13392    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13393        .await;
13394    cx.update_editor(|editor, window, cx| {
13395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13396            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13397        })
13398    });
13399    cx.assert_editor_state(indoc! {"
13400        fn main() {
13401            sample(param1, «ˇparam2»);
13402        }
13403
13404        fn sample(param1: u8, param2: u8) {}
13405    "});
13406    cx.update_editor(|editor, window, cx| {
13407        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13408            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13409        })
13410    });
13411    cx.assert_editor_state(indoc! {"
13412        fn main() {
13413            sample(param1, ˇparam2);
13414        }
13415
13416        fn sample(param1: u8, param2: u8) {}
13417    "});
13418    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13419        .await;
13420}
13421
13422#[gpui::test]
13423async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13424    init_test(cx, |_| {});
13425
13426    let mut cx = EditorLspTestContext::new_rust(
13427        lsp::ServerCapabilities {
13428            signature_help_provider: Some(lsp::SignatureHelpOptions {
13429                ..Default::default()
13430            }),
13431            ..Default::default()
13432        },
13433        cx,
13434    )
13435    .await;
13436
13437    cx.set_state(indoc! {"
13438        fn main() {
13439            overloadedˇ
13440        }
13441    "});
13442
13443    cx.update_editor(|editor, window, cx| {
13444        editor.handle_input("(", window, cx);
13445        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13446    });
13447
13448    // Mock response with 3 signatures
13449    let mocked_response = lsp::SignatureHelp {
13450        signatures: vec![
13451            lsp::SignatureInformation {
13452                label: "fn overloaded(x: i32)".to_string(),
13453                documentation: None,
13454                parameters: Some(vec![lsp::ParameterInformation {
13455                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13456                    documentation: None,
13457                }]),
13458                active_parameter: None,
13459            },
13460            lsp::SignatureInformation {
13461                label: "fn overloaded(x: i32, y: i32)".to_string(),
13462                documentation: None,
13463                parameters: Some(vec![
13464                    lsp::ParameterInformation {
13465                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13466                        documentation: None,
13467                    },
13468                    lsp::ParameterInformation {
13469                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13470                        documentation: None,
13471                    },
13472                ]),
13473                active_parameter: None,
13474            },
13475            lsp::SignatureInformation {
13476                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13477                documentation: None,
13478                parameters: Some(vec![
13479                    lsp::ParameterInformation {
13480                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13481                        documentation: None,
13482                    },
13483                    lsp::ParameterInformation {
13484                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13485                        documentation: None,
13486                    },
13487                    lsp::ParameterInformation {
13488                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13489                        documentation: None,
13490                    },
13491                ]),
13492                active_parameter: None,
13493            },
13494        ],
13495        active_signature: Some(1),
13496        active_parameter: Some(0),
13497    };
13498    handle_signature_help_request(&mut cx, mocked_response).await;
13499
13500    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13501        .await;
13502
13503    // Verify we have multiple signatures and the right one is selected
13504    cx.editor(|editor, _, _| {
13505        let popover = editor.signature_help_state.popover().cloned().unwrap();
13506        assert_eq!(popover.signatures.len(), 3);
13507        // active_signature was 1, so that should be the current
13508        assert_eq!(popover.current_signature, 1);
13509        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13510        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13511        assert_eq!(
13512            popover.signatures[2].label,
13513            "fn overloaded(x: i32, y: i32, z: i32)"
13514        );
13515    });
13516
13517    // Test navigation functionality
13518    cx.update_editor(|editor, window, cx| {
13519        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13520    });
13521
13522    cx.editor(|editor, _, _| {
13523        let popover = editor.signature_help_state.popover().cloned().unwrap();
13524        assert_eq!(popover.current_signature, 2);
13525    });
13526
13527    // Test wrap around
13528    cx.update_editor(|editor, window, cx| {
13529        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13530    });
13531
13532    cx.editor(|editor, _, _| {
13533        let popover = editor.signature_help_state.popover().cloned().unwrap();
13534        assert_eq!(popover.current_signature, 0);
13535    });
13536
13537    // Test previous navigation
13538    cx.update_editor(|editor, window, cx| {
13539        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13540    });
13541
13542    cx.editor(|editor, _, _| {
13543        let popover = editor.signature_help_state.popover().cloned().unwrap();
13544        assert_eq!(popover.current_signature, 2);
13545    });
13546}
13547
13548#[gpui::test]
13549async fn test_completion_mode(cx: &mut TestAppContext) {
13550    init_test(cx, |_| {});
13551    let mut cx = EditorLspTestContext::new_rust(
13552        lsp::ServerCapabilities {
13553            completion_provider: Some(lsp::CompletionOptions {
13554                resolve_provider: Some(true),
13555                ..Default::default()
13556            }),
13557            ..Default::default()
13558        },
13559        cx,
13560    )
13561    .await;
13562
13563    struct Run {
13564        run_description: &'static str,
13565        initial_state: String,
13566        buffer_marked_text: String,
13567        completion_label: &'static str,
13568        completion_text: &'static str,
13569        expected_with_insert_mode: String,
13570        expected_with_replace_mode: String,
13571        expected_with_replace_subsequence_mode: String,
13572        expected_with_replace_suffix_mode: String,
13573    }
13574
13575    let runs = [
13576        Run {
13577            run_description: "Start of word matches completion text",
13578            initial_state: "before ediˇ after".into(),
13579            buffer_marked_text: "before <edi|> after".into(),
13580            completion_label: "editor",
13581            completion_text: "editor",
13582            expected_with_insert_mode: "before editorˇ after".into(),
13583            expected_with_replace_mode: "before editorˇ after".into(),
13584            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13585            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13586        },
13587        Run {
13588            run_description: "Accept same text at the middle of the word",
13589            initial_state: "before ediˇtor after".into(),
13590            buffer_marked_text: "before <edi|tor> after".into(),
13591            completion_label: "editor",
13592            completion_text: "editor",
13593            expected_with_insert_mode: "before editorˇtor after".into(),
13594            expected_with_replace_mode: "before editorˇ after".into(),
13595            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13596            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13597        },
13598        Run {
13599            run_description: "End of word matches completion text -- cursor at end",
13600            initial_state: "before torˇ after".into(),
13601            buffer_marked_text: "before <tor|> after".into(),
13602            completion_label: "editor",
13603            completion_text: "editor",
13604            expected_with_insert_mode: "before editorˇ after".into(),
13605            expected_with_replace_mode: "before editorˇ after".into(),
13606            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13607            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13608        },
13609        Run {
13610            run_description: "End of word matches completion text -- cursor at start",
13611            initial_state: "before ˇtor after".into(),
13612            buffer_marked_text: "before <|tor> after".into(),
13613            completion_label: "editor",
13614            completion_text: "editor",
13615            expected_with_insert_mode: "before editorˇtor after".into(),
13616            expected_with_replace_mode: "before editorˇ after".into(),
13617            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13618            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13619        },
13620        Run {
13621            run_description: "Prepend text containing whitespace",
13622            initial_state: "pˇfield: bool".into(),
13623            buffer_marked_text: "<p|field>: bool".into(),
13624            completion_label: "pub ",
13625            completion_text: "pub ",
13626            expected_with_insert_mode: "pub ˇfield: bool".into(),
13627            expected_with_replace_mode: "pub ˇ: bool".into(),
13628            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13629            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13630        },
13631        Run {
13632            run_description: "Add element to start of list",
13633            initial_state: "[element_ˇelement_2]".into(),
13634            buffer_marked_text: "[<element_|element_2>]".into(),
13635            completion_label: "element_1",
13636            completion_text: "element_1",
13637            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13638            expected_with_replace_mode: "[element_1ˇ]".into(),
13639            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13640            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13641        },
13642        Run {
13643            run_description: "Add element to start of list -- first and second elements are equal",
13644            initial_state: "[elˇelement]".into(),
13645            buffer_marked_text: "[<el|element>]".into(),
13646            completion_label: "element",
13647            completion_text: "element",
13648            expected_with_insert_mode: "[elementˇelement]".into(),
13649            expected_with_replace_mode: "[elementˇ]".into(),
13650            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13651            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13652        },
13653        Run {
13654            run_description: "Ends with matching suffix",
13655            initial_state: "SubˇError".into(),
13656            buffer_marked_text: "<Sub|Error>".into(),
13657            completion_label: "SubscriptionError",
13658            completion_text: "SubscriptionError",
13659            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13660            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13661            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13662            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13663        },
13664        Run {
13665            run_description: "Suffix is a subsequence -- contiguous",
13666            initial_state: "SubˇErr".into(),
13667            buffer_marked_text: "<Sub|Err>".into(),
13668            completion_label: "SubscriptionError",
13669            completion_text: "SubscriptionError",
13670            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13671            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13672            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13673            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13674        },
13675        Run {
13676            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13677            initial_state: "Suˇscrirr".into(),
13678            buffer_marked_text: "<Su|scrirr>".into(),
13679            completion_label: "SubscriptionError",
13680            completion_text: "SubscriptionError",
13681            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13682            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13683            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13684            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13685        },
13686        Run {
13687            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13688            initial_state: "foo(indˇix)".into(),
13689            buffer_marked_text: "foo(<ind|ix>)".into(),
13690            completion_label: "node_index",
13691            completion_text: "node_index",
13692            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13693            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13694            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13695            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13696        },
13697        Run {
13698            run_description: "Replace range ends before cursor - should extend to cursor",
13699            initial_state: "before editˇo after".into(),
13700            buffer_marked_text: "before <{ed}>it|o after".into(),
13701            completion_label: "editor",
13702            completion_text: "editor",
13703            expected_with_insert_mode: "before editorˇo after".into(),
13704            expected_with_replace_mode: "before editorˇo after".into(),
13705            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13706            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13707        },
13708        Run {
13709            run_description: "Uses label for suffix matching",
13710            initial_state: "before ediˇtor after".into(),
13711            buffer_marked_text: "before <edi|tor> after".into(),
13712            completion_label: "editor",
13713            completion_text: "editor()",
13714            expected_with_insert_mode: "before editor()ˇtor after".into(),
13715            expected_with_replace_mode: "before editor()ˇ after".into(),
13716            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13717            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13718        },
13719        Run {
13720            run_description: "Case insensitive subsequence and suffix matching",
13721            initial_state: "before EDiˇtoR after".into(),
13722            buffer_marked_text: "before <EDi|toR> after".into(),
13723            completion_label: "editor",
13724            completion_text: "editor",
13725            expected_with_insert_mode: "before editorˇtoR after".into(),
13726            expected_with_replace_mode: "before editorˇ after".into(),
13727            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13728            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13729        },
13730    ];
13731
13732    for run in runs {
13733        let run_variations = [
13734            (LspInsertMode::Insert, run.expected_with_insert_mode),
13735            (LspInsertMode::Replace, run.expected_with_replace_mode),
13736            (
13737                LspInsertMode::ReplaceSubsequence,
13738                run.expected_with_replace_subsequence_mode,
13739            ),
13740            (
13741                LspInsertMode::ReplaceSuffix,
13742                run.expected_with_replace_suffix_mode,
13743            ),
13744        ];
13745
13746        for (lsp_insert_mode, expected_text) in run_variations {
13747            eprintln!(
13748                "run = {:?}, mode = {lsp_insert_mode:.?}",
13749                run.run_description,
13750            );
13751
13752            update_test_language_settings(&mut cx, |settings| {
13753                settings.defaults.completions = Some(CompletionSettingsContent {
13754                    lsp_insert_mode: Some(lsp_insert_mode),
13755                    words: Some(WordsCompletionMode::Disabled),
13756                    words_min_length: Some(0),
13757                    ..Default::default()
13758                });
13759            });
13760
13761            cx.set_state(&run.initial_state);
13762            cx.update_editor(|editor, window, cx| {
13763                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13764            });
13765
13766            let counter = Arc::new(AtomicUsize::new(0));
13767            handle_completion_request_with_insert_and_replace(
13768                &mut cx,
13769                &run.buffer_marked_text,
13770                vec![(run.completion_label, run.completion_text)],
13771                counter.clone(),
13772            )
13773            .await;
13774            cx.condition(|editor, _| editor.context_menu_visible())
13775                .await;
13776            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13777
13778            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13779                editor
13780                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13781                    .unwrap()
13782            });
13783            cx.assert_editor_state(&expected_text);
13784            handle_resolve_completion_request(&mut cx, None).await;
13785            apply_additional_edits.await.unwrap();
13786        }
13787    }
13788}
13789
13790#[gpui::test]
13791async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13792    init_test(cx, |_| {});
13793    let mut cx = EditorLspTestContext::new_rust(
13794        lsp::ServerCapabilities {
13795            completion_provider: Some(lsp::CompletionOptions {
13796                resolve_provider: Some(true),
13797                ..Default::default()
13798            }),
13799            ..Default::default()
13800        },
13801        cx,
13802    )
13803    .await;
13804
13805    let initial_state = "SubˇError";
13806    let buffer_marked_text = "<Sub|Error>";
13807    let completion_text = "SubscriptionError";
13808    let expected_with_insert_mode = "SubscriptionErrorˇError";
13809    let expected_with_replace_mode = "SubscriptionErrorˇ";
13810
13811    update_test_language_settings(&mut cx, |settings| {
13812        settings.defaults.completions = Some(CompletionSettingsContent {
13813            words: Some(WordsCompletionMode::Disabled),
13814            words_min_length: Some(0),
13815            // set the opposite here to ensure that the action is overriding the default behavior
13816            lsp_insert_mode: Some(LspInsertMode::Insert),
13817            ..Default::default()
13818        });
13819    });
13820
13821    cx.set_state(initial_state);
13822    cx.update_editor(|editor, window, cx| {
13823        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13824    });
13825
13826    let counter = Arc::new(AtomicUsize::new(0));
13827    handle_completion_request_with_insert_and_replace(
13828        &mut cx,
13829        buffer_marked_text,
13830        vec![(completion_text, completion_text)],
13831        counter.clone(),
13832    )
13833    .await;
13834    cx.condition(|editor, _| editor.context_menu_visible())
13835        .await;
13836    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13837
13838    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13839        editor
13840            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13841            .unwrap()
13842    });
13843    cx.assert_editor_state(expected_with_replace_mode);
13844    handle_resolve_completion_request(&mut cx, None).await;
13845    apply_additional_edits.await.unwrap();
13846
13847    update_test_language_settings(&mut cx, |settings| {
13848        settings.defaults.completions = Some(CompletionSettingsContent {
13849            words: Some(WordsCompletionMode::Disabled),
13850            words_min_length: Some(0),
13851            // set the opposite here to ensure that the action is overriding the default behavior
13852            lsp_insert_mode: Some(LspInsertMode::Replace),
13853            ..Default::default()
13854        });
13855    });
13856
13857    cx.set_state(initial_state);
13858    cx.update_editor(|editor, window, cx| {
13859        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13860    });
13861    handle_completion_request_with_insert_and_replace(
13862        &mut cx,
13863        buffer_marked_text,
13864        vec![(completion_text, completion_text)],
13865        counter.clone(),
13866    )
13867    .await;
13868    cx.condition(|editor, _| editor.context_menu_visible())
13869        .await;
13870    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13871
13872    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13873        editor
13874            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13875            .unwrap()
13876    });
13877    cx.assert_editor_state(expected_with_insert_mode);
13878    handle_resolve_completion_request(&mut cx, None).await;
13879    apply_additional_edits.await.unwrap();
13880}
13881
13882#[gpui::test]
13883async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13884    init_test(cx, |_| {});
13885    let mut cx = EditorLspTestContext::new_rust(
13886        lsp::ServerCapabilities {
13887            completion_provider: Some(lsp::CompletionOptions {
13888                resolve_provider: Some(true),
13889                ..Default::default()
13890            }),
13891            ..Default::default()
13892        },
13893        cx,
13894    )
13895    .await;
13896
13897    // scenario: surrounding text matches completion text
13898    let completion_text = "to_offset";
13899    let initial_state = indoc! {"
13900        1. buf.to_offˇsuffix
13901        2. buf.to_offˇsuf
13902        3. buf.to_offˇfix
13903        4. buf.to_offˇ
13904        5. into_offˇensive
13905        6. ˇsuffix
13906        7. let ˇ //
13907        8. aaˇzz
13908        9. buf.to_off«zzzzzˇ»suffix
13909        10. buf.«ˇzzzzz»suffix
13910        11. to_off«ˇzzzzz»
13911
13912        buf.to_offˇsuffix  // newest cursor
13913    "};
13914    let completion_marked_buffer = indoc! {"
13915        1. buf.to_offsuffix
13916        2. buf.to_offsuf
13917        3. buf.to_offfix
13918        4. buf.to_off
13919        5. into_offensive
13920        6. suffix
13921        7. let  //
13922        8. aazz
13923        9. buf.to_offzzzzzsuffix
13924        10. buf.zzzzzsuffix
13925        11. to_offzzzzz
13926
13927        buf.<to_off|suffix>  // newest cursor
13928    "};
13929    let expected = indoc! {"
13930        1. buf.to_offsetˇ
13931        2. buf.to_offsetˇsuf
13932        3. buf.to_offsetˇfix
13933        4. buf.to_offsetˇ
13934        5. into_offsetˇensive
13935        6. to_offsetˇsuffix
13936        7. let to_offsetˇ //
13937        8. aato_offsetˇzz
13938        9. buf.to_offsetˇ
13939        10. buf.to_offsetˇsuffix
13940        11. to_offsetˇ
13941
13942        buf.to_offsetˇ  // newest cursor
13943    "};
13944    cx.set_state(initial_state);
13945    cx.update_editor(|editor, window, cx| {
13946        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13947    });
13948    handle_completion_request_with_insert_and_replace(
13949        &mut cx,
13950        completion_marked_buffer,
13951        vec![(completion_text, completion_text)],
13952        Arc::new(AtomicUsize::new(0)),
13953    )
13954    .await;
13955    cx.condition(|editor, _| editor.context_menu_visible())
13956        .await;
13957    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13958        editor
13959            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13960            .unwrap()
13961    });
13962    cx.assert_editor_state(expected);
13963    handle_resolve_completion_request(&mut cx, None).await;
13964    apply_additional_edits.await.unwrap();
13965
13966    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13967    let completion_text = "foo_and_bar";
13968    let initial_state = indoc! {"
13969        1. ooanbˇ
13970        2. zooanbˇ
13971        3. ooanbˇz
13972        4. zooanbˇz
13973        5. ooanˇ
13974        6. oanbˇ
13975
13976        ooanbˇ
13977    "};
13978    let completion_marked_buffer = indoc! {"
13979        1. ooanb
13980        2. zooanb
13981        3. ooanbz
13982        4. zooanbz
13983        5. ooan
13984        6. oanb
13985
13986        <ooanb|>
13987    "};
13988    let expected = indoc! {"
13989        1. foo_and_barˇ
13990        2. zfoo_and_barˇ
13991        3. foo_and_barˇz
13992        4. zfoo_and_barˇz
13993        5. ooanfoo_and_barˇ
13994        6. oanbfoo_and_barˇ
13995
13996        foo_and_barˇ
13997    "};
13998    cx.set_state(initial_state);
13999    cx.update_editor(|editor, window, cx| {
14000        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14001    });
14002    handle_completion_request_with_insert_and_replace(
14003        &mut cx,
14004        completion_marked_buffer,
14005        vec![(completion_text, completion_text)],
14006        Arc::new(AtomicUsize::new(0)),
14007    )
14008    .await;
14009    cx.condition(|editor, _| editor.context_menu_visible())
14010        .await;
14011    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14012        editor
14013            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14014            .unwrap()
14015    });
14016    cx.assert_editor_state(expected);
14017    handle_resolve_completion_request(&mut cx, None).await;
14018    apply_additional_edits.await.unwrap();
14019
14020    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14021    // (expects the same as if it was inserted at the end)
14022    let completion_text = "foo_and_bar";
14023    let initial_state = indoc! {"
14024        1. ooˇanb
14025        2. zooˇanb
14026        3. ooˇanbz
14027        4. zooˇanbz
14028
14029        ooˇanb
14030    "};
14031    let completion_marked_buffer = indoc! {"
14032        1. ooanb
14033        2. zooanb
14034        3. ooanbz
14035        4. zooanbz
14036
14037        <oo|anb>
14038    "};
14039    let expected = indoc! {"
14040        1. foo_and_barˇ
14041        2. zfoo_and_barˇ
14042        3. foo_and_barˇz
14043        4. zfoo_and_barˇz
14044
14045        foo_and_barˇ
14046    "};
14047    cx.set_state(initial_state);
14048    cx.update_editor(|editor, window, cx| {
14049        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14050    });
14051    handle_completion_request_with_insert_and_replace(
14052        &mut cx,
14053        completion_marked_buffer,
14054        vec![(completion_text, completion_text)],
14055        Arc::new(AtomicUsize::new(0)),
14056    )
14057    .await;
14058    cx.condition(|editor, _| editor.context_menu_visible())
14059        .await;
14060    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14061        editor
14062            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14063            .unwrap()
14064    });
14065    cx.assert_editor_state(expected);
14066    handle_resolve_completion_request(&mut cx, None).await;
14067    apply_additional_edits.await.unwrap();
14068}
14069
14070// This used to crash
14071#[gpui::test]
14072async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14073    init_test(cx, |_| {});
14074
14075    let buffer_text = indoc! {"
14076        fn main() {
14077            10.satu;
14078
14079            //
14080            // separate cursors so they open in different excerpts (manually reproducible)
14081            //
14082
14083            10.satu20;
14084        }
14085    "};
14086    let multibuffer_text_with_selections = indoc! {"
14087        fn main() {
14088            10.satuˇ;
14089
14090            //
14091
14092            //
14093
14094            10.satuˇ20;
14095        }
14096    "};
14097    let expected_multibuffer = indoc! {"
14098        fn main() {
14099            10.saturating_sub()ˇ;
14100
14101            //
14102
14103            //
14104
14105            10.saturating_sub()ˇ;
14106        }
14107    "};
14108
14109    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14110    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14111
14112    let fs = FakeFs::new(cx.executor());
14113    fs.insert_tree(
14114        path!("/a"),
14115        json!({
14116            "main.rs": buffer_text,
14117        }),
14118    )
14119    .await;
14120
14121    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14122    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14123    language_registry.add(rust_lang());
14124    let mut fake_servers = language_registry.register_fake_lsp(
14125        "Rust",
14126        FakeLspAdapter {
14127            capabilities: lsp::ServerCapabilities {
14128                completion_provider: Some(lsp::CompletionOptions {
14129                    resolve_provider: None,
14130                    ..lsp::CompletionOptions::default()
14131                }),
14132                ..lsp::ServerCapabilities::default()
14133            },
14134            ..FakeLspAdapter::default()
14135        },
14136    );
14137    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14138    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14139    let buffer = project
14140        .update(cx, |project, cx| {
14141            project.open_local_buffer(path!("/a/main.rs"), cx)
14142        })
14143        .await
14144        .unwrap();
14145
14146    let multi_buffer = cx.new(|cx| {
14147        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14148        multi_buffer.push_excerpts(
14149            buffer.clone(),
14150            [ExcerptRange::new(0..first_excerpt_end)],
14151            cx,
14152        );
14153        multi_buffer.push_excerpts(
14154            buffer.clone(),
14155            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14156            cx,
14157        );
14158        multi_buffer
14159    });
14160
14161    let editor = workspace
14162        .update(cx, |_, window, cx| {
14163            cx.new(|cx| {
14164                Editor::new(
14165                    EditorMode::Full {
14166                        scale_ui_elements_with_buffer_font_size: false,
14167                        show_active_line_background: false,
14168                        sizing_behavior: SizingBehavior::Default,
14169                    },
14170                    multi_buffer.clone(),
14171                    Some(project.clone()),
14172                    window,
14173                    cx,
14174                )
14175            })
14176        })
14177        .unwrap();
14178
14179    let pane = workspace
14180        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14181        .unwrap();
14182    pane.update_in(cx, |pane, window, cx| {
14183        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14184    });
14185
14186    let fake_server = fake_servers.next().await.unwrap();
14187
14188    editor.update_in(cx, |editor, window, cx| {
14189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14190            s.select_ranges([
14191                Point::new(1, 11)..Point::new(1, 11),
14192                Point::new(7, 11)..Point::new(7, 11),
14193            ])
14194        });
14195
14196        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14197    });
14198
14199    editor.update_in(cx, |editor, window, cx| {
14200        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14201    });
14202
14203    fake_server
14204        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14205            let completion_item = lsp::CompletionItem {
14206                label: "saturating_sub()".into(),
14207                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14208                    lsp::InsertReplaceEdit {
14209                        new_text: "saturating_sub()".to_owned(),
14210                        insert: lsp::Range::new(
14211                            lsp::Position::new(7, 7),
14212                            lsp::Position::new(7, 11),
14213                        ),
14214                        replace: lsp::Range::new(
14215                            lsp::Position::new(7, 7),
14216                            lsp::Position::new(7, 13),
14217                        ),
14218                    },
14219                )),
14220                ..lsp::CompletionItem::default()
14221            };
14222
14223            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14224        })
14225        .next()
14226        .await
14227        .unwrap();
14228
14229    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14230        .await;
14231
14232    editor
14233        .update_in(cx, |editor, window, cx| {
14234            editor
14235                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14236                .unwrap()
14237        })
14238        .await
14239        .unwrap();
14240
14241    editor.update(cx, |editor, cx| {
14242        assert_text_with_selections(editor, expected_multibuffer, cx);
14243    })
14244}
14245
14246#[gpui::test]
14247async fn test_completion(cx: &mut TestAppContext) {
14248    init_test(cx, |_| {});
14249
14250    let mut cx = EditorLspTestContext::new_rust(
14251        lsp::ServerCapabilities {
14252            completion_provider: Some(lsp::CompletionOptions {
14253                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14254                resolve_provider: Some(true),
14255                ..Default::default()
14256            }),
14257            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14258            ..Default::default()
14259        },
14260        cx,
14261    )
14262    .await;
14263    let counter = Arc::new(AtomicUsize::new(0));
14264
14265    cx.set_state(indoc! {"
14266        oneˇ
14267        two
14268        three
14269    "});
14270    cx.simulate_keystroke(".");
14271    handle_completion_request(
14272        indoc! {"
14273            one.|<>
14274            two
14275            three
14276        "},
14277        vec!["first_completion", "second_completion"],
14278        true,
14279        counter.clone(),
14280        &mut cx,
14281    )
14282    .await;
14283    cx.condition(|editor, _| editor.context_menu_visible())
14284        .await;
14285    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14286
14287    let _handler = handle_signature_help_request(
14288        &mut cx,
14289        lsp::SignatureHelp {
14290            signatures: vec![lsp::SignatureInformation {
14291                label: "test signature".to_string(),
14292                documentation: None,
14293                parameters: Some(vec![lsp::ParameterInformation {
14294                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14295                    documentation: None,
14296                }]),
14297                active_parameter: None,
14298            }],
14299            active_signature: None,
14300            active_parameter: None,
14301        },
14302    );
14303    cx.update_editor(|editor, window, cx| {
14304        assert!(
14305            !editor.signature_help_state.is_shown(),
14306            "No signature help was called for"
14307        );
14308        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14309    });
14310    cx.run_until_parked();
14311    cx.update_editor(|editor, _, _| {
14312        assert!(
14313            !editor.signature_help_state.is_shown(),
14314            "No signature help should be shown when completions menu is open"
14315        );
14316    });
14317
14318    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14319        editor.context_menu_next(&Default::default(), window, cx);
14320        editor
14321            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14322            .unwrap()
14323    });
14324    cx.assert_editor_state(indoc! {"
14325        one.second_completionˇ
14326        two
14327        three
14328    "});
14329
14330    handle_resolve_completion_request(
14331        &mut cx,
14332        Some(vec![
14333            (
14334                //This overlaps with the primary completion edit which is
14335                //misbehavior from the LSP spec, test that we filter it out
14336                indoc! {"
14337                    one.second_ˇcompletion
14338                    two
14339                    threeˇ
14340                "},
14341                "overlapping additional edit",
14342            ),
14343            (
14344                indoc! {"
14345                    one.second_completion
14346                    two
14347                    threeˇ
14348                "},
14349                "\nadditional edit",
14350            ),
14351        ]),
14352    )
14353    .await;
14354    apply_additional_edits.await.unwrap();
14355    cx.assert_editor_state(indoc! {"
14356        one.second_completionˇ
14357        two
14358        three
14359        additional edit
14360    "});
14361
14362    cx.set_state(indoc! {"
14363        one.second_completion
14364        twoˇ
14365        threeˇ
14366        additional edit
14367    "});
14368    cx.simulate_keystroke(" ");
14369    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14370    cx.simulate_keystroke("s");
14371    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14372
14373    cx.assert_editor_state(indoc! {"
14374        one.second_completion
14375        two sˇ
14376        three sˇ
14377        additional edit
14378    "});
14379    handle_completion_request(
14380        indoc! {"
14381            one.second_completion
14382            two s
14383            three <s|>
14384            additional edit
14385        "},
14386        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14387        true,
14388        counter.clone(),
14389        &mut cx,
14390    )
14391    .await;
14392    cx.condition(|editor, _| editor.context_menu_visible())
14393        .await;
14394    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14395
14396    cx.simulate_keystroke("i");
14397
14398    handle_completion_request(
14399        indoc! {"
14400            one.second_completion
14401            two si
14402            three <si|>
14403            additional edit
14404        "},
14405        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14406        true,
14407        counter.clone(),
14408        &mut cx,
14409    )
14410    .await;
14411    cx.condition(|editor, _| editor.context_menu_visible())
14412        .await;
14413    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14414
14415    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14416        editor
14417            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14418            .unwrap()
14419    });
14420    cx.assert_editor_state(indoc! {"
14421        one.second_completion
14422        two sixth_completionˇ
14423        three sixth_completionˇ
14424        additional edit
14425    "});
14426
14427    apply_additional_edits.await.unwrap();
14428
14429    update_test_language_settings(&mut cx, |settings| {
14430        settings.defaults.show_completions_on_input = Some(false);
14431    });
14432    cx.set_state("editorˇ");
14433    cx.simulate_keystroke(".");
14434    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14435    cx.simulate_keystrokes("c l o");
14436    cx.assert_editor_state("editor.cloˇ");
14437    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14438    cx.update_editor(|editor, window, cx| {
14439        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14440    });
14441    handle_completion_request(
14442        "editor.<clo|>",
14443        vec!["close", "clobber"],
14444        true,
14445        counter.clone(),
14446        &mut cx,
14447    )
14448    .await;
14449    cx.condition(|editor, _| editor.context_menu_visible())
14450        .await;
14451    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14452
14453    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14454        editor
14455            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14456            .unwrap()
14457    });
14458    cx.assert_editor_state("editor.clobberˇ");
14459    handle_resolve_completion_request(&mut cx, None).await;
14460    apply_additional_edits.await.unwrap();
14461}
14462
14463#[gpui::test]
14464async fn test_completion_reuse(cx: &mut TestAppContext) {
14465    init_test(cx, |_| {});
14466
14467    let mut cx = EditorLspTestContext::new_rust(
14468        lsp::ServerCapabilities {
14469            completion_provider: Some(lsp::CompletionOptions {
14470                trigger_characters: Some(vec![".".to_string()]),
14471                ..Default::default()
14472            }),
14473            ..Default::default()
14474        },
14475        cx,
14476    )
14477    .await;
14478
14479    let counter = Arc::new(AtomicUsize::new(0));
14480    cx.set_state("objˇ");
14481    cx.simulate_keystroke(".");
14482
14483    // Initial completion request returns complete results
14484    let is_incomplete = false;
14485    handle_completion_request(
14486        "obj.|<>",
14487        vec!["a", "ab", "abc"],
14488        is_incomplete,
14489        counter.clone(),
14490        &mut cx,
14491    )
14492    .await;
14493    cx.run_until_parked();
14494    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14495    cx.assert_editor_state("obj.ˇ");
14496    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14497
14498    // Type "a" - filters existing completions
14499    cx.simulate_keystroke("a");
14500    cx.run_until_parked();
14501    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14502    cx.assert_editor_state("obj.aˇ");
14503    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14504
14505    // Type "b" - filters existing completions
14506    cx.simulate_keystroke("b");
14507    cx.run_until_parked();
14508    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14509    cx.assert_editor_state("obj.abˇ");
14510    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14511
14512    // Type "c" - filters existing completions
14513    cx.simulate_keystroke("c");
14514    cx.run_until_parked();
14515    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14516    cx.assert_editor_state("obj.abcˇ");
14517    check_displayed_completions(vec!["abc"], &mut cx);
14518
14519    // Backspace to delete "c" - filters existing completions
14520    cx.update_editor(|editor, window, cx| {
14521        editor.backspace(&Backspace, window, cx);
14522    });
14523    cx.run_until_parked();
14524    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14525    cx.assert_editor_state("obj.abˇ");
14526    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14527
14528    // Moving cursor to the left dismisses menu.
14529    cx.update_editor(|editor, window, cx| {
14530        editor.move_left(&MoveLeft, window, cx);
14531    });
14532    cx.run_until_parked();
14533    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14534    cx.assert_editor_state("obj.aˇb");
14535    cx.update_editor(|editor, _, _| {
14536        assert_eq!(editor.context_menu_visible(), false);
14537    });
14538
14539    // Type "b" - new request
14540    cx.simulate_keystroke("b");
14541    let is_incomplete = false;
14542    handle_completion_request(
14543        "obj.<ab|>a",
14544        vec!["ab", "abc"],
14545        is_incomplete,
14546        counter.clone(),
14547        &mut cx,
14548    )
14549    .await;
14550    cx.run_until_parked();
14551    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14552    cx.assert_editor_state("obj.abˇb");
14553    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14554
14555    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14556    cx.update_editor(|editor, window, cx| {
14557        editor.backspace(&Backspace, window, cx);
14558    });
14559    let is_incomplete = false;
14560    handle_completion_request(
14561        "obj.<a|>b",
14562        vec!["a", "ab", "abc"],
14563        is_incomplete,
14564        counter.clone(),
14565        &mut cx,
14566    )
14567    .await;
14568    cx.run_until_parked();
14569    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14570    cx.assert_editor_state("obj.aˇb");
14571    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14572
14573    // Backspace to delete "a" - dismisses menu.
14574    cx.update_editor(|editor, window, cx| {
14575        editor.backspace(&Backspace, window, cx);
14576    });
14577    cx.run_until_parked();
14578    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14579    cx.assert_editor_state("obj.ˇb");
14580    cx.update_editor(|editor, _, _| {
14581        assert_eq!(editor.context_menu_visible(), false);
14582    });
14583}
14584
14585#[gpui::test]
14586async fn test_word_completion(cx: &mut TestAppContext) {
14587    let lsp_fetch_timeout_ms = 10;
14588    init_test(cx, |language_settings| {
14589        language_settings.defaults.completions = Some(CompletionSettingsContent {
14590            words_min_length: Some(0),
14591            lsp_fetch_timeout_ms: Some(10),
14592            lsp_insert_mode: Some(LspInsertMode::Insert),
14593            ..Default::default()
14594        });
14595    });
14596
14597    let mut cx = EditorLspTestContext::new_rust(
14598        lsp::ServerCapabilities {
14599            completion_provider: Some(lsp::CompletionOptions {
14600                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14601                ..lsp::CompletionOptions::default()
14602            }),
14603            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14604            ..lsp::ServerCapabilities::default()
14605        },
14606        cx,
14607    )
14608    .await;
14609
14610    let throttle_completions = Arc::new(AtomicBool::new(false));
14611
14612    let lsp_throttle_completions = throttle_completions.clone();
14613    let _completion_requests_handler =
14614        cx.lsp
14615            .server
14616            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14617                let lsp_throttle_completions = lsp_throttle_completions.clone();
14618                let cx = cx.clone();
14619                async move {
14620                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14621                        cx.background_executor()
14622                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14623                            .await;
14624                    }
14625                    Ok(Some(lsp::CompletionResponse::Array(vec![
14626                        lsp::CompletionItem {
14627                            label: "first".into(),
14628                            ..lsp::CompletionItem::default()
14629                        },
14630                        lsp::CompletionItem {
14631                            label: "last".into(),
14632                            ..lsp::CompletionItem::default()
14633                        },
14634                    ])))
14635                }
14636            });
14637
14638    cx.set_state(indoc! {"
14639        oneˇ
14640        two
14641        three
14642    "});
14643    cx.simulate_keystroke(".");
14644    cx.executor().run_until_parked();
14645    cx.condition(|editor, _| editor.context_menu_visible())
14646        .await;
14647    cx.update_editor(|editor, window, cx| {
14648        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14649        {
14650            assert_eq!(
14651                completion_menu_entries(menu),
14652                &["first", "last"],
14653                "When LSP server is fast to reply, no fallback word completions are used"
14654            );
14655        } else {
14656            panic!("expected completion menu to be open");
14657        }
14658        editor.cancel(&Cancel, window, cx);
14659    });
14660    cx.executor().run_until_parked();
14661    cx.condition(|editor, _| !editor.context_menu_visible())
14662        .await;
14663
14664    throttle_completions.store(true, atomic::Ordering::Release);
14665    cx.simulate_keystroke(".");
14666    cx.executor()
14667        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14668    cx.executor().run_until_parked();
14669    cx.condition(|editor, _| editor.context_menu_visible())
14670        .await;
14671    cx.update_editor(|editor, _, _| {
14672        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14673        {
14674            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14675                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14676        } else {
14677            panic!("expected completion menu to be open");
14678        }
14679    });
14680}
14681
14682#[gpui::test]
14683async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14684    init_test(cx, |language_settings| {
14685        language_settings.defaults.completions = Some(CompletionSettingsContent {
14686            words: Some(WordsCompletionMode::Enabled),
14687            words_min_length: Some(0),
14688            lsp_insert_mode: Some(LspInsertMode::Insert),
14689            ..Default::default()
14690        });
14691    });
14692
14693    let mut cx = EditorLspTestContext::new_rust(
14694        lsp::ServerCapabilities {
14695            completion_provider: Some(lsp::CompletionOptions {
14696                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14697                ..lsp::CompletionOptions::default()
14698            }),
14699            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14700            ..lsp::ServerCapabilities::default()
14701        },
14702        cx,
14703    )
14704    .await;
14705
14706    let _completion_requests_handler =
14707        cx.lsp
14708            .server
14709            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14710                Ok(Some(lsp::CompletionResponse::Array(vec![
14711                    lsp::CompletionItem {
14712                        label: "first".into(),
14713                        ..lsp::CompletionItem::default()
14714                    },
14715                    lsp::CompletionItem {
14716                        label: "last".into(),
14717                        ..lsp::CompletionItem::default()
14718                    },
14719                ])))
14720            });
14721
14722    cx.set_state(indoc! {"ˇ
14723        first
14724        last
14725        second
14726    "});
14727    cx.simulate_keystroke(".");
14728    cx.executor().run_until_parked();
14729    cx.condition(|editor, _| editor.context_menu_visible())
14730        .await;
14731    cx.update_editor(|editor, _, _| {
14732        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14733        {
14734            assert_eq!(
14735                completion_menu_entries(menu),
14736                &["first", "last", "second"],
14737                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14738            );
14739        } else {
14740            panic!("expected completion menu to be open");
14741        }
14742    });
14743}
14744
14745#[gpui::test]
14746async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14747    init_test(cx, |language_settings| {
14748        language_settings.defaults.completions = Some(CompletionSettingsContent {
14749            words: Some(WordsCompletionMode::Disabled),
14750            words_min_length: Some(0),
14751            lsp_insert_mode: Some(LspInsertMode::Insert),
14752            ..Default::default()
14753        });
14754    });
14755
14756    let mut cx = EditorLspTestContext::new_rust(
14757        lsp::ServerCapabilities {
14758            completion_provider: Some(lsp::CompletionOptions {
14759                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14760                ..lsp::CompletionOptions::default()
14761            }),
14762            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14763            ..lsp::ServerCapabilities::default()
14764        },
14765        cx,
14766    )
14767    .await;
14768
14769    let _completion_requests_handler =
14770        cx.lsp
14771            .server
14772            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14773                panic!("LSP completions should not be queried when dealing with word completions")
14774            });
14775
14776    cx.set_state(indoc! {"ˇ
14777        first
14778        last
14779        second
14780    "});
14781    cx.update_editor(|editor, window, cx| {
14782        editor.show_word_completions(&ShowWordCompletions, window, cx);
14783    });
14784    cx.executor().run_until_parked();
14785    cx.condition(|editor, _| editor.context_menu_visible())
14786        .await;
14787    cx.update_editor(|editor, _, _| {
14788        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14789        {
14790            assert_eq!(
14791                completion_menu_entries(menu),
14792                &["first", "last", "second"],
14793                "`ShowWordCompletions` action should show word completions"
14794            );
14795        } else {
14796            panic!("expected completion menu to be open");
14797        }
14798    });
14799
14800    cx.simulate_keystroke("l");
14801    cx.executor().run_until_parked();
14802    cx.condition(|editor, _| editor.context_menu_visible())
14803        .await;
14804    cx.update_editor(|editor, _, _| {
14805        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14806        {
14807            assert_eq!(
14808                completion_menu_entries(menu),
14809                &["last"],
14810                "After showing word completions, further editing should filter them and not query the LSP"
14811            );
14812        } else {
14813            panic!("expected completion menu to be open");
14814        }
14815    });
14816}
14817
14818#[gpui::test]
14819async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14820    init_test(cx, |language_settings| {
14821        language_settings.defaults.completions = Some(CompletionSettingsContent {
14822            words_min_length: Some(0),
14823            lsp: Some(false),
14824            lsp_insert_mode: Some(LspInsertMode::Insert),
14825            ..Default::default()
14826        });
14827    });
14828
14829    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14830
14831    cx.set_state(indoc! {"ˇ
14832        0_usize
14833        let
14834        33
14835        4.5f32
14836    "});
14837    cx.update_editor(|editor, window, cx| {
14838        editor.show_completions(&ShowCompletions::default(), window, cx);
14839    });
14840    cx.executor().run_until_parked();
14841    cx.condition(|editor, _| editor.context_menu_visible())
14842        .await;
14843    cx.update_editor(|editor, window, cx| {
14844        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14845        {
14846            assert_eq!(
14847                completion_menu_entries(menu),
14848                &["let"],
14849                "With no digits in the completion query, no digits should be in the word completions"
14850            );
14851        } else {
14852            panic!("expected completion menu to be open");
14853        }
14854        editor.cancel(&Cancel, window, cx);
14855    });
14856
14857    cx.set_state(indoc! {"14858        0_usize
14859        let
14860        3
14861        33.35f32
14862    "});
14863    cx.update_editor(|editor, window, cx| {
14864        editor.show_completions(&ShowCompletions::default(), window, cx);
14865    });
14866    cx.executor().run_until_parked();
14867    cx.condition(|editor, _| editor.context_menu_visible())
14868        .await;
14869    cx.update_editor(|editor, _, _| {
14870        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14871        {
14872            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14873                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14874        } else {
14875            panic!("expected completion menu to be open");
14876        }
14877    });
14878}
14879
14880#[gpui::test]
14881async fn test_word_completions_do_not_show_before_threshold(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(3),
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.set_state(indoc! {"ˇ
14893        wow
14894        wowen
14895        wowser
14896    "});
14897    cx.simulate_keystroke("w");
14898    cx.executor().run_until_parked();
14899    cx.update_editor(|editor, _, _| {
14900        if editor.context_menu.borrow_mut().is_some() {
14901            panic!(
14902                "expected completion menu to be hidden, as words completion threshold is not met"
14903            );
14904        }
14905    });
14906
14907    cx.update_editor(|editor, window, cx| {
14908        editor.show_word_completions(&ShowWordCompletions, window, cx);
14909    });
14910    cx.executor().run_until_parked();
14911    cx.update_editor(|editor, window, cx| {
14912        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14913        {
14914            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");
14915        } else {
14916            panic!("expected completion menu to be open after the word completions are called with an action");
14917        }
14918
14919        editor.cancel(&Cancel, window, cx);
14920    });
14921    cx.update_editor(|editor, _, _| {
14922        if editor.context_menu.borrow_mut().is_some() {
14923            panic!("expected completion menu to be hidden after canceling");
14924        }
14925    });
14926
14927    cx.simulate_keystroke("o");
14928    cx.executor().run_until_parked();
14929    cx.update_editor(|editor, _, _| {
14930        if editor.context_menu.borrow_mut().is_some() {
14931            panic!(
14932                "expected completion menu to be hidden, as words completion threshold is not met still"
14933            );
14934        }
14935    });
14936
14937    cx.simulate_keystroke("w");
14938    cx.executor().run_until_parked();
14939    cx.update_editor(|editor, _, _| {
14940        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14941        {
14942            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14943        } else {
14944            panic!("expected completion menu to be open after the word completions threshold is met");
14945        }
14946    });
14947}
14948
14949#[gpui::test]
14950async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14951    init_test(cx, |language_settings| {
14952        language_settings.defaults.completions = Some(CompletionSettingsContent {
14953            words: Some(WordsCompletionMode::Enabled),
14954            words_min_length: Some(0),
14955            lsp_insert_mode: Some(LspInsertMode::Insert),
14956            ..Default::default()
14957        });
14958    });
14959
14960    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14961    cx.update_editor(|editor, _, _| {
14962        editor.disable_word_completions();
14963    });
14964    cx.set_state(indoc! {"ˇ
14965        wow
14966        wowen
14967        wowser
14968    "});
14969    cx.simulate_keystroke("w");
14970    cx.executor().run_until_parked();
14971    cx.update_editor(|editor, _, _| {
14972        if editor.context_menu.borrow_mut().is_some() {
14973            panic!(
14974                "expected completion menu to be hidden, as words completion are disabled for this editor"
14975            );
14976        }
14977    });
14978
14979    cx.update_editor(|editor, window, cx| {
14980        editor.show_word_completions(&ShowWordCompletions, window, cx);
14981    });
14982    cx.executor().run_until_parked();
14983    cx.update_editor(|editor, _, _| {
14984        if editor.context_menu.borrow_mut().is_some() {
14985            panic!(
14986                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14987            );
14988        }
14989    });
14990}
14991
14992fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14993    let position = || lsp::Position {
14994        line: params.text_document_position.position.line,
14995        character: params.text_document_position.position.character,
14996    };
14997    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14998        range: lsp::Range {
14999            start: position(),
15000            end: position(),
15001        },
15002        new_text: text.to_string(),
15003    }))
15004}
15005
15006#[gpui::test]
15007async fn test_multiline_completion(cx: &mut TestAppContext) {
15008    init_test(cx, |_| {});
15009
15010    let fs = FakeFs::new(cx.executor());
15011    fs.insert_tree(
15012        path!("/a"),
15013        json!({
15014            "main.ts": "a",
15015        }),
15016    )
15017    .await;
15018
15019    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15020    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15021    let typescript_language = Arc::new(Language::new(
15022        LanguageConfig {
15023            name: "TypeScript".into(),
15024            matcher: LanguageMatcher {
15025                path_suffixes: vec!["ts".to_string()],
15026                ..LanguageMatcher::default()
15027            },
15028            line_comments: vec!["// ".into()],
15029            ..LanguageConfig::default()
15030        },
15031        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15032    ));
15033    language_registry.add(typescript_language.clone());
15034    let mut fake_servers = language_registry.register_fake_lsp(
15035        "TypeScript",
15036        FakeLspAdapter {
15037            capabilities: lsp::ServerCapabilities {
15038                completion_provider: Some(lsp::CompletionOptions {
15039                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15040                    ..lsp::CompletionOptions::default()
15041                }),
15042                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15043                ..lsp::ServerCapabilities::default()
15044            },
15045            // Emulate vtsls label generation
15046            label_for_completion: Some(Box::new(|item, _| {
15047                let text = if let Some(description) = item
15048                    .label_details
15049                    .as_ref()
15050                    .and_then(|label_details| label_details.description.as_ref())
15051                {
15052                    format!("{} {}", item.label, description)
15053                } else if let Some(detail) = &item.detail {
15054                    format!("{} {}", item.label, detail)
15055                } else {
15056                    item.label.clone()
15057                };
15058                Some(language::CodeLabel::plain(text, None))
15059            })),
15060            ..FakeLspAdapter::default()
15061        },
15062    );
15063    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15064    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15065    let worktree_id = workspace
15066        .update(cx, |workspace, _window, cx| {
15067            workspace.project().update(cx, |project, cx| {
15068                project.worktrees(cx).next().unwrap().read(cx).id()
15069            })
15070        })
15071        .unwrap();
15072    let _buffer = project
15073        .update(cx, |project, cx| {
15074            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15075        })
15076        .await
15077        .unwrap();
15078    let editor = workspace
15079        .update(cx, |workspace, window, cx| {
15080            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15081        })
15082        .unwrap()
15083        .await
15084        .unwrap()
15085        .downcast::<Editor>()
15086        .unwrap();
15087    let fake_server = fake_servers.next().await.unwrap();
15088
15089    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15090    let multiline_label_2 = "a\nb\nc\n";
15091    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15092    let multiline_description = "d\ne\nf\n";
15093    let multiline_detail_2 = "g\nh\ni\n";
15094
15095    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15096        move |params, _| async move {
15097            Ok(Some(lsp::CompletionResponse::Array(vec![
15098                lsp::CompletionItem {
15099                    label: multiline_label.to_string(),
15100                    text_edit: gen_text_edit(&params, "new_text_1"),
15101                    ..lsp::CompletionItem::default()
15102                },
15103                lsp::CompletionItem {
15104                    label: "single line label 1".to_string(),
15105                    detail: Some(multiline_detail.to_string()),
15106                    text_edit: gen_text_edit(&params, "new_text_2"),
15107                    ..lsp::CompletionItem::default()
15108                },
15109                lsp::CompletionItem {
15110                    label: "single line label 2".to_string(),
15111                    label_details: Some(lsp::CompletionItemLabelDetails {
15112                        description: Some(multiline_description.to_string()),
15113                        detail: None,
15114                    }),
15115                    text_edit: gen_text_edit(&params, "new_text_2"),
15116                    ..lsp::CompletionItem::default()
15117                },
15118                lsp::CompletionItem {
15119                    label: multiline_label_2.to_string(),
15120                    detail: Some(multiline_detail_2.to_string()),
15121                    text_edit: gen_text_edit(&params, "new_text_3"),
15122                    ..lsp::CompletionItem::default()
15123                },
15124                lsp::CompletionItem {
15125                    label: "Label with many     spaces and \t but without newlines".to_string(),
15126                    detail: Some(
15127                        "Details with many     spaces and \t but without newlines".to_string(),
15128                    ),
15129                    text_edit: gen_text_edit(&params, "new_text_4"),
15130                    ..lsp::CompletionItem::default()
15131                },
15132            ])))
15133        },
15134    );
15135
15136    editor.update_in(cx, |editor, window, cx| {
15137        cx.focus_self(window);
15138        editor.move_to_end(&MoveToEnd, window, cx);
15139        editor.handle_input(".", window, cx);
15140    });
15141    cx.run_until_parked();
15142    completion_handle.next().await.unwrap();
15143
15144    editor.update(cx, |editor, _| {
15145        assert!(editor.context_menu_visible());
15146        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15147        {
15148            let completion_labels = menu
15149                .completions
15150                .borrow()
15151                .iter()
15152                .map(|c| c.label.text.clone())
15153                .collect::<Vec<_>>();
15154            assert_eq!(
15155                completion_labels,
15156                &[
15157                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15158                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15159                    "single line label 2 d e f ",
15160                    "a b c g h i ",
15161                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15162                ],
15163                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15164            );
15165
15166            for completion in menu
15167                .completions
15168                .borrow()
15169                .iter() {
15170                    assert_eq!(
15171                        completion.label.filter_range,
15172                        0..completion.label.text.len(),
15173                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15174                    );
15175                }
15176        } else {
15177            panic!("expected completion menu to be open");
15178        }
15179    });
15180}
15181
15182#[gpui::test]
15183async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15184    init_test(cx, |_| {});
15185    let mut cx = EditorLspTestContext::new_rust(
15186        lsp::ServerCapabilities {
15187            completion_provider: Some(lsp::CompletionOptions {
15188                trigger_characters: Some(vec![".".to_string()]),
15189                ..Default::default()
15190            }),
15191            ..Default::default()
15192        },
15193        cx,
15194    )
15195    .await;
15196    cx.lsp
15197        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15198            Ok(Some(lsp::CompletionResponse::Array(vec![
15199                lsp::CompletionItem {
15200                    label: "first".into(),
15201                    ..Default::default()
15202                },
15203                lsp::CompletionItem {
15204                    label: "last".into(),
15205                    ..Default::default()
15206                },
15207            ])))
15208        });
15209    cx.set_state("variableˇ");
15210    cx.simulate_keystroke(".");
15211    cx.executor().run_until_parked();
15212
15213    cx.update_editor(|editor, _, _| {
15214        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15215        {
15216            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15217        } else {
15218            panic!("expected completion menu to be open");
15219        }
15220    });
15221
15222    cx.update_editor(|editor, window, cx| {
15223        editor.move_page_down(&MovePageDown::default(), window, cx);
15224        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15225        {
15226            assert!(
15227                menu.selected_item == 1,
15228                "expected PageDown to select the last item from the context menu"
15229            );
15230        } else {
15231            panic!("expected completion menu to stay open after PageDown");
15232        }
15233    });
15234
15235    cx.update_editor(|editor, window, cx| {
15236        editor.move_page_up(&MovePageUp::default(), window, cx);
15237        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15238        {
15239            assert!(
15240                menu.selected_item == 0,
15241                "expected PageUp to select the first item from the context menu"
15242            );
15243        } else {
15244            panic!("expected completion menu to stay open after PageUp");
15245        }
15246    });
15247}
15248
15249#[gpui::test]
15250async fn test_as_is_completions(cx: &mut TestAppContext) {
15251    init_test(cx, |_| {});
15252    let mut cx = EditorLspTestContext::new_rust(
15253        lsp::ServerCapabilities {
15254            completion_provider: Some(lsp::CompletionOptions {
15255                ..Default::default()
15256            }),
15257            ..Default::default()
15258        },
15259        cx,
15260    )
15261    .await;
15262    cx.lsp
15263        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15264            Ok(Some(lsp::CompletionResponse::Array(vec![
15265                lsp::CompletionItem {
15266                    label: "unsafe".into(),
15267                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15268                        range: lsp::Range {
15269                            start: lsp::Position {
15270                                line: 1,
15271                                character: 2,
15272                            },
15273                            end: lsp::Position {
15274                                line: 1,
15275                                character: 3,
15276                            },
15277                        },
15278                        new_text: "unsafe".to_string(),
15279                    })),
15280                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15281                    ..Default::default()
15282                },
15283            ])))
15284        });
15285    cx.set_state("fn a() {}\n");
15286    cx.executor().run_until_parked();
15287    cx.update_editor(|editor, window, cx| {
15288        editor.show_completions(
15289            &ShowCompletions {
15290                trigger: Some("\n".into()),
15291            },
15292            window,
15293            cx,
15294        );
15295    });
15296    cx.executor().run_until_parked();
15297
15298    cx.update_editor(|editor, window, cx| {
15299        editor.confirm_completion(&Default::default(), window, cx)
15300    });
15301    cx.executor().run_until_parked();
15302    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15303}
15304
15305#[gpui::test]
15306async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15307    init_test(cx, |_| {});
15308    let language =
15309        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15310    let mut cx = EditorLspTestContext::new(
15311        language,
15312        lsp::ServerCapabilities {
15313            completion_provider: Some(lsp::CompletionOptions {
15314                ..lsp::CompletionOptions::default()
15315            }),
15316            ..lsp::ServerCapabilities::default()
15317        },
15318        cx,
15319    )
15320    .await;
15321
15322    cx.set_state(
15323        "#ifndef BAR_H
15324#define BAR_H
15325
15326#include <stdbool.h>
15327
15328int fn_branch(bool do_branch1, bool do_branch2);
15329
15330#endif // BAR_H
15331ˇ",
15332    );
15333    cx.executor().run_until_parked();
15334    cx.update_editor(|editor, window, cx| {
15335        editor.handle_input("#", window, cx);
15336    });
15337    cx.executor().run_until_parked();
15338    cx.update_editor(|editor, window, cx| {
15339        editor.handle_input("i", window, cx);
15340    });
15341    cx.executor().run_until_parked();
15342    cx.update_editor(|editor, window, cx| {
15343        editor.handle_input("n", window, cx);
15344    });
15345    cx.executor().run_until_parked();
15346    cx.assert_editor_state(
15347        "#ifndef BAR_H
15348#define BAR_H
15349
15350#include <stdbool.h>
15351
15352int fn_branch(bool do_branch1, bool do_branch2);
15353
15354#endif // BAR_H
15355#inˇ",
15356    );
15357
15358    cx.lsp
15359        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15360            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15361                is_incomplete: false,
15362                item_defaults: None,
15363                items: vec![lsp::CompletionItem {
15364                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15365                    label_details: Some(lsp::CompletionItemLabelDetails {
15366                        detail: Some("header".to_string()),
15367                        description: None,
15368                    }),
15369                    label: " include".to_string(),
15370                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15371                        range: lsp::Range {
15372                            start: lsp::Position {
15373                                line: 8,
15374                                character: 1,
15375                            },
15376                            end: lsp::Position {
15377                                line: 8,
15378                                character: 1,
15379                            },
15380                        },
15381                        new_text: "include \"$0\"".to_string(),
15382                    })),
15383                    sort_text: Some("40b67681include".to_string()),
15384                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15385                    filter_text: Some("include".to_string()),
15386                    insert_text: Some("include \"$0\"".to_string()),
15387                    ..lsp::CompletionItem::default()
15388                }],
15389            })))
15390        });
15391    cx.update_editor(|editor, window, cx| {
15392        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15393    });
15394    cx.executor().run_until_parked();
15395    cx.update_editor(|editor, window, cx| {
15396        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15397    });
15398    cx.executor().run_until_parked();
15399    cx.assert_editor_state(
15400        "#ifndef BAR_H
15401#define BAR_H
15402
15403#include <stdbool.h>
15404
15405int fn_branch(bool do_branch1, bool do_branch2);
15406
15407#endif // BAR_H
15408#include \"ˇ\"",
15409    );
15410
15411    cx.lsp
15412        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15413            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15414                is_incomplete: true,
15415                item_defaults: None,
15416                items: vec![lsp::CompletionItem {
15417                    kind: Some(lsp::CompletionItemKind::FILE),
15418                    label: "AGL/".to_string(),
15419                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15420                        range: lsp::Range {
15421                            start: lsp::Position {
15422                                line: 8,
15423                                character: 10,
15424                            },
15425                            end: lsp::Position {
15426                                line: 8,
15427                                character: 11,
15428                            },
15429                        },
15430                        new_text: "AGL/".to_string(),
15431                    })),
15432                    sort_text: Some("40b67681AGL/".to_string()),
15433                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15434                    filter_text: Some("AGL/".to_string()),
15435                    insert_text: Some("AGL/".to_string()),
15436                    ..lsp::CompletionItem::default()
15437                }],
15438            })))
15439        });
15440    cx.update_editor(|editor, window, cx| {
15441        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15442    });
15443    cx.executor().run_until_parked();
15444    cx.update_editor(|editor, window, cx| {
15445        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15446    });
15447    cx.executor().run_until_parked();
15448    cx.assert_editor_state(
15449        r##"#ifndef BAR_H
15450#define BAR_H
15451
15452#include <stdbool.h>
15453
15454int fn_branch(bool do_branch1, bool do_branch2);
15455
15456#endif // BAR_H
15457#include "AGL/ˇ"##,
15458    );
15459
15460    cx.update_editor(|editor, window, cx| {
15461        editor.handle_input("\"", window, cx);
15462    });
15463    cx.executor().run_until_parked();
15464    cx.assert_editor_state(
15465        r##"#ifndef BAR_H
15466#define BAR_H
15467
15468#include <stdbool.h>
15469
15470int fn_branch(bool do_branch1, bool do_branch2);
15471
15472#endif // BAR_H
15473#include "AGL/"ˇ"##,
15474    );
15475}
15476
15477#[gpui::test]
15478async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15479    init_test(cx, |_| {});
15480
15481    let mut cx = EditorLspTestContext::new_rust(
15482        lsp::ServerCapabilities {
15483            completion_provider: Some(lsp::CompletionOptions {
15484                trigger_characters: Some(vec![".".to_string()]),
15485                resolve_provider: Some(true),
15486                ..Default::default()
15487            }),
15488            ..Default::default()
15489        },
15490        cx,
15491    )
15492    .await;
15493
15494    cx.set_state("fn main() { let a = 2ˇ; }");
15495    cx.simulate_keystroke(".");
15496    let completion_item = lsp::CompletionItem {
15497        label: "Some".into(),
15498        kind: Some(lsp::CompletionItemKind::SNIPPET),
15499        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15500        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15501            kind: lsp::MarkupKind::Markdown,
15502            value: "```rust\nSome(2)\n```".to_string(),
15503        })),
15504        deprecated: Some(false),
15505        sort_text: Some("Some".to_string()),
15506        filter_text: Some("Some".to_string()),
15507        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15508        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15509            range: lsp::Range {
15510                start: lsp::Position {
15511                    line: 0,
15512                    character: 22,
15513                },
15514                end: lsp::Position {
15515                    line: 0,
15516                    character: 22,
15517                },
15518            },
15519            new_text: "Some(2)".to_string(),
15520        })),
15521        additional_text_edits: Some(vec![lsp::TextEdit {
15522            range: lsp::Range {
15523                start: lsp::Position {
15524                    line: 0,
15525                    character: 20,
15526                },
15527                end: lsp::Position {
15528                    line: 0,
15529                    character: 22,
15530                },
15531            },
15532            new_text: "".to_string(),
15533        }]),
15534        ..Default::default()
15535    };
15536
15537    let closure_completion_item = completion_item.clone();
15538    let counter = Arc::new(AtomicUsize::new(0));
15539    let counter_clone = counter.clone();
15540    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15541        let task_completion_item = closure_completion_item.clone();
15542        counter_clone.fetch_add(1, atomic::Ordering::Release);
15543        async move {
15544            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15545                is_incomplete: true,
15546                item_defaults: None,
15547                items: vec![task_completion_item],
15548            })))
15549        }
15550    });
15551
15552    cx.condition(|editor, _| editor.context_menu_visible())
15553        .await;
15554    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15555    assert!(request.next().await.is_some());
15556    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15557
15558    cx.simulate_keystrokes("S o m");
15559    cx.condition(|editor, _| editor.context_menu_visible())
15560        .await;
15561    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15562    assert!(request.next().await.is_some());
15563    assert!(request.next().await.is_some());
15564    assert!(request.next().await.is_some());
15565    request.close();
15566    assert!(request.next().await.is_none());
15567    assert_eq!(
15568        counter.load(atomic::Ordering::Acquire),
15569        4,
15570        "With the completions menu open, only one LSP request should happen per input"
15571    );
15572}
15573
15574#[gpui::test]
15575async fn test_toggle_comment(cx: &mut TestAppContext) {
15576    init_test(cx, |_| {});
15577    let mut cx = EditorTestContext::new(cx).await;
15578    let language = Arc::new(Language::new(
15579        LanguageConfig {
15580            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15581            ..Default::default()
15582        },
15583        Some(tree_sitter_rust::LANGUAGE.into()),
15584    ));
15585    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15586
15587    // If multiple selections intersect a line, the line is only toggled once.
15588    cx.set_state(indoc! {"
15589        fn a() {
15590            «//b();
15591            ˇ»// «c();
15592            //ˇ»  d();
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            «b();
15601            c();
15602            ˇ» d();
15603        }
15604    "});
15605
15606    // The comment prefix is inserted at the same column for every line in a
15607    // selection.
15608    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15609
15610    cx.assert_editor_state(indoc! {"
15611        fn a() {
15612            // «b();
15613            // c();
15614            ˇ»//  d();
15615        }
15616    "});
15617
15618    // If a selection ends at the beginning of a line, that line is not toggled.
15619    cx.set_selections_state(indoc! {"
15620        fn a() {
15621            // b();
15622            «// c();
15623        ˇ»    //  d();
15624        }
15625    "});
15626
15627    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15628
15629    cx.assert_editor_state(indoc! {"
15630        fn a() {
15631            // b();
15632            «c();
15633        ˇ»    //  d();
15634        }
15635    "});
15636
15637    // If a selection span a single line and is empty, the line is toggled.
15638    cx.set_state(indoc! {"
15639        fn a() {
15640            a();
15641            b();
15642        ˇ
15643        }
15644    "});
15645
15646    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15647
15648    cx.assert_editor_state(indoc! {"
15649        fn a() {
15650            a();
15651            b();
15652        //•ˇ
15653        }
15654    "});
15655
15656    // If a selection span multiple lines, empty lines are not toggled.
15657    cx.set_state(indoc! {"
15658        fn a() {
15659            «a();
15660
15661            c();ˇ»
15662        }
15663    "});
15664
15665    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15666
15667    cx.assert_editor_state(indoc! {"
15668        fn a() {
15669            // «a();
15670
15671            // c();ˇ»
15672        }
15673    "});
15674
15675    // If a selection includes multiple comment prefixes, all lines are uncommented.
15676    cx.set_state(indoc! {"
15677        fn a() {
15678            «// a();
15679            /// b();
15680            //! c();ˇ»
15681        }
15682    "});
15683
15684    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15685
15686    cx.assert_editor_state(indoc! {"
15687        fn a() {
15688            «a();
15689            b();
15690            c();ˇ»
15691        }
15692    "});
15693}
15694
15695#[gpui::test]
15696async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15697    init_test(cx, |_| {});
15698    let mut cx = EditorTestContext::new(cx).await;
15699    let language = Arc::new(Language::new(
15700        LanguageConfig {
15701            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15702            ..Default::default()
15703        },
15704        Some(tree_sitter_rust::LANGUAGE.into()),
15705    ));
15706    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15707
15708    let toggle_comments = &ToggleComments {
15709        advance_downwards: false,
15710        ignore_indent: true,
15711    };
15712
15713    // If multiple selections intersect a line, the line is only toggled once.
15714    cx.set_state(indoc! {"
15715        fn a() {
15716        //    «b();
15717        //    c();
15718        //    ˇ» d();
15719        }
15720    "});
15721
15722    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15723
15724    cx.assert_editor_state(indoc! {"
15725        fn a() {
15726            «b();
15727            c();
15728            ˇ» d();
15729        }
15730    "});
15731
15732    // The comment prefix is inserted at the beginning of each line
15733    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15734
15735    cx.assert_editor_state(indoc! {"
15736        fn a() {
15737        //    «b();
15738        //    c();
15739        //    ˇ» d();
15740        }
15741    "});
15742
15743    // If a selection ends at the beginning of a line, that line is not toggled.
15744    cx.set_selections_state(indoc! {"
15745        fn a() {
15746        //    b();
15747        //    «c();
15748        ˇ»//     d();
15749        }
15750    "});
15751
15752    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15753
15754    cx.assert_editor_state(indoc! {"
15755        fn a() {
15756        //    b();
15757            «c();
15758        ˇ»//     d();
15759        }
15760    "});
15761
15762    // If a selection span a single line and is empty, the line is toggled.
15763    cx.set_state(indoc! {"
15764        fn a() {
15765            a();
15766            b();
15767        ˇ
15768        }
15769    "});
15770
15771    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15772
15773    cx.assert_editor_state(indoc! {"
15774        fn a() {
15775            a();
15776            b();
15777        //ˇ
15778        }
15779    "});
15780
15781    // If a selection span multiple lines, empty lines are not toggled.
15782    cx.set_state(indoc! {"
15783        fn a() {
15784            «a();
15785
15786            c();ˇ»
15787        }
15788    "});
15789
15790    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15791
15792    cx.assert_editor_state(indoc! {"
15793        fn a() {
15794        //    «a();
15795
15796        //    c();ˇ»
15797        }
15798    "});
15799
15800    // If a selection includes multiple comment prefixes, all lines are uncommented.
15801    cx.set_state(indoc! {"
15802        fn a() {
15803        //    «a();
15804        ///    b();
15805        //!    c();ˇ»
15806        }
15807    "});
15808
15809    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15810
15811    cx.assert_editor_state(indoc! {"
15812        fn a() {
15813            «a();
15814            b();
15815            c();ˇ»
15816        }
15817    "});
15818}
15819
15820#[gpui::test]
15821async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15822    init_test(cx, |_| {});
15823
15824    let language = Arc::new(Language::new(
15825        LanguageConfig {
15826            line_comments: vec!["// ".into()],
15827            ..Default::default()
15828        },
15829        Some(tree_sitter_rust::LANGUAGE.into()),
15830    ));
15831
15832    let mut cx = EditorTestContext::new(cx).await;
15833
15834    cx.language_registry().add(language.clone());
15835    cx.update_buffer(|buffer, cx| {
15836        buffer.set_language(Some(language), cx);
15837    });
15838
15839    let toggle_comments = &ToggleComments {
15840        advance_downwards: true,
15841        ignore_indent: false,
15842    };
15843
15844    // Single cursor on one line -> advance
15845    // Cursor moves horizontally 3 characters as well on non-blank line
15846    cx.set_state(indoc!(
15847        "fn a() {
15848             ˇdog();
15849             cat();
15850        }"
15851    ));
15852    cx.update_editor(|editor, window, cx| {
15853        editor.toggle_comments(toggle_comments, window, cx);
15854    });
15855    cx.assert_editor_state(indoc!(
15856        "fn a() {
15857             // dog();
15858             catˇ();
15859        }"
15860    ));
15861
15862    // Single selection on one line -> don't advance
15863    cx.set_state(indoc!(
15864        "fn a() {
15865             «dog()ˇ»;
15866             cat();
15867        }"
15868    ));
15869    cx.update_editor(|editor, window, cx| {
15870        editor.toggle_comments(toggle_comments, window, cx);
15871    });
15872    cx.assert_editor_state(indoc!(
15873        "fn a() {
15874             // «dog()ˇ»;
15875             cat();
15876        }"
15877    ));
15878
15879    // Multiple cursors on one line -> advance
15880    cx.set_state(indoc!(
15881        "fn a() {
15882             ˇdˇog();
15883             cat();
15884        }"
15885    ));
15886    cx.update_editor(|editor, window, cx| {
15887        editor.toggle_comments(toggle_comments, window, cx);
15888    });
15889    cx.assert_editor_state(indoc!(
15890        "fn a() {
15891             // dog();
15892             catˇ(ˇ);
15893        }"
15894    ));
15895
15896    // Multiple cursors on one line, with selection -> don't advance
15897    cx.set_state(indoc!(
15898        "fn a() {
15899             ˇdˇog«()ˇ»;
15900             cat();
15901        }"
15902    ));
15903    cx.update_editor(|editor, window, cx| {
15904        editor.toggle_comments(toggle_comments, window, cx);
15905    });
15906    cx.assert_editor_state(indoc!(
15907        "fn a() {
15908             // ˇdˇog«()ˇ»;
15909             cat();
15910        }"
15911    ));
15912
15913    // Single cursor on one line -> advance
15914    // Cursor moves to column 0 on blank line
15915    cx.set_state(indoc!(
15916        "fn a() {
15917             ˇdog();
15918
15919             cat();
15920        }"
15921    ));
15922    cx.update_editor(|editor, window, cx| {
15923        editor.toggle_comments(toggle_comments, window, cx);
15924    });
15925    cx.assert_editor_state(indoc!(
15926        "fn a() {
15927             // dog();
15928        ˇ
15929             cat();
15930        }"
15931    ));
15932
15933    // Single cursor on one line -> advance
15934    // Cursor starts and ends at column 0
15935    cx.set_state(indoc!(
15936        "fn a() {
15937         ˇ    dog();
15938             cat();
15939        }"
15940    ));
15941    cx.update_editor(|editor, window, cx| {
15942        editor.toggle_comments(toggle_comments, window, cx);
15943    });
15944    cx.assert_editor_state(indoc!(
15945        "fn a() {
15946             // dog();
15947         ˇ    cat();
15948        }"
15949    ));
15950}
15951
15952#[gpui::test]
15953async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15954    init_test(cx, |_| {});
15955
15956    let mut cx = EditorTestContext::new(cx).await;
15957
15958    let html_language = Arc::new(
15959        Language::new(
15960            LanguageConfig {
15961                name: "HTML".into(),
15962                block_comment: Some(BlockCommentConfig {
15963                    start: "<!-- ".into(),
15964                    prefix: "".into(),
15965                    end: " -->".into(),
15966                    tab_size: 0,
15967                }),
15968                ..Default::default()
15969            },
15970            Some(tree_sitter_html::LANGUAGE.into()),
15971        )
15972        .with_injection_query(
15973            r#"
15974            (script_element
15975                (raw_text) @injection.content
15976                (#set! injection.language "javascript"))
15977            "#,
15978        )
15979        .unwrap(),
15980    );
15981
15982    let javascript_language = Arc::new(Language::new(
15983        LanguageConfig {
15984            name: "JavaScript".into(),
15985            line_comments: vec!["// ".into()],
15986            ..Default::default()
15987        },
15988        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15989    ));
15990
15991    cx.language_registry().add(html_language.clone());
15992    cx.language_registry().add(javascript_language);
15993    cx.update_buffer(|buffer, cx| {
15994        buffer.set_language(Some(html_language), cx);
15995    });
15996
15997    // Toggle comments for empty selections
15998    cx.set_state(
15999        &r#"
16000            <p>A</p>ˇ
16001            <p>B</p>ˇ
16002            <p>C</p>ˇ
16003        "#
16004        .unindent(),
16005    );
16006    cx.update_editor(|editor, window, cx| {
16007        editor.toggle_comments(&ToggleComments::default(), window, cx)
16008    });
16009    cx.assert_editor_state(
16010        &r#"
16011            <!-- <p>A</p>ˇ -->
16012            <!-- <p>B</p>ˇ -->
16013            <!-- <p>C</p>ˇ -->
16014        "#
16015        .unindent(),
16016    );
16017    cx.update_editor(|editor, window, cx| {
16018        editor.toggle_comments(&ToggleComments::default(), window, cx)
16019    });
16020    cx.assert_editor_state(
16021        &r#"
16022            <p>A</p>ˇ
16023            <p>B</p>ˇ
16024            <p>C</p>ˇ
16025        "#
16026        .unindent(),
16027    );
16028
16029    // Toggle comments for mixture of empty and non-empty selections, where
16030    // multiple selections occupy a given line.
16031    cx.set_state(
16032        &r#"
16033            <p>A«</p>
16034            <p>ˇ»B</p>ˇ
16035            <p>C«</p>
16036            <p>ˇ»D</p>ˇ
16037        "#
16038        .unindent(),
16039    );
16040
16041    cx.update_editor(|editor, window, cx| {
16042        editor.toggle_comments(&ToggleComments::default(), window, cx)
16043    });
16044    cx.assert_editor_state(
16045        &r#"
16046            <!-- <p>A«</p>
16047            <p>ˇ»B</p>ˇ -->
16048            <!-- <p>C«</p>
16049            <p>ˇ»D</p>ˇ -->
16050        "#
16051        .unindent(),
16052    );
16053    cx.update_editor(|editor, window, cx| {
16054        editor.toggle_comments(&ToggleComments::default(), window, cx)
16055    });
16056    cx.assert_editor_state(
16057        &r#"
16058            <p>A«</p>
16059            <p>ˇ»B</p>ˇ
16060            <p>C«</p>
16061            <p>ˇ»D</p>ˇ
16062        "#
16063        .unindent(),
16064    );
16065
16066    // Toggle comments when different languages are active for different
16067    // selections.
16068    cx.set_state(
16069        &r#"
16070            ˇ<script>
16071                ˇvar x = new Y();
16072            ˇ</script>
16073        "#
16074        .unindent(),
16075    );
16076    cx.executor().run_until_parked();
16077    cx.update_editor(|editor, window, cx| {
16078        editor.toggle_comments(&ToggleComments::default(), window, cx)
16079    });
16080    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16081    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16082    cx.assert_editor_state(
16083        &r#"
16084            <!-- ˇ<script> -->
16085                // ˇvar x = new Y();
16086            <!-- ˇ</script> -->
16087        "#
16088        .unindent(),
16089    );
16090}
16091
16092#[gpui::test]
16093fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16094    init_test(cx, |_| {});
16095
16096    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16097    let multibuffer = cx.new(|cx| {
16098        let mut multibuffer = MultiBuffer::new(ReadWrite);
16099        multibuffer.push_excerpts(
16100            buffer.clone(),
16101            [
16102                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16103                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16104            ],
16105            cx,
16106        );
16107        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16108        multibuffer
16109    });
16110
16111    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16112    editor.update_in(cx, |editor, window, cx| {
16113        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16114        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16115            s.select_ranges([
16116                Point::new(0, 0)..Point::new(0, 0),
16117                Point::new(1, 0)..Point::new(1, 0),
16118            ])
16119        });
16120
16121        editor.handle_input("X", window, cx);
16122        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16123        assert_eq!(
16124            editor.selections.ranges(&editor.display_snapshot(cx)),
16125            [
16126                Point::new(0, 1)..Point::new(0, 1),
16127                Point::new(1, 1)..Point::new(1, 1),
16128            ]
16129        );
16130
16131        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16133            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16134        });
16135        editor.backspace(&Default::default(), window, cx);
16136        assert_eq!(editor.text(cx), "Xa\nbbb");
16137        assert_eq!(
16138            editor.selections.ranges(&editor.display_snapshot(cx)),
16139            [Point::new(1, 0)..Point::new(1, 0)]
16140        );
16141
16142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16143            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16144        });
16145        editor.backspace(&Default::default(), window, cx);
16146        assert_eq!(editor.text(cx), "X\nbb");
16147        assert_eq!(
16148            editor.selections.ranges(&editor.display_snapshot(cx)),
16149            [Point::new(0, 1)..Point::new(0, 1)]
16150        );
16151    });
16152}
16153
16154#[gpui::test]
16155fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16156    init_test(cx, |_| {});
16157
16158    let markers = vec![('[', ']').into(), ('(', ')').into()];
16159    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16160        indoc! {"
16161            [aaaa
16162            (bbbb]
16163            cccc)",
16164        },
16165        markers.clone(),
16166    );
16167    let excerpt_ranges = markers.into_iter().map(|marker| {
16168        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16169        ExcerptRange::new(context)
16170    });
16171    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16172    let multibuffer = cx.new(|cx| {
16173        let mut multibuffer = MultiBuffer::new(ReadWrite);
16174        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16175        multibuffer
16176    });
16177
16178    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16179    editor.update_in(cx, |editor, window, cx| {
16180        let (expected_text, selection_ranges) = marked_text_ranges(
16181            indoc! {"
16182                aaaa
16183                bˇbbb
16184                bˇbbˇb
16185                cccc"
16186            },
16187            true,
16188        );
16189        assert_eq!(editor.text(cx), expected_text);
16190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16191            s.select_ranges(selection_ranges)
16192        });
16193
16194        editor.handle_input("X", window, cx);
16195
16196        let (expected_text, expected_selections) = marked_text_ranges(
16197            indoc! {"
16198                aaaa
16199                bXˇbbXb
16200                bXˇbbXˇb
16201                cccc"
16202            },
16203            false,
16204        );
16205        assert_eq!(editor.text(cx), expected_text);
16206        assert_eq!(
16207            editor.selections.ranges(&editor.display_snapshot(cx)),
16208            expected_selections
16209        );
16210
16211        editor.newline(&Newline, window, cx);
16212        let (expected_text, expected_selections) = marked_text_ranges(
16213            indoc! {"
16214                aaaa
16215                bX
16216                ˇbbX
16217                b
16218                bX
16219                ˇbbX
16220                ˇb
16221                cccc"
16222            },
16223            false,
16224        );
16225        assert_eq!(editor.text(cx), expected_text);
16226        assert_eq!(
16227            editor.selections.ranges(&editor.display_snapshot(cx)),
16228            expected_selections
16229        );
16230    });
16231}
16232
16233#[gpui::test]
16234fn test_refresh_selections(cx: &mut TestAppContext) {
16235    init_test(cx, |_| {});
16236
16237    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16238    let mut excerpt1_id = None;
16239    let multibuffer = cx.new(|cx| {
16240        let mut multibuffer = MultiBuffer::new(ReadWrite);
16241        excerpt1_id = multibuffer
16242            .push_excerpts(
16243                buffer.clone(),
16244                [
16245                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16246                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16247                ],
16248                cx,
16249            )
16250            .into_iter()
16251            .next();
16252        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16253        multibuffer
16254    });
16255
16256    let editor = cx.add_window(|window, cx| {
16257        let mut editor = build_editor(multibuffer.clone(), window, cx);
16258        let snapshot = editor.snapshot(window, cx);
16259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16260            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16261        });
16262        editor.begin_selection(
16263            Point::new(2, 1).to_display_point(&snapshot),
16264            true,
16265            1,
16266            window,
16267            cx,
16268        );
16269        assert_eq!(
16270            editor.selections.ranges(&editor.display_snapshot(cx)),
16271            [
16272                Point::new(1, 3)..Point::new(1, 3),
16273                Point::new(2, 1)..Point::new(2, 1),
16274            ]
16275        );
16276        editor
16277    });
16278
16279    // Refreshing selections is a no-op when excerpts haven't changed.
16280    _ = editor.update(cx, |editor, window, cx| {
16281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16282        assert_eq!(
16283            editor.selections.ranges(&editor.display_snapshot(cx)),
16284            [
16285                Point::new(1, 3)..Point::new(1, 3),
16286                Point::new(2, 1)..Point::new(2, 1),
16287            ]
16288        );
16289    });
16290
16291    multibuffer.update(cx, |multibuffer, cx| {
16292        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16293    });
16294    _ = editor.update(cx, |editor, window, cx| {
16295        // Removing an excerpt causes the first selection to become degenerate.
16296        assert_eq!(
16297            editor.selections.ranges(&editor.display_snapshot(cx)),
16298            [
16299                Point::new(0, 0)..Point::new(0, 0),
16300                Point::new(0, 1)..Point::new(0, 1)
16301            ]
16302        );
16303
16304        // Refreshing selections will relocate the first selection to the original buffer
16305        // location.
16306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16307        assert_eq!(
16308            editor.selections.ranges(&editor.display_snapshot(cx)),
16309            [
16310                Point::new(0, 1)..Point::new(0, 1),
16311                Point::new(0, 3)..Point::new(0, 3)
16312            ]
16313        );
16314        assert!(editor.selections.pending_anchor().is_some());
16315    });
16316}
16317
16318#[gpui::test]
16319fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16320    init_test(cx, |_| {});
16321
16322    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16323    let mut excerpt1_id = None;
16324    let multibuffer = cx.new(|cx| {
16325        let mut multibuffer = MultiBuffer::new(ReadWrite);
16326        excerpt1_id = multibuffer
16327            .push_excerpts(
16328                buffer.clone(),
16329                [
16330                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16331                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16332                ],
16333                cx,
16334            )
16335            .into_iter()
16336            .next();
16337        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16338        multibuffer
16339    });
16340
16341    let editor = cx.add_window(|window, cx| {
16342        let mut editor = build_editor(multibuffer.clone(), window, cx);
16343        let snapshot = editor.snapshot(window, cx);
16344        editor.begin_selection(
16345            Point::new(1, 3).to_display_point(&snapshot),
16346            false,
16347            1,
16348            window,
16349            cx,
16350        );
16351        assert_eq!(
16352            editor.selections.ranges(&editor.display_snapshot(cx)),
16353            [Point::new(1, 3)..Point::new(1, 3)]
16354        );
16355        editor
16356    });
16357
16358    multibuffer.update(cx, |multibuffer, cx| {
16359        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16360    });
16361    _ = editor.update(cx, |editor, window, cx| {
16362        assert_eq!(
16363            editor.selections.ranges(&editor.display_snapshot(cx)),
16364            [Point::new(0, 0)..Point::new(0, 0)]
16365        );
16366
16367        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16368        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16369        assert_eq!(
16370            editor.selections.ranges(&editor.display_snapshot(cx)),
16371            [Point::new(0, 3)..Point::new(0, 3)]
16372        );
16373        assert!(editor.selections.pending_anchor().is_some());
16374    });
16375}
16376
16377#[gpui::test]
16378async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16379    init_test(cx, |_| {});
16380
16381    let language = Arc::new(
16382        Language::new(
16383            LanguageConfig {
16384                brackets: BracketPairConfig {
16385                    pairs: vec![
16386                        BracketPair {
16387                            start: "{".to_string(),
16388                            end: "}".to_string(),
16389                            close: true,
16390                            surround: true,
16391                            newline: true,
16392                        },
16393                        BracketPair {
16394                            start: "/* ".to_string(),
16395                            end: " */".to_string(),
16396                            close: true,
16397                            surround: true,
16398                            newline: true,
16399                        },
16400                    ],
16401                    ..Default::default()
16402                },
16403                ..Default::default()
16404            },
16405            Some(tree_sitter_rust::LANGUAGE.into()),
16406        )
16407        .with_indents_query("")
16408        .unwrap(),
16409    );
16410
16411    let text = concat!(
16412        "{   }\n",     //
16413        "  x\n",       //
16414        "  /*   */\n", //
16415        "x\n",         //
16416        "{{} }\n",     //
16417    );
16418
16419    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16420    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16421    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16422    editor
16423        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16424        .await;
16425
16426    editor.update_in(cx, |editor, window, cx| {
16427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16428            s.select_display_ranges([
16429                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16430                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16431                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16432            ])
16433        });
16434        editor.newline(&Newline, window, cx);
16435
16436        assert_eq!(
16437            editor.buffer().read(cx).read(cx).text(),
16438            concat!(
16439                "{ \n",    // Suppress rustfmt
16440                "\n",      //
16441                "}\n",     //
16442                "  x\n",   //
16443                "  /* \n", //
16444                "  \n",    //
16445                "  */\n",  //
16446                "x\n",     //
16447                "{{} \n",  //
16448                "}\n",     //
16449            )
16450        );
16451    });
16452}
16453
16454#[gpui::test]
16455fn test_highlighted_ranges(cx: &mut TestAppContext) {
16456    init_test(cx, |_| {});
16457
16458    let editor = cx.add_window(|window, cx| {
16459        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16460        build_editor(buffer, window, cx)
16461    });
16462
16463    _ = editor.update(cx, |editor, window, cx| {
16464        struct Type1;
16465        struct Type2;
16466
16467        let buffer = editor.buffer.read(cx).snapshot(cx);
16468
16469        let anchor_range =
16470            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16471
16472        editor.highlight_background::<Type1>(
16473            &[
16474                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16475                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16476                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16477                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16478            ],
16479            |_| Hsla::red(),
16480            cx,
16481        );
16482        editor.highlight_background::<Type2>(
16483            &[
16484                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16485                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16486                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16487                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16488            ],
16489            |_| Hsla::green(),
16490            cx,
16491        );
16492
16493        let snapshot = editor.snapshot(window, cx);
16494        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16495            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16496            &snapshot,
16497            cx.theme(),
16498        );
16499        assert_eq!(
16500            highlighted_ranges,
16501            &[
16502                (
16503                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16504                    Hsla::green(),
16505                ),
16506                (
16507                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16508                    Hsla::red(),
16509                ),
16510                (
16511                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16512                    Hsla::green(),
16513                ),
16514                (
16515                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16516                    Hsla::red(),
16517                ),
16518            ]
16519        );
16520        assert_eq!(
16521            editor.sorted_background_highlights_in_range(
16522                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16523                &snapshot,
16524                cx.theme(),
16525            ),
16526            &[(
16527                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16528                Hsla::red(),
16529            )]
16530        );
16531    });
16532}
16533
16534#[gpui::test]
16535async fn test_following(cx: &mut TestAppContext) {
16536    init_test(cx, |_| {});
16537
16538    let fs = FakeFs::new(cx.executor());
16539    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16540
16541    let buffer = project.update(cx, |project, cx| {
16542        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16543        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16544    });
16545    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16546    let follower = cx.update(|cx| {
16547        cx.open_window(
16548            WindowOptions {
16549                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16550                    gpui::Point::new(px(0.), px(0.)),
16551                    gpui::Point::new(px(10.), px(80.)),
16552                ))),
16553                ..Default::default()
16554            },
16555            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16556        )
16557        .unwrap()
16558    });
16559
16560    let is_still_following = Rc::new(RefCell::new(true));
16561    let follower_edit_event_count = Rc::new(RefCell::new(0));
16562    let pending_update = Rc::new(RefCell::new(None));
16563    let leader_entity = leader.root(cx).unwrap();
16564    let follower_entity = follower.root(cx).unwrap();
16565    _ = follower.update(cx, {
16566        let update = pending_update.clone();
16567        let is_still_following = is_still_following.clone();
16568        let follower_edit_event_count = follower_edit_event_count.clone();
16569        |_, window, cx| {
16570            cx.subscribe_in(
16571                &leader_entity,
16572                window,
16573                move |_, leader, event, window, cx| {
16574                    leader.read(cx).add_event_to_update_proto(
16575                        event,
16576                        &mut update.borrow_mut(),
16577                        window,
16578                        cx,
16579                    );
16580                },
16581            )
16582            .detach();
16583
16584            cx.subscribe_in(
16585                &follower_entity,
16586                window,
16587                move |_, _, event: &EditorEvent, _window, _cx| {
16588                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16589                        *is_still_following.borrow_mut() = false;
16590                    }
16591
16592                    if let EditorEvent::BufferEdited = event {
16593                        *follower_edit_event_count.borrow_mut() += 1;
16594                    }
16595                },
16596            )
16597            .detach();
16598        }
16599    });
16600
16601    // Update the selections only
16602    _ = leader.update(cx, |leader, window, cx| {
16603        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16604            s.select_ranges([1..1])
16605        });
16606    });
16607    follower
16608        .update(cx, |follower, window, cx| {
16609            follower.apply_update_proto(
16610                &project,
16611                pending_update.borrow_mut().take().unwrap(),
16612                window,
16613                cx,
16614            )
16615        })
16616        .unwrap()
16617        .await
16618        .unwrap();
16619    _ = follower.update(cx, |follower, _, cx| {
16620        assert_eq!(
16621            follower.selections.ranges(&follower.display_snapshot(cx)),
16622            vec![1..1]
16623        );
16624    });
16625    assert!(*is_still_following.borrow());
16626    assert_eq!(*follower_edit_event_count.borrow(), 0);
16627
16628    // Update the scroll position only
16629    _ = leader.update(cx, |leader, window, cx| {
16630        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16631    });
16632    follower
16633        .update(cx, |follower, window, cx| {
16634            follower.apply_update_proto(
16635                &project,
16636                pending_update.borrow_mut().take().unwrap(),
16637                window,
16638                cx,
16639            )
16640        })
16641        .unwrap()
16642        .await
16643        .unwrap();
16644    assert_eq!(
16645        follower
16646            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16647            .unwrap(),
16648        gpui::Point::new(1.5, 3.5)
16649    );
16650    assert!(*is_still_following.borrow());
16651    assert_eq!(*follower_edit_event_count.borrow(), 0);
16652
16653    // Update the selections and scroll position. The follower's scroll position is updated
16654    // via autoscroll, not via the leader's exact scroll position.
16655    _ = leader.update(cx, |leader, window, cx| {
16656        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16657            s.select_ranges([0..0])
16658        });
16659        leader.request_autoscroll(Autoscroll::newest(), cx);
16660        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16661    });
16662    follower
16663        .update(cx, |follower, window, cx| {
16664            follower.apply_update_proto(
16665                &project,
16666                pending_update.borrow_mut().take().unwrap(),
16667                window,
16668                cx,
16669            )
16670        })
16671        .unwrap()
16672        .await
16673        .unwrap();
16674    _ = follower.update(cx, |follower, _, cx| {
16675        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16676        assert_eq!(
16677            follower.selections.ranges(&follower.display_snapshot(cx)),
16678            vec![0..0]
16679        );
16680    });
16681    assert!(*is_still_following.borrow());
16682
16683    // Creating a pending selection that precedes another selection
16684    _ = leader.update(cx, |leader, window, cx| {
16685        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16686            s.select_ranges([1..1])
16687        });
16688        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16689    });
16690    follower
16691        .update(cx, |follower, window, cx| {
16692            follower.apply_update_proto(
16693                &project,
16694                pending_update.borrow_mut().take().unwrap(),
16695                window,
16696                cx,
16697            )
16698        })
16699        .unwrap()
16700        .await
16701        .unwrap();
16702    _ = follower.update(cx, |follower, _, cx| {
16703        assert_eq!(
16704            follower.selections.ranges(&follower.display_snapshot(cx)),
16705            vec![0..0, 1..1]
16706        );
16707    });
16708    assert!(*is_still_following.borrow());
16709
16710    // Extend the pending selection so that it surrounds another selection
16711    _ = leader.update(cx, |leader, window, cx| {
16712        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16713    });
16714    follower
16715        .update(cx, |follower, window, cx| {
16716            follower.apply_update_proto(
16717                &project,
16718                pending_update.borrow_mut().take().unwrap(),
16719                window,
16720                cx,
16721            )
16722        })
16723        .unwrap()
16724        .await
16725        .unwrap();
16726    _ = follower.update(cx, |follower, _, cx| {
16727        assert_eq!(
16728            follower.selections.ranges(&follower.display_snapshot(cx)),
16729            vec![0..2]
16730        );
16731    });
16732
16733    // Scrolling locally breaks the follow
16734    _ = follower.update(cx, |follower, window, cx| {
16735        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16736        follower.set_scroll_anchor(
16737            ScrollAnchor {
16738                anchor: top_anchor,
16739                offset: gpui::Point::new(0.0, 0.5),
16740            },
16741            window,
16742            cx,
16743        );
16744    });
16745    assert!(!(*is_still_following.borrow()));
16746}
16747
16748#[gpui::test]
16749async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16750    init_test(cx, |_| {});
16751
16752    let fs = FakeFs::new(cx.executor());
16753    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16754    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16755    let pane = workspace
16756        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16757        .unwrap();
16758
16759    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16760
16761    let leader = pane.update_in(cx, |_, window, cx| {
16762        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16763        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16764    });
16765
16766    // Start following the editor when it has no excerpts.
16767    let mut state_message =
16768        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16769    let workspace_entity = workspace.root(cx).unwrap();
16770    let follower_1 = cx
16771        .update_window(*workspace.deref(), |_, window, cx| {
16772            Editor::from_state_proto(
16773                workspace_entity,
16774                ViewId {
16775                    creator: CollaboratorId::PeerId(PeerId::default()),
16776                    id: 0,
16777                },
16778                &mut state_message,
16779                window,
16780                cx,
16781            )
16782        })
16783        .unwrap()
16784        .unwrap()
16785        .await
16786        .unwrap();
16787
16788    let update_message = Rc::new(RefCell::new(None));
16789    follower_1.update_in(cx, {
16790        let update = update_message.clone();
16791        |_, window, cx| {
16792            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16793                leader.read(cx).add_event_to_update_proto(
16794                    event,
16795                    &mut update.borrow_mut(),
16796                    window,
16797                    cx,
16798                );
16799            })
16800            .detach();
16801        }
16802    });
16803
16804    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16805        (
16806            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16807            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16808        )
16809    });
16810
16811    // Insert some excerpts.
16812    leader.update(cx, |leader, cx| {
16813        leader.buffer.update(cx, |multibuffer, cx| {
16814            multibuffer.set_excerpts_for_path(
16815                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16816                buffer_1.clone(),
16817                vec![
16818                    Point::row_range(0..3),
16819                    Point::row_range(1..6),
16820                    Point::row_range(12..15),
16821                ],
16822                0,
16823                cx,
16824            );
16825            multibuffer.set_excerpts_for_path(
16826                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16827                buffer_2.clone(),
16828                vec![Point::row_range(0..6), Point::row_range(8..12)],
16829                0,
16830                cx,
16831            );
16832        });
16833    });
16834
16835    // Apply the update of adding the excerpts.
16836    follower_1
16837        .update_in(cx, |follower, window, cx| {
16838            follower.apply_update_proto(
16839                &project,
16840                update_message.borrow().clone().unwrap(),
16841                window,
16842                cx,
16843            )
16844        })
16845        .await
16846        .unwrap();
16847    assert_eq!(
16848        follower_1.update(cx, |editor, cx| editor.text(cx)),
16849        leader.update(cx, |editor, cx| editor.text(cx))
16850    );
16851    update_message.borrow_mut().take();
16852
16853    // Start following separately after it already has excerpts.
16854    let mut state_message =
16855        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16856    let workspace_entity = workspace.root(cx).unwrap();
16857    let follower_2 = cx
16858        .update_window(*workspace.deref(), |_, window, cx| {
16859            Editor::from_state_proto(
16860                workspace_entity,
16861                ViewId {
16862                    creator: CollaboratorId::PeerId(PeerId::default()),
16863                    id: 0,
16864                },
16865                &mut state_message,
16866                window,
16867                cx,
16868            )
16869        })
16870        .unwrap()
16871        .unwrap()
16872        .await
16873        .unwrap();
16874    assert_eq!(
16875        follower_2.update(cx, |editor, cx| editor.text(cx)),
16876        leader.update(cx, |editor, cx| editor.text(cx))
16877    );
16878
16879    // Remove some excerpts.
16880    leader.update(cx, |leader, cx| {
16881        leader.buffer.update(cx, |multibuffer, cx| {
16882            let excerpt_ids = multibuffer.excerpt_ids();
16883            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16884            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16885        });
16886    });
16887
16888    // Apply the update of removing the excerpts.
16889    follower_1
16890        .update_in(cx, |follower, window, cx| {
16891            follower.apply_update_proto(
16892                &project,
16893                update_message.borrow().clone().unwrap(),
16894                window,
16895                cx,
16896            )
16897        })
16898        .await
16899        .unwrap();
16900    follower_2
16901        .update_in(cx, |follower, window, cx| {
16902            follower.apply_update_proto(
16903                &project,
16904                update_message.borrow().clone().unwrap(),
16905                window,
16906                cx,
16907            )
16908        })
16909        .await
16910        .unwrap();
16911    update_message.borrow_mut().take();
16912    assert_eq!(
16913        follower_1.update(cx, |editor, cx| editor.text(cx)),
16914        leader.update(cx, |editor, cx| editor.text(cx))
16915    );
16916}
16917
16918#[gpui::test]
16919async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16920    init_test(cx, |_| {});
16921
16922    let mut cx = EditorTestContext::new(cx).await;
16923    let lsp_store =
16924        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16925
16926    cx.set_state(indoc! {"
16927        ˇfn func(abc def: i32) -> u32 {
16928        }
16929    "});
16930
16931    cx.update(|_, cx| {
16932        lsp_store.update(cx, |lsp_store, cx| {
16933            lsp_store
16934                .update_diagnostics(
16935                    LanguageServerId(0),
16936                    lsp::PublishDiagnosticsParams {
16937                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16938                        version: None,
16939                        diagnostics: vec![
16940                            lsp::Diagnostic {
16941                                range: lsp::Range::new(
16942                                    lsp::Position::new(0, 11),
16943                                    lsp::Position::new(0, 12),
16944                                ),
16945                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16946                                ..Default::default()
16947                            },
16948                            lsp::Diagnostic {
16949                                range: lsp::Range::new(
16950                                    lsp::Position::new(0, 12),
16951                                    lsp::Position::new(0, 15),
16952                                ),
16953                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16954                                ..Default::default()
16955                            },
16956                            lsp::Diagnostic {
16957                                range: lsp::Range::new(
16958                                    lsp::Position::new(0, 25),
16959                                    lsp::Position::new(0, 28),
16960                                ),
16961                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16962                                ..Default::default()
16963                            },
16964                        ],
16965                    },
16966                    None,
16967                    DiagnosticSourceKind::Pushed,
16968                    &[],
16969                    cx,
16970                )
16971                .unwrap()
16972        });
16973    });
16974
16975    executor.run_until_parked();
16976
16977    cx.update_editor(|editor, window, cx| {
16978        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16979    });
16980
16981    cx.assert_editor_state(indoc! {"
16982        fn func(abc def: i32) -> ˇu32 {
16983        }
16984    "});
16985
16986    cx.update_editor(|editor, window, cx| {
16987        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16988    });
16989
16990    cx.assert_editor_state(indoc! {"
16991        fn func(abc ˇdef: i32) -> u32 {
16992        }
16993    "});
16994
16995    cx.update_editor(|editor, window, cx| {
16996        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16997    });
16998
16999    cx.assert_editor_state(indoc! {"
17000        fn func(abcˇ def: i32) -> u32 {
17001        }
17002    "});
17003
17004    cx.update_editor(|editor, window, cx| {
17005        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17006    });
17007
17008    cx.assert_editor_state(indoc! {"
17009        fn func(abc def: i32) -> ˇu32 {
17010        }
17011    "});
17012}
17013
17014#[gpui::test]
17015async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17016    init_test(cx, |_| {});
17017
17018    let mut cx = EditorTestContext::new(cx).await;
17019
17020    let diff_base = r#"
17021        use some::mod;
17022
17023        const A: u32 = 42;
17024
17025        fn main() {
17026            println!("hello");
17027
17028            println!("world");
17029        }
17030        "#
17031    .unindent();
17032
17033    // Edits are modified, removed, modified, added
17034    cx.set_state(
17035        &r#"
17036        use some::modified;
17037
17038        ˇ
17039        fn main() {
17040            println!("hello there");
17041
17042            println!("around the");
17043            println!("world");
17044        }
17045        "#
17046        .unindent(),
17047    );
17048
17049    cx.set_head_text(&diff_base);
17050    executor.run_until_parked();
17051
17052    cx.update_editor(|editor, window, cx| {
17053        //Wrap around the bottom of the buffer
17054        for _ in 0..3 {
17055            editor.go_to_next_hunk(&GoToHunk, window, cx);
17056        }
17057    });
17058
17059    cx.assert_editor_state(
17060        &r#"
17061        ˇuse some::modified;
17062
17063
17064        fn main() {
17065            println!("hello there");
17066
17067            println!("around the");
17068            println!("world");
17069        }
17070        "#
17071        .unindent(),
17072    );
17073
17074    cx.update_editor(|editor, window, cx| {
17075        //Wrap around the top of the buffer
17076        for _ in 0..2 {
17077            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17078        }
17079    });
17080
17081    cx.assert_editor_state(
17082        &r#"
17083        use some::modified;
17084
17085
17086        fn main() {
17087        ˇ    println!("hello there");
17088
17089            println!("around the");
17090            println!("world");
17091        }
17092        "#
17093        .unindent(),
17094    );
17095
17096    cx.update_editor(|editor, window, cx| {
17097        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17098    });
17099
17100    cx.assert_editor_state(
17101        &r#"
17102        use some::modified;
17103
17104        ˇ
17105        fn main() {
17106            println!("hello there");
17107
17108            println!("around the");
17109            println!("world");
17110        }
17111        "#
17112        .unindent(),
17113    );
17114
17115    cx.update_editor(|editor, window, cx| {
17116        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17117    });
17118
17119    cx.assert_editor_state(
17120        &r#"
17121        ˇuse some::modified;
17122
17123
17124        fn main() {
17125            println!("hello there");
17126
17127            println!("around the");
17128            println!("world");
17129        }
17130        "#
17131        .unindent(),
17132    );
17133
17134    cx.update_editor(|editor, window, cx| {
17135        for _ in 0..2 {
17136            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17137        }
17138    });
17139
17140    cx.assert_editor_state(
17141        &r#"
17142        use some::modified;
17143
17144
17145        fn main() {
17146        ˇ    println!("hello there");
17147
17148            println!("around the");
17149            println!("world");
17150        }
17151        "#
17152        .unindent(),
17153    );
17154
17155    cx.update_editor(|editor, window, cx| {
17156        editor.fold(&Fold, window, cx);
17157    });
17158
17159    cx.update_editor(|editor, window, cx| {
17160        editor.go_to_next_hunk(&GoToHunk, window, cx);
17161    });
17162
17163    cx.assert_editor_state(
17164        &r#"
17165        ˇuse some::modified;
17166
17167
17168        fn main() {
17169            println!("hello there");
17170
17171            println!("around the");
17172            println!("world");
17173        }
17174        "#
17175        .unindent(),
17176    );
17177}
17178
17179#[test]
17180fn test_split_words() {
17181    fn split(text: &str) -> Vec<&str> {
17182        split_words(text).collect()
17183    }
17184
17185    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17186    assert_eq!(split("hello_world"), &["hello_", "world"]);
17187    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17188    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17189    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17190    assert_eq!(split("helloworld"), &["helloworld"]);
17191
17192    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17193}
17194
17195#[gpui::test]
17196async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17197    init_test(cx, |_| {});
17198
17199    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17200    let mut assert = |before, after| {
17201        let _state_context = cx.set_state(before);
17202        cx.run_until_parked();
17203        cx.update_editor(|editor, window, cx| {
17204            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17205        });
17206        cx.run_until_parked();
17207        cx.assert_editor_state(after);
17208    };
17209
17210    // Outside bracket jumps to outside of matching bracket
17211    assert("console.logˇ(var);", "console.log(var)ˇ;");
17212    assert("console.log(var)ˇ;", "console.logˇ(var);");
17213
17214    // Inside bracket jumps to inside of matching bracket
17215    assert("console.log(ˇvar);", "console.log(varˇ);");
17216    assert("console.log(varˇ);", "console.log(ˇvar);");
17217
17218    // When outside a bracket and inside, favor jumping to the inside bracket
17219    assert(
17220        "console.log('foo', [1, 2, 3]ˇ);",
17221        "console.log(ˇ'foo', [1, 2, 3]);",
17222    );
17223    assert(
17224        "console.log(ˇ'foo', [1, 2, 3]);",
17225        "console.log('foo', [1, 2, 3]ˇ);",
17226    );
17227
17228    // Bias forward if two options are equally likely
17229    assert(
17230        "let result = curried_fun()ˇ();",
17231        "let result = curried_fun()()ˇ;",
17232    );
17233
17234    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17235    assert(
17236        indoc! {"
17237            function test() {
17238                console.log('test')ˇ
17239            }"},
17240        indoc! {"
17241            function test() {
17242                console.logˇ('test')
17243            }"},
17244    );
17245}
17246
17247#[gpui::test]
17248async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17249    init_test(cx, |_| {});
17250
17251    let fs = FakeFs::new(cx.executor());
17252    fs.insert_tree(
17253        path!("/a"),
17254        json!({
17255            "main.rs": "fn main() { let a = 5; }",
17256            "other.rs": "// Test file",
17257        }),
17258    )
17259    .await;
17260    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17261
17262    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17263    language_registry.add(Arc::new(Language::new(
17264        LanguageConfig {
17265            name: "Rust".into(),
17266            matcher: LanguageMatcher {
17267                path_suffixes: vec!["rs".to_string()],
17268                ..Default::default()
17269            },
17270            brackets: BracketPairConfig {
17271                pairs: vec![BracketPair {
17272                    start: "{".to_string(),
17273                    end: "}".to_string(),
17274                    close: true,
17275                    surround: true,
17276                    newline: true,
17277                }],
17278                disabled_scopes_by_bracket_ix: Vec::new(),
17279            },
17280            ..Default::default()
17281        },
17282        Some(tree_sitter_rust::LANGUAGE.into()),
17283    )));
17284    let mut fake_servers = language_registry.register_fake_lsp(
17285        "Rust",
17286        FakeLspAdapter {
17287            capabilities: lsp::ServerCapabilities {
17288                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17289                    first_trigger_character: "{".to_string(),
17290                    more_trigger_character: None,
17291                }),
17292                ..Default::default()
17293            },
17294            ..Default::default()
17295        },
17296    );
17297
17298    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17299
17300    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17301
17302    let worktree_id = workspace
17303        .update(cx, |workspace, _, cx| {
17304            workspace.project().update(cx, |project, cx| {
17305                project.worktrees(cx).next().unwrap().read(cx).id()
17306            })
17307        })
17308        .unwrap();
17309
17310    let buffer = project
17311        .update(cx, |project, cx| {
17312            project.open_local_buffer(path!("/a/main.rs"), cx)
17313        })
17314        .await
17315        .unwrap();
17316    let editor_handle = workspace
17317        .update(cx, |workspace, window, cx| {
17318            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17319        })
17320        .unwrap()
17321        .await
17322        .unwrap()
17323        .downcast::<Editor>()
17324        .unwrap();
17325
17326    cx.executor().start_waiting();
17327    let fake_server = fake_servers.next().await.unwrap();
17328
17329    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17330        |params, _| async move {
17331            assert_eq!(
17332                params.text_document_position.text_document.uri,
17333                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17334            );
17335            assert_eq!(
17336                params.text_document_position.position,
17337                lsp::Position::new(0, 21),
17338            );
17339
17340            Ok(Some(vec![lsp::TextEdit {
17341                new_text: "]".to_string(),
17342                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17343            }]))
17344        },
17345    );
17346
17347    editor_handle.update_in(cx, |editor, window, cx| {
17348        window.focus(&editor.focus_handle(cx));
17349        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17350            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17351        });
17352        editor.handle_input("{", window, cx);
17353    });
17354
17355    cx.executor().run_until_parked();
17356
17357    buffer.update(cx, |buffer, _| {
17358        assert_eq!(
17359            buffer.text(),
17360            "fn main() { let a = {5}; }",
17361            "No extra braces from on type formatting should appear in the buffer"
17362        )
17363    });
17364}
17365
17366#[gpui::test(iterations = 20, seeds(31))]
17367async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17368    init_test(cx, |_| {});
17369
17370    let mut cx = EditorLspTestContext::new_rust(
17371        lsp::ServerCapabilities {
17372            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17373                first_trigger_character: ".".to_string(),
17374                more_trigger_character: None,
17375            }),
17376            ..Default::default()
17377        },
17378        cx,
17379    )
17380    .await;
17381
17382    cx.update_buffer(|buffer, _| {
17383        // This causes autoindent to be async.
17384        buffer.set_sync_parse_timeout(Duration::ZERO)
17385    });
17386
17387    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17388    cx.simulate_keystroke("\n");
17389    cx.run_until_parked();
17390
17391    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17392    let mut request =
17393        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17394            let buffer_cloned = buffer_cloned.clone();
17395            async move {
17396                buffer_cloned.update(&mut cx, |buffer, _| {
17397                    assert_eq!(
17398                        buffer.text(),
17399                        "fn c() {\n    d()\n        .\n}\n",
17400                        "OnTypeFormatting should triggered after autoindent applied"
17401                    )
17402                })?;
17403
17404                Ok(Some(vec![]))
17405            }
17406        });
17407
17408    cx.simulate_keystroke(".");
17409    cx.run_until_parked();
17410
17411    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17412    assert!(request.next().await.is_some());
17413    request.close();
17414    assert!(request.next().await.is_none());
17415}
17416
17417#[gpui::test]
17418async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17419    init_test(cx, |_| {});
17420
17421    let fs = FakeFs::new(cx.executor());
17422    fs.insert_tree(
17423        path!("/a"),
17424        json!({
17425            "main.rs": "fn main() { let a = 5; }",
17426            "other.rs": "// Test file",
17427        }),
17428    )
17429    .await;
17430
17431    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17432
17433    let server_restarts = Arc::new(AtomicUsize::new(0));
17434    let closure_restarts = Arc::clone(&server_restarts);
17435    let language_server_name = "test language server";
17436    let language_name: LanguageName = "Rust".into();
17437
17438    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17439    language_registry.add(Arc::new(Language::new(
17440        LanguageConfig {
17441            name: language_name.clone(),
17442            matcher: LanguageMatcher {
17443                path_suffixes: vec!["rs".to_string()],
17444                ..Default::default()
17445            },
17446            ..Default::default()
17447        },
17448        Some(tree_sitter_rust::LANGUAGE.into()),
17449    )));
17450    let mut fake_servers = language_registry.register_fake_lsp(
17451        "Rust",
17452        FakeLspAdapter {
17453            name: language_server_name,
17454            initialization_options: Some(json!({
17455                "testOptionValue": true
17456            })),
17457            initializer: Some(Box::new(move |fake_server| {
17458                let task_restarts = Arc::clone(&closure_restarts);
17459                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17460                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17461                    futures::future::ready(Ok(()))
17462                });
17463            })),
17464            ..Default::default()
17465        },
17466    );
17467
17468    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17469    let _buffer = project
17470        .update(cx, |project, cx| {
17471            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17472        })
17473        .await
17474        .unwrap();
17475    let _fake_server = fake_servers.next().await.unwrap();
17476    update_test_language_settings(cx, |language_settings| {
17477        language_settings.languages.0.insert(
17478            language_name.clone().0,
17479            LanguageSettingsContent {
17480                tab_size: NonZeroU32::new(8),
17481                ..Default::default()
17482            },
17483        );
17484    });
17485    cx.executor().run_until_parked();
17486    assert_eq!(
17487        server_restarts.load(atomic::Ordering::Acquire),
17488        0,
17489        "Should not restart LSP server on an unrelated change"
17490    );
17491
17492    update_test_project_settings(cx, |project_settings| {
17493        project_settings.lsp.insert(
17494            "Some other server name".into(),
17495            LspSettings {
17496                binary: None,
17497                settings: None,
17498                initialization_options: Some(json!({
17499                    "some other init value": false
17500                })),
17501                enable_lsp_tasks: false,
17502                fetch: None,
17503            },
17504        );
17505    });
17506    cx.executor().run_until_parked();
17507    assert_eq!(
17508        server_restarts.load(atomic::Ordering::Acquire),
17509        0,
17510        "Should not restart LSP server on an unrelated LSP settings change"
17511    );
17512
17513    update_test_project_settings(cx, |project_settings| {
17514        project_settings.lsp.insert(
17515            language_server_name.into(),
17516            LspSettings {
17517                binary: None,
17518                settings: None,
17519                initialization_options: Some(json!({
17520                    "anotherInitValue": false
17521                })),
17522                enable_lsp_tasks: false,
17523                fetch: None,
17524            },
17525        );
17526    });
17527    cx.executor().run_until_parked();
17528    assert_eq!(
17529        server_restarts.load(atomic::Ordering::Acquire),
17530        1,
17531        "Should restart LSP server on a related LSP settings change"
17532    );
17533
17534    update_test_project_settings(cx, |project_settings| {
17535        project_settings.lsp.insert(
17536            language_server_name.into(),
17537            LspSettings {
17538                binary: None,
17539                settings: None,
17540                initialization_options: Some(json!({
17541                    "anotherInitValue": false
17542                })),
17543                enable_lsp_tasks: false,
17544                fetch: None,
17545            },
17546        );
17547    });
17548    cx.executor().run_until_parked();
17549    assert_eq!(
17550        server_restarts.load(atomic::Ordering::Acquire),
17551        1,
17552        "Should not restart LSP server on a related LSP settings change that is the same"
17553    );
17554
17555    update_test_project_settings(cx, |project_settings| {
17556        project_settings.lsp.insert(
17557            language_server_name.into(),
17558            LspSettings {
17559                binary: None,
17560                settings: None,
17561                initialization_options: None,
17562                enable_lsp_tasks: false,
17563                fetch: None,
17564            },
17565        );
17566    });
17567    cx.executor().run_until_parked();
17568    assert_eq!(
17569        server_restarts.load(atomic::Ordering::Acquire),
17570        2,
17571        "Should restart LSP server on another related LSP settings change"
17572    );
17573}
17574
17575#[gpui::test]
17576async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17577    init_test(cx, |_| {});
17578
17579    let mut cx = EditorLspTestContext::new_rust(
17580        lsp::ServerCapabilities {
17581            completion_provider: Some(lsp::CompletionOptions {
17582                trigger_characters: Some(vec![".".to_string()]),
17583                resolve_provider: Some(true),
17584                ..Default::default()
17585            }),
17586            ..Default::default()
17587        },
17588        cx,
17589    )
17590    .await;
17591
17592    cx.set_state("fn main() { let a = 2ˇ; }");
17593    cx.simulate_keystroke(".");
17594    let completion_item = lsp::CompletionItem {
17595        label: "some".into(),
17596        kind: Some(lsp::CompletionItemKind::SNIPPET),
17597        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17598        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17599            kind: lsp::MarkupKind::Markdown,
17600            value: "```rust\nSome(2)\n```".to_string(),
17601        })),
17602        deprecated: Some(false),
17603        sort_text: Some("fffffff2".to_string()),
17604        filter_text: Some("some".to_string()),
17605        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17606        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17607            range: lsp::Range {
17608                start: lsp::Position {
17609                    line: 0,
17610                    character: 22,
17611                },
17612                end: lsp::Position {
17613                    line: 0,
17614                    character: 22,
17615                },
17616            },
17617            new_text: "Some(2)".to_string(),
17618        })),
17619        additional_text_edits: Some(vec![lsp::TextEdit {
17620            range: lsp::Range {
17621                start: lsp::Position {
17622                    line: 0,
17623                    character: 20,
17624                },
17625                end: lsp::Position {
17626                    line: 0,
17627                    character: 22,
17628                },
17629            },
17630            new_text: "".to_string(),
17631        }]),
17632        ..Default::default()
17633    };
17634
17635    let closure_completion_item = completion_item.clone();
17636    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17637        let task_completion_item = closure_completion_item.clone();
17638        async move {
17639            Ok(Some(lsp::CompletionResponse::Array(vec![
17640                task_completion_item,
17641            ])))
17642        }
17643    });
17644
17645    request.next().await;
17646
17647    cx.condition(|editor, _| editor.context_menu_visible())
17648        .await;
17649    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17650        editor
17651            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17652            .unwrap()
17653    });
17654    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17655
17656    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17657        let task_completion_item = completion_item.clone();
17658        async move { Ok(task_completion_item) }
17659    })
17660    .next()
17661    .await
17662    .unwrap();
17663    apply_additional_edits.await.unwrap();
17664    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17665}
17666
17667#[gpui::test]
17668async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17669    init_test(cx, |_| {});
17670
17671    let mut cx = EditorLspTestContext::new_rust(
17672        lsp::ServerCapabilities {
17673            completion_provider: Some(lsp::CompletionOptions {
17674                trigger_characters: Some(vec![".".to_string()]),
17675                resolve_provider: Some(true),
17676                ..Default::default()
17677            }),
17678            ..Default::default()
17679        },
17680        cx,
17681    )
17682    .await;
17683
17684    cx.set_state("fn main() { let a = 2ˇ; }");
17685    cx.simulate_keystroke(".");
17686
17687    let item1 = lsp::CompletionItem {
17688        label: "method id()".to_string(),
17689        filter_text: Some("id".to_string()),
17690        detail: None,
17691        documentation: None,
17692        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17693            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17694            new_text: ".id".to_string(),
17695        })),
17696        ..lsp::CompletionItem::default()
17697    };
17698
17699    let item2 = lsp::CompletionItem {
17700        label: "other".to_string(),
17701        filter_text: Some("other".to_string()),
17702        detail: None,
17703        documentation: None,
17704        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17705            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17706            new_text: ".other".to_string(),
17707        })),
17708        ..lsp::CompletionItem::default()
17709    };
17710
17711    let item1 = item1.clone();
17712    cx.set_request_handler::<lsp::request::Completion, _, _>({
17713        let item1 = item1.clone();
17714        move |_, _, _| {
17715            let item1 = item1.clone();
17716            let item2 = item2.clone();
17717            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17718        }
17719    })
17720    .next()
17721    .await;
17722
17723    cx.condition(|editor, _| editor.context_menu_visible())
17724        .await;
17725    cx.update_editor(|editor, _, _| {
17726        let context_menu = editor.context_menu.borrow_mut();
17727        let context_menu = context_menu
17728            .as_ref()
17729            .expect("Should have the context menu deployed");
17730        match context_menu {
17731            CodeContextMenu::Completions(completions_menu) => {
17732                let completions = completions_menu.completions.borrow_mut();
17733                assert_eq!(
17734                    completions
17735                        .iter()
17736                        .map(|completion| &completion.label.text)
17737                        .collect::<Vec<_>>(),
17738                    vec!["method id()", "other"]
17739                )
17740            }
17741            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17742        }
17743    });
17744
17745    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17746        let item1 = item1.clone();
17747        move |_, item_to_resolve, _| {
17748            let item1 = item1.clone();
17749            async move {
17750                if item1 == item_to_resolve {
17751                    Ok(lsp::CompletionItem {
17752                        label: "method id()".to_string(),
17753                        filter_text: Some("id".to_string()),
17754                        detail: Some("Now resolved!".to_string()),
17755                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17756                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17757                            range: lsp::Range::new(
17758                                lsp::Position::new(0, 22),
17759                                lsp::Position::new(0, 22),
17760                            ),
17761                            new_text: ".id".to_string(),
17762                        })),
17763                        ..lsp::CompletionItem::default()
17764                    })
17765                } else {
17766                    Ok(item_to_resolve)
17767                }
17768            }
17769        }
17770    })
17771    .next()
17772    .await
17773    .unwrap();
17774    cx.run_until_parked();
17775
17776    cx.update_editor(|editor, window, cx| {
17777        editor.context_menu_next(&Default::default(), window, cx);
17778    });
17779
17780    cx.update_editor(|editor, _, _| {
17781        let context_menu = editor.context_menu.borrow_mut();
17782        let context_menu = context_menu
17783            .as_ref()
17784            .expect("Should have the context menu deployed");
17785        match context_menu {
17786            CodeContextMenu::Completions(completions_menu) => {
17787                let completions = completions_menu.completions.borrow_mut();
17788                assert_eq!(
17789                    completions
17790                        .iter()
17791                        .map(|completion| &completion.label.text)
17792                        .collect::<Vec<_>>(),
17793                    vec!["method id() Now resolved!", "other"],
17794                    "Should update first completion label, but not second as the filter text did not match."
17795                );
17796            }
17797            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17798        }
17799    });
17800}
17801
17802#[gpui::test]
17803async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17804    init_test(cx, |_| {});
17805    let mut cx = EditorLspTestContext::new_rust(
17806        lsp::ServerCapabilities {
17807            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17808            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17809            completion_provider: Some(lsp::CompletionOptions {
17810                resolve_provider: Some(true),
17811                ..Default::default()
17812            }),
17813            ..Default::default()
17814        },
17815        cx,
17816    )
17817    .await;
17818    cx.set_state(indoc! {"
17819        struct TestStruct {
17820            field: i32
17821        }
17822
17823        fn mainˇ() {
17824            let unused_var = 42;
17825            let test_struct = TestStruct { field: 42 };
17826        }
17827    "});
17828    let symbol_range = cx.lsp_range(indoc! {"
17829        struct TestStruct {
17830            field: i32
17831        }
17832
17833        «fn main»() {
17834            let unused_var = 42;
17835            let test_struct = TestStruct { field: 42 };
17836        }
17837    "});
17838    let mut hover_requests =
17839        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17840            Ok(Some(lsp::Hover {
17841                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17842                    kind: lsp::MarkupKind::Markdown,
17843                    value: "Function documentation".to_string(),
17844                }),
17845                range: Some(symbol_range),
17846            }))
17847        });
17848
17849    // Case 1: Test that code action menu hide hover popover
17850    cx.dispatch_action(Hover);
17851    hover_requests.next().await;
17852    cx.condition(|editor, _| editor.hover_state.visible()).await;
17853    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17854        move |_, _, _| async move {
17855            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17856                lsp::CodeAction {
17857                    title: "Remove unused variable".to_string(),
17858                    kind: Some(CodeActionKind::QUICKFIX),
17859                    edit: Some(lsp::WorkspaceEdit {
17860                        changes: Some(
17861                            [(
17862                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17863                                vec![lsp::TextEdit {
17864                                    range: lsp::Range::new(
17865                                        lsp::Position::new(5, 4),
17866                                        lsp::Position::new(5, 27),
17867                                    ),
17868                                    new_text: "".to_string(),
17869                                }],
17870                            )]
17871                            .into_iter()
17872                            .collect(),
17873                        ),
17874                        ..Default::default()
17875                    }),
17876                    ..Default::default()
17877                },
17878            )]))
17879        },
17880    );
17881    cx.update_editor(|editor, window, cx| {
17882        editor.toggle_code_actions(
17883            &ToggleCodeActions {
17884                deployed_from: None,
17885                quick_launch: false,
17886            },
17887            window,
17888            cx,
17889        );
17890    });
17891    code_action_requests.next().await;
17892    cx.run_until_parked();
17893    cx.condition(|editor, _| editor.context_menu_visible())
17894        .await;
17895    cx.update_editor(|editor, _, _| {
17896        assert!(
17897            !editor.hover_state.visible(),
17898            "Hover popover should be hidden when code action menu is shown"
17899        );
17900        // Hide code actions
17901        editor.context_menu.take();
17902    });
17903
17904    // Case 2: Test that code completions hide hover popover
17905    cx.dispatch_action(Hover);
17906    hover_requests.next().await;
17907    cx.condition(|editor, _| editor.hover_state.visible()).await;
17908    let counter = Arc::new(AtomicUsize::new(0));
17909    let mut completion_requests =
17910        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17911            let counter = counter.clone();
17912            async move {
17913                counter.fetch_add(1, atomic::Ordering::Release);
17914                Ok(Some(lsp::CompletionResponse::Array(vec![
17915                    lsp::CompletionItem {
17916                        label: "main".into(),
17917                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17918                        detail: Some("() -> ()".to_string()),
17919                        ..Default::default()
17920                    },
17921                    lsp::CompletionItem {
17922                        label: "TestStruct".into(),
17923                        kind: Some(lsp::CompletionItemKind::STRUCT),
17924                        detail: Some("struct TestStruct".to_string()),
17925                        ..Default::default()
17926                    },
17927                ])))
17928            }
17929        });
17930    cx.update_editor(|editor, window, cx| {
17931        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17932    });
17933    completion_requests.next().await;
17934    cx.condition(|editor, _| editor.context_menu_visible())
17935        .await;
17936    cx.update_editor(|editor, _, _| {
17937        assert!(
17938            !editor.hover_state.visible(),
17939            "Hover popover should be hidden when completion menu is shown"
17940        );
17941    });
17942}
17943
17944#[gpui::test]
17945async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17946    init_test(cx, |_| {});
17947
17948    let mut cx = EditorLspTestContext::new_rust(
17949        lsp::ServerCapabilities {
17950            completion_provider: Some(lsp::CompletionOptions {
17951                trigger_characters: Some(vec![".".to_string()]),
17952                resolve_provider: Some(true),
17953                ..Default::default()
17954            }),
17955            ..Default::default()
17956        },
17957        cx,
17958    )
17959    .await;
17960
17961    cx.set_state("fn main() { let a = 2ˇ; }");
17962    cx.simulate_keystroke(".");
17963
17964    let unresolved_item_1 = lsp::CompletionItem {
17965        label: "id".to_string(),
17966        filter_text: Some("id".to_string()),
17967        detail: None,
17968        documentation: None,
17969        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17970            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17971            new_text: ".id".to_string(),
17972        })),
17973        ..lsp::CompletionItem::default()
17974    };
17975    let resolved_item_1 = lsp::CompletionItem {
17976        additional_text_edits: Some(vec![lsp::TextEdit {
17977            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17978            new_text: "!!".to_string(),
17979        }]),
17980        ..unresolved_item_1.clone()
17981    };
17982    let unresolved_item_2 = lsp::CompletionItem {
17983        label: "other".to_string(),
17984        filter_text: Some("other".to_string()),
17985        detail: None,
17986        documentation: None,
17987        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17988            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17989            new_text: ".other".to_string(),
17990        })),
17991        ..lsp::CompletionItem::default()
17992    };
17993    let resolved_item_2 = lsp::CompletionItem {
17994        additional_text_edits: Some(vec![lsp::TextEdit {
17995            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17996            new_text: "??".to_string(),
17997        }]),
17998        ..unresolved_item_2.clone()
17999    };
18000
18001    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18002    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18003    cx.lsp
18004        .server
18005        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18006            let unresolved_item_1 = unresolved_item_1.clone();
18007            let resolved_item_1 = resolved_item_1.clone();
18008            let unresolved_item_2 = unresolved_item_2.clone();
18009            let resolved_item_2 = resolved_item_2.clone();
18010            let resolve_requests_1 = resolve_requests_1.clone();
18011            let resolve_requests_2 = resolve_requests_2.clone();
18012            move |unresolved_request, _| {
18013                let unresolved_item_1 = unresolved_item_1.clone();
18014                let resolved_item_1 = resolved_item_1.clone();
18015                let unresolved_item_2 = unresolved_item_2.clone();
18016                let resolved_item_2 = resolved_item_2.clone();
18017                let resolve_requests_1 = resolve_requests_1.clone();
18018                let resolve_requests_2 = resolve_requests_2.clone();
18019                async move {
18020                    if unresolved_request == unresolved_item_1 {
18021                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18022                        Ok(resolved_item_1.clone())
18023                    } else if unresolved_request == unresolved_item_2 {
18024                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18025                        Ok(resolved_item_2.clone())
18026                    } else {
18027                        panic!("Unexpected completion item {unresolved_request:?}")
18028                    }
18029                }
18030            }
18031        })
18032        .detach();
18033
18034    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18035        let unresolved_item_1 = unresolved_item_1.clone();
18036        let unresolved_item_2 = unresolved_item_2.clone();
18037        async move {
18038            Ok(Some(lsp::CompletionResponse::Array(vec![
18039                unresolved_item_1,
18040                unresolved_item_2,
18041            ])))
18042        }
18043    })
18044    .next()
18045    .await;
18046
18047    cx.condition(|editor, _| editor.context_menu_visible())
18048        .await;
18049    cx.update_editor(|editor, _, _| {
18050        let context_menu = editor.context_menu.borrow_mut();
18051        let context_menu = context_menu
18052            .as_ref()
18053            .expect("Should have the context menu deployed");
18054        match context_menu {
18055            CodeContextMenu::Completions(completions_menu) => {
18056                let completions = completions_menu.completions.borrow_mut();
18057                assert_eq!(
18058                    completions
18059                        .iter()
18060                        .map(|completion| &completion.label.text)
18061                        .collect::<Vec<_>>(),
18062                    vec!["id", "other"]
18063                )
18064            }
18065            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18066        }
18067    });
18068    cx.run_until_parked();
18069
18070    cx.update_editor(|editor, window, cx| {
18071        editor.context_menu_next(&ContextMenuNext, window, cx);
18072    });
18073    cx.run_until_parked();
18074    cx.update_editor(|editor, window, cx| {
18075        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18076    });
18077    cx.run_until_parked();
18078    cx.update_editor(|editor, window, cx| {
18079        editor.context_menu_next(&ContextMenuNext, window, cx);
18080    });
18081    cx.run_until_parked();
18082    cx.update_editor(|editor, window, cx| {
18083        editor
18084            .compose_completion(&ComposeCompletion::default(), window, cx)
18085            .expect("No task returned")
18086    })
18087    .await
18088    .expect("Completion failed");
18089    cx.run_until_parked();
18090
18091    cx.update_editor(|editor, _, cx| {
18092        assert_eq!(
18093            resolve_requests_1.load(atomic::Ordering::Acquire),
18094            1,
18095            "Should always resolve once despite multiple selections"
18096        );
18097        assert_eq!(
18098            resolve_requests_2.load(atomic::Ordering::Acquire),
18099            1,
18100            "Should always resolve once after multiple selections and applying the completion"
18101        );
18102        assert_eq!(
18103            editor.text(cx),
18104            "fn main() { let a = ??.other; }",
18105            "Should use resolved data when applying the completion"
18106        );
18107    });
18108}
18109
18110#[gpui::test]
18111async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18112    init_test(cx, |_| {});
18113
18114    let item_0 = lsp::CompletionItem {
18115        label: "abs".into(),
18116        insert_text: Some("abs".into()),
18117        data: Some(json!({ "very": "special"})),
18118        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18119        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18120            lsp::InsertReplaceEdit {
18121                new_text: "abs".to_string(),
18122                insert: lsp::Range::default(),
18123                replace: lsp::Range::default(),
18124            },
18125        )),
18126        ..lsp::CompletionItem::default()
18127    };
18128    let items = iter::once(item_0.clone())
18129        .chain((11..51).map(|i| lsp::CompletionItem {
18130            label: format!("item_{}", i),
18131            insert_text: Some(format!("item_{}", i)),
18132            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18133            ..lsp::CompletionItem::default()
18134        }))
18135        .collect::<Vec<_>>();
18136
18137    let default_commit_characters = vec!["?".to_string()];
18138    let default_data = json!({ "default": "data"});
18139    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18140    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18141    let default_edit_range = lsp::Range {
18142        start: lsp::Position {
18143            line: 0,
18144            character: 5,
18145        },
18146        end: lsp::Position {
18147            line: 0,
18148            character: 5,
18149        },
18150    };
18151
18152    let mut cx = EditorLspTestContext::new_rust(
18153        lsp::ServerCapabilities {
18154            completion_provider: Some(lsp::CompletionOptions {
18155                trigger_characters: Some(vec![".".to_string()]),
18156                resolve_provider: Some(true),
18157                ..Default::default()
18158            }),
18159            ..Default::default()
18160        },
18161        cx,
18162    )
18163    .await;
18164
18165    cx.set_state("fn main() { let a = 2ˇ; }");
18166    cx.simulate_keystroke(".");
18167
18168    let completion_data = default_data.clone();
18169    let completion_characters = default_commit_characters.clone();
18170    let completion_items = items.clone();
18171    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18172        let default_data = completion_data.clone();
18173        let default_commit_characters = completion_characters.clone();
18174        let items = completion_items.clone();
18175        async move {
18176            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18177                items,
18178                item_defaults: Some(lsp::CompletionListItemDefaults {
18179                    data: Some(default_data.clone()),
18180                    commit_characters: Some(default_commit_characters.clone()),
18181                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18182                        default_edit_range,
18183                    )),
18184                    insert_text_format: Some(default_insert_text_format),
18185                    insert_text_mode: Some(default_insert_text_mode),
18186                }),
18187                ..lsp::CompletionList::default()
18188            })))
18189        }
18190    })
18191    .next()
18192    .await;
18193
18194    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18195    cx.lsp
18196        .server
18197        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18198            let closure_resolved_items = resolved_items.clone();
18199            move |item_to_resolve, _| {
18200                let closure_resolved_items = closure_resolved_items.clone();
18201                async move {
18202                    closure_resolved_items.lock().push(item_to_resolve.clone());
18203                    Ok(item_to_resolve)
18204                }
18205            }
18206        })
18207        .detach();
18208
18209    cx.condition(|editor, _| editor.context_menu_visible())
18210        .await;
18211    cx.run_until_parked();
18212    cx.update_editor(|editor, _, _| {
18213        let menu = editor.context_menu.borrow_mut();
18214        match menu.as_ref().expect("should have the completions menu") {
18215            CodeContextMenu::Completions(completions_menu) => {
18216                assert_eq!(
18217                    completions_menu
18218                        .entries
18219                        .borrow()
18220                        .iter()
18221                        .map(|mat| mat.string.clone())
18222                        .collect::<Vec<String>>(),
18223                    items
18224                        .iter()
18225                        .map(|completion| completion.label.clone())
18226                        .collect::<Vec<String>>()
18227                );
18228            }
18229            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18230        }
18231    });
18232    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18233    // with 4 from the end.
18234    assert_eq!(
18235        *resolved_items.lock(),
18236        [&items[0..16], &items[items.len() - 4..items.len()]]
18237            .concat()
18238            .iter()
18239            .cloned()
18240            .map(|mut item| {
18241                if item.data.is_none() {
18242                    item.data = Some(default_data.clone());
18243                }
18244                item
18245            })
18246            .collect::<Vec<lsp::CompletionItem>>(),
18247        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18248    );
18249    resolved_items.lock().clear();
18250
18251    cx.update_editor(|editor, window, cx| {
18252        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18253    });
18254    cx.run_until_parked();
18255    // Completions that have already been resolved are skipped.
18256    assert_eq!(
18257        *resolved_items.lock(),
18258        items[items.len() - 17..items.len() - 4]
18259            .iter()
18260            .cloned()
18261            .map(|mut item| {
18262                if item.data.is_none() {
18263                    item.data = Some(default_data.clone());
18264                }
18265                item
18266            })
18267            .collect::<Vec<lsp::CompletionItem>>()
18268    );
18269    resolved_items.lock().clear();
18270}
18271
18272#[gpui::test]
18273async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18274    init_test(cx, |_| {});
18275
18276    let mut cx = EditorLspTestContext::new(
18277        Language::new(
18278            LanguageConfig {
18279                matcher: LanguageMatcher {
18280                    path_suffixes: vec!["jsx".into()],
18281                    ..Default::default()
18282                },
18283                overrides: [(
18284                    "element".into(),
18285                    LanguageConfigOverride {
18286                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18287                        ..Default::default()
18288                    },
18289                )]
18290                .into_iter()
18291                .collect(),
18292                ..Default::default()
18293            },
18294            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18295        )
18296        .with_override_query("(jsx_self_closing_element) @element")
18297        .unwrap(),
18298        lsp::ServerCapabilities {
18299            completion_provider: Some(lsp::CompletionOptions {
18300                trigger_characters: Some(vec![":".to_string()]),
18301                ..Default::default()
18302            }),
18303            ..Default::default()
18304        },
18305        cx,
18306    )
18307    .await;
18308
18309    cx.lsp
18310        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18311            Ok(Some(lsp::CompletionResponse::Array(vec![
18312                lsp::CompletionItem {
18313                    label: "bg-blue".into(),
18314                    ..Default::default()
18315                },
18316                lsp::CompletionItem {
18317                    label: "bg-red".into(),
18318                    ..Default::default()
18319                },
18320                lsp::CompletionItem {
18321                    label: "bg-yellow".into(),
18322                    ..Default::default()
18323                },
18324            ])))
18325        });
18326
18327    cx.set_state(r#"<p class="bgˇ" />"#);
18328
18329    // Trigger completion when typing a dash, because the dash is an extra
18330    // word character in the 'element' scope, which contains the cursor.
18331    cx.simulate_keystroke("-");
18332    cx.executor().run_until_parked();
18333    cx.update_editor(|editor, _, _| {
18334        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18335        {
18336            assert_eq!(
18337                completion_menu_entries(menu),
18338                &["bg-blue", "bg-red", "bg-yellow"]
18339            );
18340        } else {
18341            panic!("expected completion menu to be open");
18342        }
18343    });
18344
18345    cx.simulate_keystroke("l");
18346    cx.executor().run_until_parked();
18347    cx.update_editor(|editor, _, _| {
18348        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18349        {
18350            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18351        } else {
18352            panic!("expected completion menu to be open");
18353        }
18354    });
18355
18356    // When filtering completions, consider the character after the '-' to
18357    // be the start of a subword.
18358    cx.set_state(r#"<p class="yelˇ" />"#);
18359    cx.simulate_keystroke("l");
18360    cx.executor().run_until_parked();
18361    cx.update_editor(|editor, _, _| {
18362        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18363        {
18364            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18365        } else {
18366            panic!("expected completion menu to be open");
18367        }
18368    });
18369}
18370
18371fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18372    let entries = menu.entries.borrow();
18373    entries.iter().map(|mat| mat.string.clone()).collect()
18374}
18375
18376#[gpui::test]
18377async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18378    init_test(cx, |settings| {
18379        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18380    });
18381
18382    let fs = FakeFs::new(cx.executor());
18383    fs.insert_file(path!("/file.ts"), Default::default()).await;
18384
18385    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18386    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18387
18388    language_registry.add(Arc::new(Language::new(
18389        LanguageConfig {
18390            name: "TypeScript".into(),
18391            matcher: LanguageMatcher {
18392                path_suffixes: vec!["ts".to_string()],
18393                ..Default::default()
18394            },
18395            ..Default::default()
18396        },
18397        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18398    )));
18399    update_test_language_settings(cx, |settings| {
18400        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18401    });
18402
18403    let test_plugin = "test_plugin";
18404    let _ = language_registry.register_fake_lsp(
18405        "TypeScript",
18406        FakeLspAdapter {
18407            prettier_plugins: vec![test_plugin],
18408            ..Default::default()
18409        },
18410    );
18411
18412    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18413    let buffer = project
18414        .update(cx, |project, cx| {
18415            project.open_local_buffer(path!("/file.ts"), cx)
18416        })
18417        .await
18418        .unwrap();
18419
18420    let buffer_text = "one\ntwo\nthree\n";
18421    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18422    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18423    editor.update_in(cx, |editor, window, cx| {
18424        editor.set_text(buffer_text, window, cx)
18425    });
18426
18427    editor
18428        .update_in(cx, |editor, window, cx| {
18429            editor.perform_format(
18430                project.clone(),
18431                FormatTrigger::Manual,
18432                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18433                window,
18434                cx,
18435            )
18436        })
18437        .unwrap()
18438        .await;
18439    assert_eq!(
18440        editor.update(cx, |editor, cx| editor.text(cx)),
18441        buffer_text.to_string() + prettier_format_suffix,
18442        "Test prettier formatting was not applied to the original buffer text",
18443    );
18444
18445    update_test_language_settings(cx, |settings| {
18446        settings.defaults.formatter = Some(FormatterList::default())
18447    });
18448    let format = editor.update_in(cx, |editor, window, cx| {
18449        editor.perform_format(
18450            project.clone(),
18451            FormatTrigger::Manual,
18452            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18453            window,
18454            cx,
18455        )
18456    });
18457    format.await.unwrap();
18458    assert_eq!(
18459        editor.update(cx, |editor, cx| editor.text(cx)),
18460        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18461        "Autoformatting (via test prettier) was not applied to the original buffer text",
18462    );
18463}
18464
18465#[gpui::test]
18466async fn test_addition_reverts(cx: &mut TestAppContext) {
18467    init_test(cx, |_| {});
18468    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18469    let base_text = indoc! {r#"
18470        struct Row;
18471        struct Row1;
18472        struct Row2;
18473
18474        struct Row4;
18475        struct Row5;
18476        struct Row6;
18477
18478        struct Row8;
18479        struct Row9;
18480        struct Row10;"#};
18481
18482    // When addition hunks are not adjacent to carets, no hunk revert is performed
18483    assert_hunk_revert(
18484        indoc! {r#"struct Row;
18485                   struct Row1;
18486                   struct Row1.1;
18487                   struct Row1.2;
18488                   struct Row2;ˇ
18489
18490                   struct Row4;
18491                   struct Row5;
18492                   struct Row6;
18493
18494                   struct Row8;
18495                   ˇstruct Row9;
18496                   struct Row9.1;
18497                   struct Row9.2;
18498                   struct Row9.3;
18499                   struct Row10;"#},
18500        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18501        indoc! {r#"struct Row;
18502                   struct Row1;
18503                   struct Row1.1;
18504                   struct Row1.2;
18505                   struct Row2;ˇ
18506
18507                   struct Row4;
18508                   struct Row5;
18509                   struct Row6;
18510
18511                   struct Row8;
18512                   ˇstruct Row9;
18513                   struct Row9.1;
18514                   struct Row9.2;
18515                   struct Row9.3;
18516                   struct Row10;"#},
18517        base_text,
18518        &mut cx,
18519    );
18520    // Same for selections
18521    assert_hunk_revert(
18522        indoc! {r#"struct Row;
18523                   struct Row1;
18524                   struct Row2;
18525                   struct Row2.1;
18526                   struct Row2.2;
18527                   «ˇ
18528                   struct Row4;
18529                   struct» Row5;
18530                   «struct Row6;
18531                   ˇ»
18532                   struct Row9.1;
18533                   struct Row9.2;
18534                   struct Row9.3;
18535                   struct Row8;
18536                   struct Row9;
18537                   struct Row10;"#},
18538        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18539        indoc! {r#"struct Row;
18540                   struct Row1;
18541                   struct Row2;
18542                   struct Row2.1;
18543                   struct Row2.2;
18544                   «ˇ
18545                   struct Row4;
18546                   struct» Row5;
18547                   «struct Row6;
18548                   ˇ»
18549                   struct Row9.1;
18550                   struct Row9.2;
18551                   struct Row9.3;
18552                   struct Row8;
18553                   struct Row9;
18554                   struct Row10;"#},
18555        base_text,
18556        &mut cx,
18557    );
18558
18559    // When carets and selections intersect the addition hunks, those are reverted.
18560    // Adjacent carets got merged.
18561    assert_hunk_revert(
18562        indoc! {r#"struct Row;
18563                   ˇ// something on the top
18564                   struct Row1;
18565                   struct Row2;
18566                   struct Roˇw3.1;
18567                   struct Row2.2;
18568                   struct Row2.3;ˇ
18569
18570                   struct Row4;
18571                   struct ˇRow5.1;
18572                   struct Row5.2;
18573                   struct «Rowˇ»5.3;
18574                   struct Row5;
18575                   struct Row6;
18576                   ˇ
18577                   struct Row9.1;
18578                   struct «Rowˇ»9.2;
18579                   struct «ˇRow»9.3;
18580                   struct Row8;
18581                   struct Row9;
18582                   «ˇ// something on bottom»
18583                   struct Row10;"#},
18584        vec![
18585            DiffHunkStatusKind::Added,
18586            DiffHunkStatusKind::Added,
18587            DiffHunkStatusKind::Added,
18588            DiffHunkStatusKind::Added,
18589            DiffHunkStatusKind::Added,
18590        ],
18591        indoc! {r#"struct Row;
18592                   ˇstruct Row1;
18593                   struct Row2;
18594                   ˇ
18595                   struct Row4;
18596                   ˇstruct Row5;
18597                   struct Row6;
18598                   ˇ
18599                   ˇstruct Row8;
18600                   struct Row9;
18601                   ˇstruct Row10;"#},
18602        base_text,
18603        &mut cx,
18604    );
18605}
18606
18607#[gpui::test]
18608async fn test_modification_reverts(cx: &mut TestAppContext) {
18609    init_test(cx, |_| {});
18610    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18611    let base_text = indoc! {r#"
18612        struct Row;
18613        struct Row1;
18614        struct Row2;
18615
18616        struct Row4;
18617        struct Row5;
18618        struct Row6;
18619
18620        struct Row8;
18621        struct Row9;
18622        struct Row10;"#};
18623
18624    // Modification hunks behave the same as the addition ones.
18625    assert_hunk_revert(
18626        indoc! {r#"struct Row;
18627                   struct Row1;
18628                   struct Row33;
18629                   ˇ
18630                   struct Row4;
18631                   struct Row5;
18632                   struct Row6;
18633                   ˇ
18634                   struct Row99;
18635                   struct Row9;
18636                   struct Row10;"#},
18637        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18638        indoc! {r#"struct Row;
18639                   struct Row1;
18640                   struct Row33;
18641                   ˇ
18642                   struct Row4;
18643                   struct Row5;
18644                   struct Row6;
18645                   ˇ
18646                   struct Row99;
18647                   struct Row9;
18648                   struct Row10;"#},
18649        base_text,
18650        &mut cx,
18651    );
18652    assert_hunk_revert(
18653        indoc! {r#"struct Row;
18654                   struct Row1;
18655                   struct Row33;
18656                   «ˇ
18657                   struct Row4;
18658                   struct» Row5;
18659                   «struct Row6;
18660                   ˇ»
18661                   struct Row99;
18662                   struct Row9;
18663                   struct Row10;"#},
18664        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18665        indoc! {r#"struct Row;
18666                   struct Row1;
18667                   struct Row33;
18668                   «ˇ
18669                   struct Row4;
18670                   struct» Row5;
18671                   «struct Row6;
18672                   ˇ»
18673                   struct Row99;
18674                   struct Row9;
18675                   struct Row10;"#},
18676        base_text,
18677        &mut cx,
18678    );
18679
18680    assert_hunk_revert(
18681        indoc! {r#"ˇstruct Row1.1;
18682                   struct Row1;
18683                   «ˇstr»uct Row22;
18684
18685                   struct ˇRow44;
18686                   struct Row5;
18687                   struct «Rˇ»ow66;ˇ
18688
18689                   «struˇ»ct Row88;
18690                   struct Row9;
18691                   struct Row1011;ˇ"#},
18692        vec![
18693            DiffHunkStatusKind::Modified,
18694            DiffHunkStatusKind::Modified,
18695            DiffHunkStatusKind::Modified,
18696            DiffHunkStatusKind::Modified,
18697            DiffHunkStatusKind::Modified,
18698            DiffHunkStatusKind::Modified,
18699        ],
18700        indoc! {r#"struct Row;
18701                   ˇstruct Row1;
18702                   struct Row2;
18703                   ˇ
18704                   struct Row4;
18705                   ˇstruct Row5;
18706                   struct Row6;
18707                   ˇ
18708                   struct Row8;
18709                   ˇstruct Row9;
18710                   struct Row10;ˇ"#},
18711        base_text,
18712        &mut cx,
18713    );
18714}
18715
18716#[gpui::test]
18717async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18718    init_test(cx, |_| {});
18719    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18720    let base_text = indoc! {r#"
18721        one
18722
18723        two
18724        three
18725        "#};
18726
18727    cx.set_head_text(base_text);
18728    cx.set_state("\nˇ\n");
18729    cx.executor().run_until_parked();
18730    cx.update_editor(|editor, _window, cx| {
18731        editor.expand_selected_diff_hunks(cx);
18732    });
18733    cx.executor().run_until_parked();
18734    cx.update_editor(|editor, window, cx| {
18735        editor.backspace(&Default::default(), window, cx);
18736    });
18737    cx.run_until_parked();
18738    cx.assert_state_with_diff(
18739        indoc! {r#"
18740
18741        - two
18742        - threeˇ
18743        +
18744        "#}
18745        .to_string(),
18746    );
18747}
18748
18749#[gpui::test]
18750async fn test_deletion_reverts(cx: &mut TestAppContext) {
18751    init_test(cx, |_| {});
18752    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18753    let base_text = indoc! {r#"struct Row;
18754struct Row1;
18755struct Row2;
18756
18757struct Row4;
18758struct Row5;
18759struct Row6;
18760
18761struct Row8;
18762struct Row9;
18763struct Row10;"#};
18764
18765    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18766    assert_hunk_revert(
18767        indoc! {r#"struct Row;
18768                   struct Row2;
18769
18770                   ˇstruct Row4;
18771                   struct Row5;
18772                   struct Row6;
18773                   ˇ
18774                   struct Row8;
18775                   struct Row10;"#},
18776        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18777        indoc! {r#"struct Row;
18778                   struct Row2;
18779
18780                   ˇstruct Row4;
18781                   struct Row5;
18782                   struct Row6;
18783                   ˇ
18784                   struct Row8;
18785                   struct Row10;"#},
18786        base_text,
18787        &mut cx,
18788    );
18789    assert_hunk_revert(
18790        indoc! {r#"struct Row;
18791                   struct Row2;
18792
18793                   «ˇstruct Row4;
18794                   struct» Row5;
18795                   «struct Row6;
18796                   ˇ»
18797                   struct Row8;
18798                   struct Row10;"#},
18799        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18800        indoc! {r#"struct Row;
18801                   struct Row2;
18802
18803                   «ˇstruct Row4;
18804                   struct» Row5;
18805                   «struct Row6;
18806                   ˇ»
18807                   struct Row8;
18808                   struct Row10;"#},
18809        base_text,
18810        &mut cx,
18811    );
18812
18813    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18814    assert_hunk_revert(
18815        indoc! {r#"struct Row;
18816                   ˇstruct Row2;
18817
18818                   struct Row4;
18819                   struct Row5;
18820                   struct Row6;
18821
18822                   struct Row8;ˇ
18823                   struct Row10;"#},
18824        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18825        indoc! {r#"struct Row;
18826                   struct Row1;
18827                   ˇstruct Row2;
18828
18829                   struct Row4;
18830                   struct Row5;
18831                   struct Row6;
18832
18833                   struct Row8;ˇ
18834                   struct Row9;
18835                   struct Row10;"#},
18836        base_text,
18837        &mut cx,
18838    );
18839    assert_hunk_revert(
18840        indoc! {r#"struct Row;
18841                   struct Row2«ˇ;
18842                   struct Row4;
18843                   struct» Row5;
18844                   «struct Row6;
18845
18846                   struct Row8;ˇ»
18847                   struct Row10;"#},
18848        vec![
18849            DiffHunkStatusKind::Deleted,
18850            DiffHunkStatusKind::Deleted,
18851            DiffHunkStatusKind::Deleted,
18852        ],
18853        indoc! {r#"struct Row;
18854                   struct Row1;
18855                   struct Row2«ˇ;
18856
18857                   struct Row4;
18858                   struct» Row5;
18859                   «struct Row6;
18860
18861                   struct Row8;ˇ»
18862                   struct Row9;
18863                   struct Row10;"#},
18864        base_text,
18865        &mut cx,
18866    );
18867}
18868
18869#[gpui::test]
18870async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18871    init_test(cx, |_| {});
18872
18873    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18874    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18875    let base_text_3 =
18876        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18877
18878    let text_1 = edit_first_char_of_every_line(base_text_1);
18879    let text_2 = edit_first_char_of_every_line(base_text_2);
18880    let text_3 = edit_first_char_of_every_line(base_text_3);
18881
18882    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18883    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18884    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18885
18886    let multibuffer = cx.new(|cx| {
18887        let mut multibuffer = MultiBuffer::new(ReadWrite);
18888        multibuffer.push_excerpts(
18889            buffer_1.clone(),
18890            [
18891                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18892                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18893                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18894            ],
18895            cx,
18896        );
18897        multibuffer.push_excerpts(
18898            buffer_2.clone(),
18899            [
18900                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18901                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18902                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18903            ],
18904            cx,
18905        );
18906        multibuffer.push_excerpts(
18907            buffer_3.clone(),
18908            [
18909                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18910                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18911                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18912            ],
18913            cx,
18914        );
18915        multibuffer
18916    });
18917
18918    let fs = FakeFs::new(cx.executor());
18919    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18920    let (editor, cx) = cx
18921        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18922    editor.update_in(cx, |editor, _window, cx| {
18923        for (buffer, diff_base) in [
18924            (buffer_1.clone(), base_text_1),
18925            (buffer_2.clone(), base_text_2),
18926            (buffer_3.clone(), base_text_3),
18927        ] {
18928            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18929            editor
18930                .buffer
18931                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18932        }
18933    });
18934    cx.executor().run_until_parked();
18935
18936    editor.update_in(cx, |editor, window, cx| {
18937        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}");
18938        editor.select_all(&SelectAll, window, cx);
18939        editor.git_restore(&Default::default(), window, cx);
18940    });
18941    cx.executor().run_until_parked();
18942
18943    // When all ranges are selected, all buffer hunks are reverted.
18944    editor.update(cx, |editor, cx| {
18945        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");
18946    });
18947    buffer_1.update(cx, |buffer, _| {
18948        assert_eq!(buffer.text(), base_text_1);
18949    });
18950    buffer_2.update(cx, |buffer, _| {
18951        assert_eq!(buffer.text(), base_text_2);
18952    });
18953    buffer_3.update(cx, |buffer, _| {
18954        assert_eq!(buffer.text(), base_text_3);
18955    });
18956
18957    editor.update_in(cx, |editor, window, cx| {
18958        editor.undo(&Default::default(), window, cx);
18959    });
18960
18961    editor.update_in(cx, |editor, window, cx| {
18962        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18963            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18964        });
18965        editor.git_restore(&Default::default(), window, cx);
18966    });
18967
18968    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18969    // but not affect buffer_2 and its related excerpts.
18970    editor.update(cx, |editor, cx| {
18971        assert_eq!(
18972            editor.text(cx),
18973            "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}"
18974        );
18975    });
18976    buffer_1.update(cx, |buffer, _| {
18977        assert_eq!(buffer.text(), base_text_1);
18978    });
18979    buffer_2.update(cx, |buffer, _| {
18980        assert_eq!(
18981            buffer.text(),
18982            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18983        );
18984    });
18985    buffer_3.update(cx, |buffer, _| {
18986        assert_eq!(
18987            buffer.text(),
18988            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18989        );
18990    });
18991
18992    fn edit_first_char_of_every_line(text: &str) -> String {
18993        text.split('\n')
18994            .map(|line| format!("X{}", &line[1..]))
18995            .collect::<Vec<_>>()
18996            .join("\n")
18997    }
18998}
18999
19000#[gpui::test]
19001async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19002    init_test(cx, |_| {});
19003
19004    let cols = 4;
19005    let rows = 10;
19006    let sample_text_1 = sample_text(rows, cols, 'a');
19007    assert_eq!(
19008        sample_text_1,
19009        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19010    );
19011    let sample_text_2 = sample_text(rows, cols, 'l');
19012    assert_eq!(
19013        sample_text_2,
19014        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19015    );
19016    let sample_text_3 = sample_text(rows, cols, 'v');
19017    assert_eq!(
19018        sample_text_3,
19019        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19020    );
19021
19022    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19023    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19024    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19025
19026    let multi_buffer = cx.new(|cx| {
19027        let mut multibuffer = MultiBuffer::new(ReadWrite);
19028        multibuffer.push_excerpts(
19029            buffer_1.clone(),
19030            [
19031                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19032                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19033                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19034            ],
19035            cx,
19036        );
19037        multibuffer.push_excerpts(
19038            buffer_2.clone(),
19039            [
19040                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19041                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19042                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19043            ],
19044            cx,
19045        );
19046        multibuffer.push_excerpts(
19047            buffer_3.clone(),
19048            [
19049                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19050                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19051                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19052            ],
19053            cx,
19054        );
19055        multibuffer
19056    });
19057
19058    let fs = FakeFs::new(cx.executor());
19059    fs.insert_tree(
19060        "/a",
19061        json!({
19062            "main.rs": sample_text_1,
19063            "other.rs": sample_text_2,
19064            "lib.rs": sample_text_3,
19065        }),
19066    )
19067    .await;
19068    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19069    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19070    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19071    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19072        Editor::new(
19073            EditorMode::full(),
19074            multi_buffer,
19075            Some(project.clone()),
19076            window,
19077            cx,
19078        )
19079    });
19080    let multibuffer_item_id = workspace
19081        .update(cx, |workspace, window, cx| {
19082            assert!(
19083                workspace.active_item(cx).is_none(),
19084                "active item should be None before the first item is added"
19085            );
19086            workspace.add_item_to_active_pane(
19087                Box::new(multi_buffer_editor.clone()),
19088                None,
19089                true,
19090                window,
19091                cx,
19092            );
19093            let active_item = workspace
19094                .active_item(cx)
19095                .expect("should have an active item after adding the multi buffer");
19096            assert_eq!(
19097                active_item.buffer_kind(cx),
19098                ItemBufferKind::Multibuffer,
19099                "A multi buffer was expected to active after adding"
19100            );
19101            active_item.item_id()
19102        })
19103        .unwrap();
19104    cx.executor().run_until_parked();
19105
19106    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19107        editor.change_selections(
19108            SelectionEffects::scroll(Autoscroll::Next),
19109            window,
19110            cx,
19111            |s| s.select_ranges(Some(1..2)),
19112        );
19113        editor.open_excerpts(&OpenExcerpts, window, cx);
19114    });
19115    cx.executor().run_until_parked();
19116    let first_item_id = workspace
19117        .update(cx, |workspace, window, cx| {
19118            let active_item = workspace
19119                .active_item(cx)
19120                .expect("should have an active item after navigating into the 1st buffer");
19121            let first_item_id = active_item.item_id();
19122            assert_ne!(
19123                first_item_id, multibuffer_item_id,
19124                "Should navigate into the 1st buffer and activate it"
19125            );
19126            assert_eq!(
19127                active_item.buffer_kind(cx),
19128                ItemBufferKind::Singleton,
19129                "New active item should be a singleton buffer"
19130            );
19131            assert_eq!(
19132                active_item
19133                    .act_as::<Editor>(cx)
19134                    .expect("should have navigated into an editor for the 1st buffer")
19135                    .read(cx)
19136                    .text(cx),
19137                sample_text_1
19138            );
19139
19140            workspace
19141                .go_back(workspace.active_pane().downgrade(), window, cx)
19142                .detach_and_log_err(cx);
19143
19144            first_item_id
19145        })
19146        .unwrap();
19147    cx.executor().run_until_parked();
19148    workspace
19149        .update(cx, |workspace, _, cx| {
19150            let active_item = workspace
19151                .active_item(cx)
19152                .expect("should have an active item after navigating back");
19153            assert_eq!(
19154                active_item.item_id(),
19155                multibuffer_item_id,
19156                "Should navigate back to the multi buffer"
19157            );
19158            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19159        })
19160        .unwrap();
19161
19162    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19163        editor.change_selections(
19164            SelectionEffects::scroll(Autoscroll::Next),
19165            window,
19166            cx,
19167            |s| s.select_ranges(Some(39..40)),
19168        );
19169        editor.open_excerpts(&OpenExcerpts, window, cx);
19170    });
19171    cx.executor().run_until_parked();
19172    let second_item_id = workspace
19173        .update(cx, |workspace, window, cx| {
19174            let active_item = workspace
19175                .active_item(cx)
19176                .expect("should have an active item after navigating into the 2nd buffer");
19177            let second_item_id = active_item.item_id();
19178            assert_ne!(
19179                second_item_id, multibuffer_item_id,
19180                "Should navigate away from the multibuffer"
19181            );
19182            assert_ne!(
19183                second_item_id, first_item_id,
19184                "Should navigate into the 2nd buffer and activate it"
19185            );
19186            assert_eq!(
19187                active_item.buffer_kind(cx),
19188                ItemBufferKind::Singleton,
19189                "New active item should be a singleton buffer"
19190            );
19191            assert_eq!(
19192                active_item
19193                    .act_as::<Editor>(cx)
19194                    .expect("should have navigated into an editor")
19195                    .read(cx)
19196                    .text(cx),
19197                sample_text_2
19198            );
19199
19200            workspace
19201                .go_back(workspace.active_pane().downgrade(), window, cx)
19202                .detach_and_log_err(cx);
19203
19204            second_item_id
19205        })
19206        .unwrap();
19207    cx.executor().run_until_parked();
19208    workspace
19209        .update(cx, |workspace, _, cx| {
19210            let active_item = workspace
19211                .active_item(cx)
19212                .expect("should have an active item after navigating back from the 2nd buffer");
19213            assert_eq!(
19214                active_item.item_id(),
19215                multibuffer_item_id,
19216                "Should navigate back from the 2nd buffer to the multi buffer"
19217            );
19218            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19219        })
19220        .unwrap();
19221
19222    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19223        editor.change_selections(
19224            SelectionEffects::scroll(Autoscroll::Next),
19225            window,
19226            cx,
19227            |s| s.select_ranges(Some(70..70)),
19228        );
19229        editor.open_excerpts(&OpenExcerpts, window, cx);
19230    });
19231    cx.executor().run_until_parked();
19232    workspace
19233        .update(cx, |workspace, window, cx| {
19234            let active_item = workspace
19235                .active_item(cx)
19236                .expect("should have an active item after navigating into the 3rd buffer");
19237            let third_item_id = active_item.item_id();
19238            assert_ne!(
19239                third_item_id, multibuffer_item_id,
19240                "Should navigate into the 3rd buffer and activate it"
19241            );
19242            assert_ne!(third_item_id, first_item_id);
19243            assert_ne!(third_item_id, second_item_id);
19244            assert_eq!(
19245                active_item.buffer_kind(cx),
19246                ItemBufferKind::Singleton,
19247                "New active item should be a singleton buffer"
19248            );
19249            assert_eq!(
19250                active_item
19251                    .act_as::<Editor>(cx)
19252                    .expect("should have navigated into an editor")
19253                    .read(cx)
19254                    .text(cx),
19255                sample_text_3
19256            );
19257
19258            workspace
19259                .go_back(workspace.active_pane().downgrade(), window, cx)
19260                .detach_and_log_err(cx);
19261        })
19262        .unwrap();
19263    cx.executor().run_until_parked();
19264    workspace
19265        .update(cx, |workspace, _, cx| {
19266            let active_item = workspace
19267                .active_item(cx)
19268                .expect("should have an active item after navigating back from the 3rd buffer");
19269            assert_eq!(
19270                active_item.item_id(),
19271                multibuffer_item_id,
19272                "Should navigate back from the 3rd buffer to the multi buffer"
19273            );
19274            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19275        })
19276        .unwrap();
19277}
19278
19279#[gpui::test]
19280async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19281    init_test(cx, |_| {});
19282
19283    let mut cx = EditorTestContext::new(cx).await;
19284
19285    let diff_base = r#"
19286        use some::mod;
19287
19288        const A: u32 = 42;
19289
19290        fn main() {
19291            println!("hello");
19292
19293            println!("world");
19294        }
19295        "#
19296    .unindent();
19297
19298    cx.set_state(
19299        &r#"
19300        use some::modified;
19301
19302        ˇ
19303        fn main() {
19304            println!("hello there");
19305
19306            println!("around the");
19307            println!("world");
19308        }
19309        "#
19310        .unindent(),
19311    );
19312
19313    cx.set_head_text(&diff_base);
19314    executor.run_until_parked();
19315
19316    cx.update_editor(|editor, window, cx| {
19317        editor.go_to_next_hunk(&GoToHunk, window, cx);
19318        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19319    });
19320    executor.run_until_parked();
19321    cx.assert_state_with_diff(
19322        r#"
19323          use some::modified;
19324
19325
19326          fn main() {
19327        -     println!("hello");
19328        + ˇ    println!("hello there");
19329
19330              println!("around the");
19331              println!("world");
19332          }
19333        "#
19334        .unindent(),
19335    );
19336
19337    cx.update_editor(|editor, window, cx| {
19338        for _ in 0..2 {
19339            editor.go_to_next_hunk(&GoToHunk, window, cx);
19340            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19341        }
19342    });
19343    executor.run_until_parked();
19344    cx.assert_state_with_diff(
19345        r#"
19346        - use some::mod;
19347        + ˇuse some::modified;
19348
19349
19350          fn main() {
19351        -     println!("hello");
19352        +     println!("hello there");
19353
19354        +     println!("around the");
19355              println!("world");
19356          }
19357        "#
19358        .unindent(),
19359    );
19360
19361    cx.update_editor(|editor, window, cx| {
19362        editor.go_to_next_hunk(&GoToHunk, window, cx);
19363        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19364    });
19365    executor.run_until_parked();
19366    cx.assert_state_with_diff(
19367        r#"
19368        - use some::mod;
19369        + use some::modified;
19370
19371        - const A: u32 = 42;
19372          ˇ
19373          fn main() {
19374        -     println!("hello");
19375        +     println!("hello there");
19376
19377        +     println!("around the");
19378              println!("world");
19379          }
19380        "#
19381        .unindent(),
19382    );
19383
19384    cx.update_editor(|editor, window, cx| {
19385        editor.cancel(&Cancel, window, cx);
19386    });
19387
19388    cx.assert_state_with_diff(
19389        r#"
19390          use some::modified;
19391
19392          ˇ
19393          fn main() {
19394              println!("hello there");
19395
19396              println!("around the");
19397              println!("world");
19398          }
19399        "#
19400        .unindent(),
19401    );
19402}
19403
19404#[gpui::test]
19405async fn test_diff_base_change_with_expanded_diff_hunks(
19406    executor: BackgroundExecutor,
19407    cx: &mut TestAppContext,
19408) {
19409    init_test(cx, |_| {});
19410
19411    let mut cx = EditorTestContext::new(cx).await;
19412
19413    let diff_base = r#"
19414        use some::mod1;
19415        use some::mod2;
19416
19417        const A: u32 = 42;
19418        const B: u32 = 42;
19419        const C: u32 = 42;
19420
19421        fn main() {
19422            println!("hello");
19423
19424            println!("world");
19425        }
19426        "#
19427    .unindent();
19428
19429    cx.set_state(
19430        &r#"
19431        use some::mod2;
19432
19433        const A: u32 = 42;
19434        const C: u32 = 42;
19435
19436        fn main(ˇ) {
19437            //println!("hello");
19438
19439            println!("world");
19440            //
19441            //
19442        }
19443        "#
19444        .unindent(),
19445    );
19446
19447    cx.set_head_text(&diff_base);
19448    executor.run_until_parked();
19449
19450    cx.update_editor(|editor, window, cx| {
19451        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19452    });
19453    executor.run_until_parked();
19454    cx.assert_state_with_diff(
19455        r#"
19456        - use some::mod1;
19457          use some::mod2;
19458
19459          const A: u32 = 42;
19460        - const B: u32 = 42;
19461          const C: u32 = 42;
19462
19463          fn main(ˇ) {
19464        -     println!("hello");
19465        +     //println!("hello");
19466
19467              println!("world");
19468        +     //
19469        +     //
19470          }
19471        "#
19472        .unindent(),
19473    );
19474
19475    cx.set_head_text("new diff base!");
19476    executor.run_until_parked();
19477    cx.assert_state_with_diff(
19478        r#"
19479        - new diff base!
19480        + use some::mod2;
19481        +
19482        + const A: u32 = 42;
19483        + const C: u32 = 42;
19484        +
19485        + fn main(ˇ) {
19486        +     //println!("hello");
19487        +
19488        +     println!("world");
19489        +     //
19490        +     //
19491        + }
19492        "#
19493        .unindent(),
19494    );
19495}
19496
19497#[gpui::test]
19498async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19499    init_test(cx, |_| {});
19500
19501    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19502    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19503    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19504    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19505    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19506    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19507
19508    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19509    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19510    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19511
19512    let multi_buffer = cx.new(|cx| {
19513        let mut multibuffer = MultiBuffer::new(ReadWrite);
19514        multibuffer.push_excerpts(
19515            buffer_1.clone(),
19516            [
19517                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19518                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19519                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19520            ],
19521            cx,
19522        );
19523        multibuffer.push_excerpts(
19524            buffer_2.clone(),
19525            [
19526                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19527                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19528                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19529            ],
19530            cx,
19531        );
19532        multibuffer.push_excerpts(
19533            buffer_3.clone(),
19534            [
19535                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19536                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19537                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19538            ],
19539            cx,
19540        );
19541        multibuffer
19542    });
19543
19544    let editor =
19545        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19546    editor
19547        .update(cx, |editor, _window, cx| {
19548            for (buffer, diff_base) in [
19549                (buffer_1.clone(), file_1_old),
19550                (buffer_2.clone(), file_2_old),
19551                (buffer_3.clone(), file_3_old),
19552            ] {
19553                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19554                editor
19555                    .buffer
19556                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19557            }
19558        })
19559        .unwrap();
19560
19561    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19562    cx.run_until_parked();
19563
19564    cx.assert_editor_state(
19565        &"
19566            ˇaaa
19567            ccc
19568            ddd
19569
19570            ggg
19571            hhh
19572
19573
19574            lll
19575            mmm
19576            NNN
19577
19578            qqq
19579            rrr
19580
19581            uuu
19582            111
19583            222
19584            333
19585
19586            666
19587            777
19588
19589            000
19590            !!!"
19591        .unindent(),
19592    );
19593
19594    cx.update_editor(|editor, window, cx| {
19595        editor.select_all(&SelectAll, window, cx);
19596        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19597    });
19598    cx.executor().run_until_parked();
19599
19600    cx.assert_state_with_diff(
19601        "
19602            «aaa
19603          - bbb
19604            ccc
19605            ddd
19606
19607            ggg
19608            hhh
19609
19610
19611            lll
19612            mmm
19613          - nnn
19614          + NNN
19615
19616            qqq
19617            rrr
19618
19619            uuu
19620            111
19621            222
19622            333
19623
19624          + 666
19625            777
19626
19627            000
19628            !!!ˇ»"
19629            .unindent(),
19630    );
19631}
19632
19633#[gpui::test]
19634async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19635    init_test(cx, |_| {});
19636
19637    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19638    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19639
19640    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19641    let multi_buffer = cx.new(|cx| {
19642        let mut multibuffer = MultiBuffer::new(ReadWrite);
19643        multibuffer.push_excerpts(
19644            buffer.clone(),
19645            [
19646                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19647                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19648                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19649            ],
19650            cx,
19651        );
19652        multibuffer
19653    });
19654
19655    let editor =
19656        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19657    editor
19658        .update(cx, |editor, _window, cx| {
19659            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19660            editor
19661                .buffer
19662                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19663        })
19664        .unwrap();
19665
19666    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19667    cx.run_until_parked();
19668
19669    cx.update_editor(|editor, window, cx| {
19670        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19671    });
19672    cx.executor().run_until_parked();
19673
19674    // When the start of a hunk coincides with the start of its excerpt,
19675    // the hunk is expanded. When the start of a hunk is earlier than
19676    // the start of its excerpt, the hunk is not expanded.
19677    cx.assert_state_with_diff(
19678        "
19679            ˇaaa
19680          - bbb
19681          + BBB
19682
19683          - ddd
19684          - eee
19685          + DDD
19686          + EEE
19687            fff
19688
19689            iii
19690        "
19691        .unindent(),
19692    );
19693}
19694
19695#[gpui::test]
19696async fn test_edits_around_expanded_insertion_hunks(
19697    executor: BackgroundExecutor,
19698    cx: &mut TestAppContext,
19699) {
19700    init_test(cx, |_| {});
19701
19702    let mut cx = EditorTestContext::new(cx).await;
19703
19704    let diff_base = r#"
19705        use some::mod1;
19706        use some::mod2;
19707
19708        const A: u32 = 42;
19709
19710        fn main() {
19711            println!("hello");
19712
19713            println!("world");
19714        }
19715        "#
19716    .unindent();
19717    executor.run_until_parked();
19718    cx.set_state(
19719        &r#"
19720        use some::mod1;
19721        use some::mod2;
19722
19723        const A: u32 = 42;
19724        const B: u32 = 42;
19725        const C: u32 = 42;
19726        ˇ
19727
19728        fn main() {
19729            println!("hello");
19730
19731            println!("world");
19732        }
19733        "#
19734        .unindent(),
19735    );
19736
19737    cx.set_head_text(&diff_base);
19738    executor.run_until_parked();
19739
19740    cx.update_editor(|editor, window, cx| {
19741        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19742    });
19743    executor.run_until_parked();
19744
19745    cx.assert_state_with_diff(
19746        r#"
19747        use some::mod1;
19748        use some::mod2;
19749
19750        const A: u32 = 42;
19751      + const B: u32 = 42;
19752      + const C: u32 = 42;
19753      + ˇ
19754
19755        fn main() {
19756            println!("hello");
19757
19758            println!("world");
19759        }
19760      "#
19761        .unindent(),
19762    );
19763
19764    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19765    executor.run_until_parked();
19766
19767    cx.assert_state_with_diff(
19768        r#"
19769        use some::mod1;
19770        use some::mod2;
19771
19772        const A: u32 = 42;
19773      + const B: u32 = 42;
19774      + const C: u32 = 42;
19775      + const D: u32 = 42;
19776      + ˇ
19777
19778        fn main() {
19779            println!("hello");
19780
19781            println!("world");
19782        }
19783      "#
19784        .unindent(),
19785    );
19786
19787    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19788    executor.run_until_parked();
19789
19790    cx.assert_state_with_diff(
19791        r#"
19792        use some::mod1;
19793        use some::mod2;
19794
19795        const A: u32 = 42;
19796      + const B: u32 = 42;
19797      + const C: u32 = 42;
19798      + const D: u32 = 42;
19799      + const E: u32 = 42;
19800      + ˇ
19801
19802        fn main() {
19803            println!("hello");
19804
19805            println!("world");
19806        }
19807      "#
19808        .unindent(),
19809    );
19810
19811    cx.update_editor(|editor, window, cx| {
19812        editor.delete_line(&DeleteLine, window, cx);
19813    });
19814    executor.run_until_parked();
19815
19816    cx.assert_state_with_diff(
19817        r#"
19818        use some::mod1;
19819        use some::mod2;
19820
19821        const A: u32 = 42;
19822      + const B: u32 = 42;
19823      + const C: u32 = 42;
19824      + const D: u32 = 42;
19825      + const E: u32 = 42;
19826        ˇ
19827        fn main() {
19828            println!("hello");
19829
19830            println!("world");
19831        }
19832      "#
19833        .unindent(),
19834    );
19835
19836    cx.update_editor(|editor, window, cx| {
19837        editor.move_up(&MoveUp, window, cx);
19838        editor.delete_line(&DeleteLine, window, cx);
19839        editor.move_up(&MoveUp, window, cx);
19840        editor.delete_line(&DeleteLine, window, cx);
19841        editor.move_up(&MoveUp, window, cx);
19842        editor.delete_line(&DeleteLine, window, cx);
19843    });
19844    executor.run_until_parked();
19845    cx.assert_state_with_diff(
19846        r#"
19847        use some::mod1;
19848        use some::mod2;
19849
19850        const A: u32 = 42;
19851      + const B: u32 = 42;
19852        ˇ
19853        fn main() {
19854            println!("hello");
19855
19856            println!("world");
19857        }
19858      "#
19859        .unindent(),
19860    );
19861
19862    cx.update_editor(|editor, window, cx| {
19863        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19864        editor.delete_line(&DeleteLine, window, cx);
19865    });
19866    executor.run_until_parked();
19867    cx.assert_state_with_diff(
19868        r#"
19869        ˇ
19870        fn main() {
19871            println!("hello");
19872
19873            println!("world");
19874        }
19875      "#
19876        .unindent(),
19877    );
19878}
19879
19880#[gpui::test]
19881async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19882    init_test(cx, |_| {});
19883
19884    let mut cx = EditorTestContext::new(cx).await;
19885    cx.set_head_text(indoc! { "
19886        one
19887        two
19888        three
19889        four
19890        five
19891        "
19892    });
19893    cx.set_state(indoc! { "
19894        one
19895        ˇthree
19896        five
19897    "});
19898    cx.run_until_parked();
19899    cx.update_editor(|editor, window, cx| {
19900        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19901    });
19902    cx.assert_state_with_diff(
19903        indoc! { "
19904        one
19905      - two
19906        ˇthree
19907      - four
19908        five
19909    "}
19910        .to_string(),
19911    );
19912    cx.update_editor(|editor, window, cx| {
19913        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19914    });
19915
19916    cx.assert_state_with_diff(
19917        indoc! { "
19918        one
19919        ˇthree
19920        five
19921    "}
19922        .to_string(),
19923    );
19924
19925    cx.set_state(indoc! { "
19926        one
19927        ˇTWO
19928        three
19929        four
19930        five
19931    "});
19932    cx.run_until_parked();
19933    cx.update_editor(|editor, window, cx| {
19934        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19935    });
19936
19937    cx.assert_state_with_diff(
19938        indoc! { "
19939            one
19940          - two
19941          + ˇTWO
19942            three
19943            four
19944            five
19945        "}
19946        .to_string(),
19947    );
19948    cx.update_editor(|editor, window, cx| {
19949        editor.move_up(&Default::default(), window, cx);
19950        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19951    });
19952    cx.assert_state_with_diff(
19953        indoc! { "
19954            one
19955            ˇTWO
19956            three
19957            four
19958            five
19959        "}
19960        .to_string(),
19961    );
19962}
19963
19964#[gpui::test]
19965async fn test_edits_around_expanded_deletion_hunks(
19966    executor: BackgroundExecutor,
19967    cx: &mut TestAppContext,
19968) {
19969    init_test(cx, |_| {});
19970
19971    let mut cx = EditorTestContext::new(cx).await;
19972
19973    let diff_base = r#"
19974        use some::mod1;
19975        use some::mod2;
19976
19977        const A: u32 = 42;
19978        const B: u32 = 42;
19979        const C: u32 = 42;
19980
19981
19982        fn main() {
19983            println!("hello");
19984
19985            println!("world");
19986        }
19987    "#
19988    .unindent();
19989    executor.run_until_parked();
19990    cx.set_state(
19991        &r#"
19992        use some::mod1;
19993        use some::mod2;
19994
19995        ˇconst B: u32 = 42;
19996        const C: u32 = 42;
19997
19998
19999        fn main() {
20000            println!("hello");
20001
20002            println!("world");
20003        }
20004        "#
20005        .unindent(),
20006    );
20007
20008    cx.set_head_text(&diff_base);
20009    executor.run_until_parked();
20010
20011    cx.update_editor(|editor, window, cx| {
20012        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20013    });
20014    executor.run_until_parked();
20015
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
20026        fn main() {
20027            println!("hello");
20028
20029            println!("world");
20030        }
20031      "#
20032        .unindent(),
20033    );
20034
20035    cx.update_editor(|editor, window, cx| {
20036        editor.delete_line(&DeleteLine, window, cx);
20037    });
20038    executor.run_until_parked();
20039    cx.assert_state_with_diff(
20040        r#"
20041        use some::mod1;
20042        use some::mod2;
20043
20044      - const A: u32 = 42;
20045      - const B: u32 = 42;
20046        ˇconst C: u32 = 42;
20047
20048
20049        fn main() {
20050            println!("hello");
20051
20052            println!("world");
20053        }
20054      "#
20055        .unindent(),
20056    );
20057
20058    cx.update_editor(|editor, window, cx| {
20059        editor.delete_line(&DeleteLine, window, cx);
20060    });
20061    executor.run_until_parked();
20062    cx.assert_state_with_diff(
20063        r#"
20064        use some::mod1;
20065        use some::mod2;
20066
20067      - const A: u32 = 42;
20068      - const B: u32 = 42;
20069      - const C: u32 = 42;
20070        ˇ
20071
20072        fn main() {
20073            println!("hello");
20074
20075            println!("world");
20076        }
20077      "#
20078        .unindent(),
20079    );
20080
20081    cx.update_editor(|editor, window, cx| {
20082        editor.handle_input("replacement", window, cx);
20083    });
20084    executor.run_until_parked();
20085    cx.assert_state_with_diff(
20086        r#"
20087        use some::mod1;
20088        use some::mod2;
20089
20090      - const A: u32 = 42;
20091      - const B: u32 = 42;
20092      - const C: u32 = 42;
20093      -
20094      + replacementˇ
20095
20096        fn main() {
20097            println!("hello");
20098
20099            println!("world");
20100        }
20101      "#
20102        .unindent(),
20103    );
20104}
20105
20106#[gpui::test]
20107async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20108    init_test(cx, |_| {});
20109
20110    let mut cx = EditorTestContext::new(cx).await;
20111
20112    let base_text = r#"
20113        one
20114        two
20115        three
20116        four
20117        five
20118    "#
20119    .unindent();
20120    executor.run_until_parked();
20121    cx.set_state(
20122        &r#"
20123        one
20124        two
20125        fˇour
20126        five
20127        "#
20128        .unindent(),
20129    );
20130
20131    cx.set_head_text(&base_text);
20132    executor.run_until_parked();
20133
20134    cx.update_editor(|editor, window, cx| {
20135        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20136    });
20137    executor.run_until_parked();
20138
20139    cx.assert_state_with_diff(
20140        r#"
20141          one
20142          two
20143        - three
20144          fˇour
20145          five
20146        "#
20147        .unindent(),
20148    );
20149
20150    cx.update_editor(|editor, window, cx| {
20151        editor.backspace(&Backspace, window, cx);
20152        editor.backspace(&Backspace, window, cx);
20153    });
20154    executor.run_until_parked();
20155    cx.assert_state_with_diff(
20156        r#"
20157          one
20158          two
20159        - threeˇ
20160        - four
20161        + our
20162          five
20163        "#
20164        .unindent(),
20165    );
20166}
20167
20168#[gpui::test]
20169async fn test_edit_after_expanded_modification_hunk(
20170    executor: BackgroundExecutor,
20171    cx: &mut TestAppContext,
20172) {
20173    init_test(cx, |_| {});
20174
20175    let mut cx = EditorTestContext::new(cx).await;
20176
20177    let diff_base = 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 D: u32 = 42;
20185
20186
20187        fn main() {
20188            println!("hello");
20189
20190            println!("world");
20191        }"#
20192    .unindent();
20193
20194    cx.set_state(
20195        &r#"
20196        use some::mod1;
20197        use some::mod2;
20198
20199        const A: u32 = 42;
20200        const B: u32 = 42;
20201        const C: u32 = 43ˇ
20202        const D: u32 = 42;
20203
20204
20205        fn main() {
20206            println!("hello");
20207
20208            println!("world");
20209        }"#
20210        .unindent(),
20211    );
20212
20213    cx.set_head_text(&diff_base);
20214    executor.run_until_parked();
20215    cx.update_editor(|editor, window, cx| {
20216        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20217    });
20218    executor.run_until_parked();
20219
20220    cx.assert_state_with_diff(
20221        r#"
20222        use some::mod1;
20223        use some::mod2;
20224
20225        const A: u32 = 42;
20226        const B: u32 = 42;
20227      - const C: u32 = 42;
20228      + const C: u32 = 43ˇ
20229        const D: u32 = 42;
20230
20231
20232        fn main() {
20233            println!("hello");
20234
20235            println!("world");
20236        }"#
20237        .unindent(),
20238    );
20239
20240    cx.update_editor(|editor, window, cx| {
20241        editor.handle_input("\nnew_line\n", window, cx);
20242    });
20243    executor.run_until_parked();
20244
20245    cx.assert_state_with_diff(
20246        r#"
20247        use some::mod1;
20248        use some::mod2;
20249
20250        const A: u32 = 42;
20251        const B: u32 = 42;
20252      - const C: u32 = 42;
20253      + const C: u32 = 43
20254      + new_line
20255      + ˇ
20256        const D: u32 = 42;
20257
20258
20259        fn main() {
20260            println!("hello");
20261
20262            println!("world");
20263        }"#
20264        .unindent(),
20265    );
20266}
20267
20268#[gpui::test]
20269async fn test_stage_and_unstage_added_file_hunk(
20270    executor: BackgroundExecutor,
20271    cx: &mut TestAppContext,
20272) {
20273    init_test(cx, |_| {});
20274
20275    let mut cx = EditorTestContext::new(cx).await;
20276    cx.update_editor(|editor, _, cx| {
20277        editor.set_expand_all_diff_hunks(cx);
20278    });
20279
20280    let working_copy = r#"
20281            ˇfn main() {
20282                println!("hello, world!");
20283            }
20284        "#
20285    .unindent();
20286
20287    cx.set_state(&working_copy);
20288    executor.run_until_parked();
20289
20290    cx.assert_state_with_diff(
20291        r#"
20292            + ˇfn main() {
20293            +     println!("hello, world!");
20294            + }
20295        "#
20296        .unindent(),
20297    );
20298    cx.assert_index_text(None);
20299
20300    cx.update_editor(|editor, window, cx| {
20301        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20302    });
20303    executor.run_until_parked();
20304    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20305    cx.assert_state_with_diff(
20306        r#"
20307            + ˇfn main() {
20308            +     println!("hello, world!");
20309            + }
20310        "#
20311        .unindent(),
20312    );
20313
20314    cx.update_editor(|editor, window, cx| {
20315        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20316    });
20317    executor.run_until_parked();
20318    cx.assert_index_text(None);
20319}
20320
20321async fn setup_indent_guides_editor(
20322    text: &str,
20323    cx: &mut TestAppContext,
20324) -> (BufferId, EditorTestContext) {
20325    init_test(cx, |_| {});
20326
20327    let mut cx = EditorTestContext::new(cx).await;
20328
20329    let buffer_id = cx.update_editor(|editor, window, cx| {
20330        editor.set_text(text, window, cx);
20331        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20332
20333        buffer_ids[0]
20334    });
20335
20336    (buffer_id, cx)
20337}
20338
20339fn assert_indent_guides(
20340    range: Range<u32>,
20341    expected: Vec<IndentGuide>,
20342    active_indices: Option<Vec<usize>>,
20343    cx: &mut EditorTestContext,
20344) {
20345    let indent_guides = cx.update_editor(|editor, window, cx| {
20346        let snapshot = editor.snapshot(window, cx).display_snapshot;
20347        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20348            editor,
20349            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20350            true,
20351            &snapshot,
20352            cx,
20353        );
20354
20355        indent_guides.sort_by(|a, b| {
20356            a.depth.cmp(&b.depth).then(
20357                a.start_row
20358                    .cmp(&b.start_row)
20359                    .then(a.end_row.cmp(&b.end_row)),
20360            )
20361        });
20362        indent_guides
20363    });
20364
20365    if let Some(expected) = active_indices {
20366        let active_indices = cx.update_editor(|editor, window, cx| {
20367            let snapshot = editor.snapshot(window, cx).display_snapshot;
20368            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20369        });
20370
20371        assert_eq!(
20372            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20373            expected,
20374            "Active indent guide indices do not match"
20375        );
20376    }
20377
20378    assert_eq!(indent_guides, expected, "Indent guides do not match");
20379}
20380
20381fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20382    IndentGuide {
20383        buffer_id,
20384        start_row: MultiBufferRow(start_row),
20385        end_row: MultiBufferRow(end_row),
20386        depth,
20387        tab_size: 4,
20388        settings: IndentGuideSettings {
20389            enabled: true,
20390            line_width: 1,
20391            active_line_width: 1,
20392            coloring: IndentGuideColoring::default(),
20393            background_coloring: IndentGuideBackgroundColoring::default(),
20394        },
20395    }
20396}
20397
20398#[gpui::test]
20399async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20400    let (buffer_id, mut cx) = setup_indent_guides_editor(
20401        &"
20402        fn main() {
20403            let a = 1;
20404        }"
20405        .unindent(),
20406        cx,
20407    )
20408    .await;
20409
20410    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20411}
20412
20413#[gpui::test]
20414async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20415    let (buffer_id, mut cx) = setup_indent_guides_editor(
20416        &"
20417        fn main() {
20418            let a = 1;
20419            let b = 2;
20420        }"
20421        .unindent(),
20422        cx,
20423    )
20424    .await;
20425
20426    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20427}
20428
20429#[gpui::test]
20430async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20431    let (buffer_id, mut cx) = setup_indent_guides_editor(
20432        &"
20433        fn main() {
20434            let a = 1;
20435            if a == 3 {
20436                let b = 2;
20437            } else {
20438                let c = 3;
20439            }
20440        }"
20441        .unindent(),
20442        cx,
20443    )
20444    .await;
20445
20446    assert_indent_guides(
20447        0..8,
20448        vec![
20449            indent_guide(buffer_id, 1, 6, 0),
20450            indent_guide(buffer_id, 3, 3, 1),
20451            indent_guide(buffer_id, 5, 5, 1),
20452        ],
20453        None,
20454        &mut cx,
20455    );
20456}
20457
20458#[gpui::test]
20459async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20460    let (buffer_id, mut cx) = setup_indent_guides_editor(
20461        &"
20462        fn main() {
20463            let a = 1;
20464                let b = 2;
20465            let c = 3;
20466        }"
20467        .unindent(),
20468        cx,
20469    )
20470    .await;
20471
20472    assert_indent_guides(
20473        0..5,
20474        vec![
20475            indent_guide(buffer_id, 1, 3, 0),
20476            indent_guide(buffer_id, 2, 2, 1),
20477        ],
20478        None,
20479        &mut cx,
20480    );
20481}
20482
20483#[gpui::test]
20484async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20485    let (buffer_id, mut cx) = setup_indent_guides_editor(
20486        &"
20487        fn main() {
20488            let a = 1;
20489
20490            let c = 3;
20491        }"
20492        .unindent(),
20493        cx,
20494    )
20495    .await;
20496
20497    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20498}
20499
20500#[gpui::test]
20501async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20502    let (buffer_id, mut cx) = setup_indent_guides_editor(
20503        &"
20504        fn main() {
20505            let a = 1;
20506
20507            let c = 3;
20508
20509            if a == 3 {
20510                let b = 2;
20511            } else {
20512                let c = 3;
20513            }
20514        }"
20515        .unindent(),
20516        cx,
20517    )
20518    .await;
20519
20520    assert_indent_guides(
20521        0..11,
20522        vec![
20523            indent_guide(buffer_id, 1, 9, 0),
20524            indent_guide(buffer_id, 6, 6, 1),
20525            indent_guide(buffer_id, 8, 8, 1),
20526        ],
20527        None,
20528        &mut cx,
20529    );
20530}
20531
20532#[gpui::test]
20533async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20534    let (buffer_id, mut cx) = setup_indent_guides_editor(
20535        &"
20536        fn main() {
20537            let a = 1;
20538
20539            let c = 3;
20540
20541            if a == 3 {
20542                let b = 2;
20543            } else {
20544                let c = 3;
20545            }
20546        }"
20547        .unindent(),
20548        cx,
20549    )
20550    .await;
20551
20552    assert_indent_guides(
20553        1..11,
20554        vec![
20555            indent_guide(buffer_id, 1, 9, 0),
20556            indent_guide(buffer_id, 6, 6, 1),
20557            indent_guide(buffer_id, 8, 8, 1),
20558        ],
20559        None,
20560        &mut cx,
20561    );
20562}
20563
20564#[gpui::test]
20565async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20566    let (buffer_id, mut cx) = setup_indent_guides_editor(
20567        &"
20568        fn main() {
20569            let a = 1;
20570
20571            let c = 3;
20572
20573            if a == 3 {
20574                let b = 2;
20575            } else {
20576                let c = 3;
20577            }
20578        }"
20579        .unindent(),
20580        cx,
20581    )
20582    .await;
20583
20584    assert_indent_guides(
20585        1..10,
20586        vec![
20587            indent_guide(buffer_id, 1, 9, 0),
20588            indent_guide(buffer_id, 6, 6, 1),
20589            indent_guide(buffer_id, 8, 8, 1),
20590        ],
20591        None,
20592        &mut cx,
20593    );
20594}
20595
20596#[gpui::test]
20597async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20598    let (buffer_id, mut cx) = setup_indent_guides_editor(
20599        &"
20600        fn main() {
20601            if a {
20602                b(
20603                    c,
20604                    d,
20605                )
20606            } else {
20607                e(
20608                    f
20609                )
20610            }
20611        }"
20612        .unindent(),
20613        cx,
20614    )
20615    .await;
20616
20617    assert_indent_guides(
20618        0..11,
20619        vec![
20620            indent_guide(buffer_id, 1, 10, 0),
20621            indent_guide(buffer_id, 2, 5, 1),
20622            indent_guide(buffer_id, 7, 9, 1),
20623            indent_guide(buffer_id, 3, 4, 2),
20624            indent_guide(buffer_id, 8, 8, 2),
20625        ],
20626        None,
20627        &mut cx,
20628    );
20629
20630    cx.update_editor(|editor, window, cx| {
20631        editor.fold_at(MultiBufferRow(2), window, cx);
20632        assert_eq!(
20633            editor.display_text(cx),
20634            "
20635            fn main() {
20636                if a {
20637                    b(⋯
20638                    )
20639                } else {
20640                    e(
20641                        f
20642                    )
20643                }
20644            }"
20645            .unindent()
20646        );
20647    });
20648
20649    assert_indent_guides(
20650        0..11,
20651        vec![
20652            indent_guide(buffer_id, 1, 10, 0),
20653            indent_guide(buffer_id, 2, 5, 1),
20654            indent_guide(buffer_id, 7, 9, 1),
20655            indent_guide(buffer_id, 8, 8, 2),
20656        ],
20657        None,
20658        &mut cx,
20659    );
20660}
20661
20662#[gpui::test]
20663async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20664    let (buffer_id, mut cx) = setup_indent_guides_editor(
20665        &"
20666        block1
20667            block2
20668                block3
20669                    block4
20670            block2
20671        block1
20672        block1"
20673            .unindent(),
20674        cx,
20675    )
20676    .await;
20677
20678    assert_indent_guides(
20679        1..10,
20680        vec![
20681            indent_guide(buffer_id, 1, 4, 0),
20682            indent_guide(buffer_id, 2, 3, 1),
20683            indent_guide(buffer_id, 3, 3, 2),
20684        ],
20685        None,
20686        &mut cx,
20687    );
20688}
20689
20690#[gpui::test]
20691async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20692    let (buffer_id, mut cx) = setup_indent_guides_editor(
20693        &"
20694        block1
20695            block2
20696                block3
20697
20698        block1
20699        block1"
20700            .unindent(),
20701        cx,
20702    )
20703    .await;
20704
20705    assert_indent_guides(
20706        0..6,
20707        vec![
20708            indent_guide(buffer_id, 1, 2, 0),
20709            indent_guide(buffer_id, 2, 2, 1),
20710        ],
20711        None,
20712        &mut cx,
20713    );
20714}
20715
20716#[gpui::test]
20717async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20718    let (buffer_id, mut cx) = setup_indent_guides_editor(
20719        &"
20720        function component() {
20721        \treturn (
20722        \t\t\t
20723        \t\t<div>
20724        \t\t\t<abc></abc>
20725        \t\t</div>
20726        \t)
20727        }"
20728        .unindent(),
20729        cx,
20730    )
20731    .await;
20732
20733    assert_indent_guides(
20734        0..8,
20735        vec![
20736            indent_guide(buffer_id, 1, 6, 0),
20737            indent_guide(buffer_id, 2, 5, 1),
20738            indent_guide(buffer_id, 4, 4, 2),
20739        ],
20740        None,
20741        &mut cx,
20742    );
20743}
20744
20745#[gpui::test]
20746async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20747    let (buffer_id, mut cx) = setup_indent_guides_editor(
20748        &"
20749        function component() {
20750        \treturn (
20751        \t
20752        \t\t<div>
20753        \t\t\t<abc></abc>
20754        \t\t</div>
20755        \t)
20756        }"
20757        .unindent(),
20758        cx,
20759    )
20760    .await;
20761
20762    assert_indent_guides(
20763        0..8,
20764        vec![
20765            indent_guide(buffer_id, 1, 6, 0),
20766            indent_guide(buffer_id, 2, 5, 1),
20767            indent_guide(buffer_id, 4, 4, 2),
20768        ],
20769        None,
20770        &mut cx,
20771    );
20772}
20773
20774#[gpui::test]
20775async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20776    let (buffer_id, mut cx) = setup_indent_guides_editor(
20777        &"
20778        block1
20779
20780
20781
20782            block2
20783        "
20784        .unindent(),
20785        cx,
20786    )
20787    .await;
20788
20789    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20790}
20791
20792#[gpui::test]
20793async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20794    let (buffer_id, mut cx) = setup_indent_guides_editor(
20795        &"
20796        def a:
20797        \tb = 3
20798        \tif True:
20799        \t\tc = 4
20800        \t\td = 5
20801        \tprint(b)
20802        "
20803        .unindent(),
20804        cx,
20805    )
20806    .await;
20807
20808    assert_indent_guides(
20809        0..6,
20810        vec![
20811            indent_guide(buffer_id, 1, 5, 0),
20812            indent_guide(buffer_id, 3, 4, 1),
20813        ],
20814        None,
20815        &mut cx,
20816    );
20817}
20818
20819#[gpui::test]
20820async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20821    let (buffer_id, mut cx) = setup_indent_guides_editor(
20822        &"
20823    fn main() {
20824        let a = 1;
20825    }"
20826        .unindent(),
20827        cx,
20828    )
20829    .await;
20830
20831    cx.update_editor(|editor, window, cx| {
20832        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20833            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20834        });
20835    });
20836
20837    assert_indent_guides(
20838        0..3,
20839        vec![indent_guide(buffer_id, 1, 1, 0)],
20840        Some(vec![0]),
20841        &mut cx,
20842    );
20843}
20844
20845#[gpui::test]
20846async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20847    let (buffer_id, mut cx) = setup_indent_guides_editor(
20848        &"
20849    fn main() {
20850        if 1 == 2 {
20851            let a = 1;
20852        }
20853    }"
20854        .unindent(),
20855        cx,
20856    )
20857    .await;
20858
20859    cx.update_editor(|editor, window, cx| {
20860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20861            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20862        });
20863    });
20864
20865    assert_indent_guides(
20866        0..4,
20867        vec![
20868            indent_guide(buffer_id, 1, 3, 0),
20869            indent_guide(buffer_id, 2, 2, 1),
20870        ],
20871        Some(vec![1]),
20872        &mut cx,
20873    );
20874
20875    cx.update_editor(|editor, window, cx| {
20876        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20877            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20878        });
20879    });
20880
20881    assert_indent_guides(
20882        0..4,
20883        vec![
20884            indent_guide(buffer_id, 1, 3, 0),
20885            indent_guide(buffer_id, 2, 2, 1),
20886        ],
20887        Some(vec![1]),
20888        &mut cx,
20889    );
20890
20891    cx.update_editor(|editor, window, cx| {
20892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20893            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20894        });
20895    });
20896
20897    assert_indent_guides(
20898        0..4,
20899        vec![
20900            indent_guide(buffer_id, 1, 3, 0),
20901            indent_guide(buffer_id, 2, 2, 1),
20902        ],
20903        Some(vec![0]),
20904        &mut cx,
20905    );
20906}
20907
20908#[gpui::test]
20909async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20910    let (buffer_id, mut cx) = setup_indent_guides_editor(
20911        &"
20912    fn main() {
20913        let a = 1;
20914
20915        let b = 2;
20916    }"
20917        .unindent(),
20918        cx,
20919    )
20920    .await;
20921
20922    cx.update_editor(|editor, window, cx| {
20923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20924            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20925        });
20926    });
20927
20928    assert_indent_guides(
20929        0..5,
20930        vec![indent_guide(buffer_id, 1, 3, 0)],
20931        Some(vec![0]),
20932        &mut cx,
20933    );
20934}
20935
20936#[gpui::test]
20937async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20938    let (buffer_id, mut cx) = setup_indent_guides_editor(
20939        &"
20940    def m:
20941        a = 1
20942        pass"
20943            .unindent(),
20944        cx,
20945    )
20946    .await;
20947
20948    cx.update_editor(|editor, window, cx| {
20949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20950            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20951        });
20952    });
20953
20954    assert_indent_guides(
20955        0..3,
20956        vec![indent_guide(buffer_id, 1, 2, 0)],
20957        Some(vec![0]),
20958        &mut cx,
20959    );
20960}
20961
20962#[gpui::test]
20963async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20964    init_test(cx, |_| {});
20965    let mut cx = EditorTestContext::new(cx).await;
20966    let text = indoc! {
20967        "
20968        impl A {
20969            fn b() {
20970                0;
20971                3;
20972                5;
20973                6;
20974                7;
20975            }
20976        }
20977        "
20978    };
20979    let base_text = indoc! {
20980        "
20981        impl A {
20982            fn b() {
20983                0;
20984                1;
20985                2;
20986                3;
20987                4;
20988            }
20989            fn c() {
20990                5;
20991                6;
20992                7;
20993            }
20994        }
20995        "
20996    };
20997
20998    cx.update_editor(|editor, window, cx| {
20999        editor.set_text(text, window, cx);
21000
21001        editor.buffer().update(cx, |multibuffer, cx| {
21002            let buffer = multibuffer.as_singleton().unwrap();
21003            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21004
21005            multibuffer.set_all_diff_hunks_expanded(cx);
21006            multibuffer.add_diff(diff, cx);
21007
21008            buffer.read(cx).remote_id()
21009        })
21010    });
21011    cx.run_until_parked();
21012
21013    cx.assert_state_with_diff(
21014        indoc! { "
21015          impl A {
21016              fn b() {
21017                  0;
21018        -         1;
21019        -         2;
21020                  3;
21021        -         4;
21022        -     }
21023        -     fn c() {
21024                  5;
21025                  6;
21026                  7;
21027              }
21028          }
21029          ˇ"
21030        }
21031        .to_string(),
21032    );
21033
21034    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21035        editor
21036            .snapshot(window, cx)
21037            .buffer_snapshot()
21038            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21039            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21040            .collect::<Vec<_>>()
21041    });
21042    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21043    assert_eq!(
21044        actual_guides,
21045        vec![
21046            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21047            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21048            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21049        ]
21050    );
21051}
21052
21053#[gpui::test]
21054async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21055    init_test(cx, |_| {});
21056    let mut cx = EditorTestContext::new(cx).await;
21057
21058    let diff_base = r#"
21059        a
21060        b
21061        c
21062        "#
21063    .unindent();
21064
21065    cx.set_state(
21066        &r#"
21067        ˇA
21068        b
21069        C
21070        "#
21071        .unindent(),
21072    );
21073    cx.set_head_text(&diff_base);
21074    cx.update_editor(|editor, window, cx| {
21075        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21076    });
21077    executor.run_until_parked();
21078
21079    let both_hunks_expanded = r#"
21080        - a
21081        + ˇA
21082          b
21083        - c
21084        + C
21085        "#
21086    .unindent();
21087
21088    cx.assert_state_with_diff(both_hunks_expanded.clone());
21089
21090    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21091        let snapshot = editor.snapshot(window, cx);
21092        let hunks = editor
21093            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21094            .collect::<Vec<_>>();
21095        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21096        let buffer_id = hunks[0].buffer_id;
21097        hunks
21098            .into_iter()
21099            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21100            .collect::<Vec<_>>()
21101    });
21102    assert_eq!(hunk_ranges.len(), 2);
21103
21104    cx.update_editor(|editor, _, cx| {
21105        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21106    });
21107    executor.run_until_parked();
21108
21109    let second_hunk_expanded = r#"
21110          ˇA
21111          b
21112        - c
21113        + C
21114        "#
21115    .unindent();
21116
21117    cx.assert_state_with_diff(second_hunk_expanded);
21118
21119    cx.update_editor(|editor, _, cx| {
21120        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21121    });
21122    executor.run_until_parked();
21123
21124    cx.assert_state_with_diff(both_hunks_expanded.clone());
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    let first_hunk_expanded = r#"
21132        - a
21133        + ˇA
21134          b
21135          C
21136        "#
21137    .unindent();
21138
21139    cx.assert_state_with_diff(first_hunk_expanded);
21140
21141    cx.update_editor(|editor, _, cx| {
21142        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21143    });
21144    executor.run_until_parked();
21145
21146    cx.assert_state_with_diff(both_hunks_expanded);
21147
21148    cx.set_state(
21149        &r#"
21150        ˇA
21151        b
21152        "#
21153        .unindent(),
21154    );
21155    cx.run_until_parked();
21156
21157    // TODO this cursor position seems bad
21158    cx.assert_state_with_diff(
21159        r#"
21160        - ˇa
21161        + A
21162          b
21163        "#
21164        .unindent(),
21165    );
21166
21167    cx.update_editor(|editor, window, cx| {
21168        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21169    });
21170
21171    cx.assert_state_with_diff(
21172        r#"
21173            - ˇa
21174            + A
21175              b
21176            - c
21177            "#
21178        .unindent(),
21179    );
21180
21181    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21182        let snapshot = editor.snapshot(window, cx);
21183        let hunks = editor
21184            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21185            .collect::<Vec<_>>();
21186        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21187        let buffer_id = hunks[0].buffer_id;
21188        hunks
21189            .into_iter()
21190            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21191            .collect::<Vec<_>>()
21192    });
21193    assert_eq!(hunk_ranges.len(), 2);
21194
21195    cx.update_editor(|editor, _, cx| {
21196        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21197    });
21198    executor.run_until_parked();
21199
21200    cx.assert_state_with_diff(
21201        r#"
21202        - ˇa
21203        + A
21204          b
21205        "#
21206        .unindent(),
21207    );
21208}
21209
21210#[gpui::test]
21211async fn test_toggle_deletion_hunk_at_start_of_file(
21212    executor: BackgroundExecutor,
21213    cx: &mut TestAppContext,
21214) {
21215    init_test(cx, |_| {});
21216    let mut cx = EditorTestContext::new(cx).await;
21217
21218    let diff_base = r#"
21219        a
21220        b
21221        c
21222        "#
21223    .unindent();
21224
21225    cx.set_state(
21226        &r#"
21227        ˇb
21228        c
21229        "#
21230        .unindent(),
21231    );
21232    cx.set_head_text(&diff_base);
21233    cx.update_editor(|editor, window, cx| {
21234        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21235    });
21236    executor.run_until_parked();
21237
21238    let hunk_expanded = r#"
21239        - a
21240          ˇb
21241          c
21242        "#
21243    .unindent();
21244
21245    cx.assert_state_with_diff(hunk_expanded.clone());
21246
21247    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21248        let snapshot = editor.snapshot(window, cx);
21249        let hunks = editor
21250            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21251            .collect::<Vec<_>>();
21252        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21253        let buffer_id = hunks[0].buffer_id;
21254        hunks
21255            .into_iter()
21256            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21257            .collect::<Vec<_>>()
21258    });
21259    assert_eq!(hunk_ranges.len(), 1);
21260
21261    cx.update_editor(|editor, _, cx| {
21262        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21263    });
21264    executor.run_until_parked();
21265
21266    let hunk_collapsed = r#"
21267          ˇb
21268          c
21269        "#
21270    .unindent();
21271
21272    cx.assert_state_with_diff(hunk_collapsed);
21273
21274    cx.update_editor(|editor, _, cx| {
21275        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21276    });
21277    executor.run_until_parked();
21278
21279    cx.assert_state_with_diff(hunk_expanded);
21280}
21281
21282#[gpui::test]
21283async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21284    init_test(cx, |_| {});
21285
21286    let fs = FakeFs::new(cx.executor());
21287    fs.insert_tree(
21288        path!("/test"),
21289        json!({
21290            ".git": {},
21291            "file-1": "ONE\n",
21292            "file-2": "TWO\n",
21293            "file-3": "THREE\n",
21294        }),
21295    )
21296    .await;
21297
21298    fs.set_head_for_repo(
21299        path!("/test/.git").as_ref(),
21300        &[
21301            ("file-1", "one\n".into()),
21302            ("file-2", "two\n".into()),
21303            ("file-3", "three\n".into()),
21304        ],
21305        "deadbeef",
21306    );
21307
21308    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21309    let mut buffers = vec![];
21310    for i in 1..=3 {
21311        let buffer = project
21312            .update(cx, |project, cx| {
21313                let path = format!(path!("/test/file-{}"), i);
21314                project.open_local_buffer(path, cx)
21315            })
21316            .await
21317            .unwrap();
21318        buffers.push(buffer);
21319    }
21320
21321    let multibuffer = cx.new(|cx| {
21322        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21323        multibuffer.set_all_diff_hunks_expanded(cx);
21324        for buffer in &buffers {
21325            let snapshot = buffer.read(cx).snapshot();
21326            multibuffer.set_excerpts_for_path(
21327                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21328                buffer.clone(),
21329                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21330                2,
21331                cx,
21332            );
21333        }
21334        multibuffer
21335    });
21336
21337    let editor = cx.add_window(|window, cx| {
21338        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21339    });
21340    cx.run_until_parked();
21341
21342    let snapshot = editor
21343        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21344        .unwrap();
21345    let hunks = snapshot
21346        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21347        .map(|hunk| match hunk {
21348            DisplayDiffHunk::Unfolded {
21349                display_row_range, ..
21350            } => display_row_range,
21351            DisplayDiffHunk::Folded { .. } => unreachable!(),
21352        })
21353        .collect::<Vec<_>>();
21354    assert_eq!(
21355        hunks,
21356        [
21357            DisplayRow(2)..DisplayRow(4),
21358            DisplayRow(7)..DisplayRow(9),
21359            DisplayRow(12)..DisplayRow(14),
21360        ]
21361    );
21362}
21363
21364#[gpui::test]
21365async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21366    init_test(cx, |_| {});
21367
21368    let mut cx = EditorTestContext::new(cx).await;
21369    cx.set_head_text(indoc! { "
21370        one
21371        two
21372        three
21373        four
21374        five
21375        "
21376    });
21377    cx.set_index_text(indoc! { "
21378        one
21379        two
21380        three
21381        four
21382        five
21383        "
21384    });
21385    cx.set_state(indoc! {"
21386        one
21387        TWO
21388        ˇTHREE
21389        FOUR
21390        five
21391    "});
21392    cx.run_until_parked();
21393    cx.update_editor(|editor, window, cx| {
21394        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21395    });
21396    cx.run_until_parked();
21397    cx.assert_index_text(Some(indoc! {"
21398        one
21399        TWO
21400        THREE
21401        FOUR
21402        five
21403    "}));
21404    cx.set_state(indoc! { "
21405        one
21406        TWO
21407        ˇTHREE-HUNDRED
21408        FOUR
21409        five
21410    "});
21411    cx.run_until_parked();
21412    cx.update_editor(|editor, window, cx| {
21413        let snapshot = editor.snapshot(window, cx);
21414        let hunks = editor
21415            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21416            .collect::<Vec<_>>();
21417        assert_eq!(hunks.len(), 1);
21418        assert_eq!(
21419            hunks[0].status(),
21420            DiffHunkStatus {
21421                kind: DiffHunkStatusKind::Modified,
21422                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21423            }
21424        );
21425
21426        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21427    });
21428    cx.run_until_parked();
21429    cx.assert_index_text(Some(indoc! {"
21430        one
21431        TWO
21432        THREE-HUNDRED
21433        FOUR
21434        five
21435    "}));
21436}
21437
21438#[gpui::test]
21439fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21440    init_test(cx, |_| {});
21441
21442    let editor = cx.add_window(|window, cx| {
21443        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21444        build_editor(buffer, window, cx)
21445    });
21446
21447    let render_args = Arc::new(Mutex::new(None));
21448    let snapshot = editor
21449        .update(cx, |editor, window, cx| {
21450            let snapshot = editor.buffer().read(cx).snapshot(cx);
21451            let range =
21452                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21453
21454            struct RenderArgs {
21455                row: MultiBufferRow,
21456                folded: bool,
21457                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21458            }
21459
21460            let crease = Crease::inline(
21461                range,
21462                FoldPlaceholder::test(),
21463                {
21464                    let toggle_callback = render_args.clone();
21465                    move |row, folded, callback, _window, _cx| {
21466                        *toggle_callback.lock() = Some(RenderArgs {
21467                            row,
21468                            folded,
21469                            callback,
21470                        });
21471                        div()
21472                    }
21473                },
21474                |_row, _folded, _window, _cx| div(),
21475            );
21476
21477            editor.insert_creases(Some(crease), cx);
21478            let snapshot = editor.snapshot(window, cx);
21479            let _div =
21480                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21481            snapshot
21482        })
21483        .unwrap();
21484
21485    let render_args = render_args.lock().take().unwrap();
21486    assert_eq!(render_args.row, MultiBufferRow(1));
21487    assert!(!render_args.folded);
21488    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21489
21490    cx.update_window(*editor, |_, window, cx| {
21491        (render_args.callback)(true, window, cx)
21492    })
21493    .unwrap();
21494    let snapshot = editor
21495        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21496        .unwrap();
21497    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21498
21499    cx.update_window(*editor, |_, window, cx| {
21500        (render_args.callback)(false, window, cx)
21501    })
21502    .unwrap();
21503    let snapshot = editor
21504        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21505        .unwrap();
21506    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21507}
21508
21509#[gpui::test]
21510async fn test_input_text(cx: &mut TestAppContext) {
21511    init_test(cx, |_| {});
21512    let mut cx = EditorTestContext::new(cx).await;
21513
21514    cx.set_state(
21515        &r#"ˇone
21516        two
21517
21518        three
21519        fourˇ
21520        five
21521
21522        siˇx"#
21523            .unindent(),
21524    );
21525
21526    cx.dispatch_action(HandleInput(String::new()));
21527    cx.assert_editor_state(
21528        &r#"ˇone
21529        two
21530
21531        three
21532        fourˇ
21533        five
21534
21535        siˇx"#
21536            .unindent(),
21537    );
21538
21539    cx.dispatch_action(HandleInput("AAAA".to_string()));
21540    cx.assert_editor_state(
21541        &r#"AAAAˇone
21542        two
21543
21544        three
21545        fourAAAAˇ
21546        five
21547
21548        siAAAAˇx"#
21549            .unindent(),
21550    );
21551}
21552
21553#[gpui::test]
21554async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21555    init_test(cx, |_| {});
21556
21557    let mut cx = EditorTestContext::new(cx).await;
21558    cx.set_state(
21559        r#"let foo = 1;
21560let foo = 2;
21561let foo = 3;
21562let fooˇ = 4;
21563let foo = 5;
21564let foo = 6;
21565let foo = 7;
21566let foo = 8;
21567let foo = 9;
21568let foo = 10;
21569let foo = 11;
21570let foo = 12;
21571let foo = 13;
21572let foo = 14;
21573let foo = 15;"#,
21574    );
21575
21576    cx.update_editor(|e, window, cx| {
21577        assert_eq!(
21578            e.next_scroll_position,
21579            NextScrollCursorCenterTopBottom::Center,
21580            "Default next scroll direction is center",
21581        );
21582
21583        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21584        assert_eq!(
21585            e.next_scroll_position,
21586            NextScrollCursorCenterTopBottom::Top,
21587            "After center, next scroll direction should be top",
21588        );
21589
21590        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21591        assert_eq!(
21592            e.next_scroll_position,
21593            NextScrollCursorCenterTopBottom::Bottom,
21594            "After top, next scroll direction should be bottom",
21595        );
21596
21597        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21598        assert_eq!(
21599            e.next_scroll_position,
21600            NextScrollCursorCenterTopBottom::Center,
21601            "After bottom, scrolling should start over",
21602        );
21603
21604        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21605        assert_eq!(
21606            e.next_scroll_position,
21607            NextScrollCursorCenterTopBottom::Top,
21608            "Scrolling continues if retriggered fast enough"
21609        );
21610    });
21611
21612    cx.executor()
21613        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21614    cx.executor().run_until_parked();
21615    cx.update_editor(|e, _, _| {
21616        assert_eq!(
21617            e.next_scroll_position,
21618            NextScrollCursorCenterTopBottom::Center,
21619            "If scrolling is not triggered fast enough, it should reset"
21620        );
21621    });
21622}
21623
21624#[gpui::test]
21625async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21626    init_test(cx, |_| {});
21627    let mut cx = EditorLspTestContext::new_rust(
21628        lsp::ServerCapabilities {
21629            definition_provider: Some(lsp::OneOf::Left(true)),
21630            references_provider: Some(lsp::OneOf::Left(true)),
21631            ..lsp::ServerCapabilities::default()
21632        },
21633        cx,
21634    )
21635    .await;
21636
21637    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21638        let go_to_definition = cx
21639            .lsp
21640            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21641                move |params, _| async move {
21642                    if empty_go_to_definition {
21643                        Ok(None)
21644                    } else {
21645                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21646                            uri: params.text_document_position_params.text_document.uri,
21647                            range: lsp::Range::new(
21648                                lsp::Position::new(4, 3),
21649                                lsp::Position::new(4, 6),
21650                            ),
21651                        })))
21652                    }
21653                },
21654            );
21655        let references = cx
21656            .lsp
21657            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21658                Ok(Some(vec![lsp::Location {
21659                    uri: params.text_document_position.text_document.uri,
21660                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21661                }]))
21662            });
21663        (go_to_definition, references)
21664    };
21665
21666    cx.set_state(
21667        &r#"fn one() {
21668            let mut a = ˇtwo();
21669        }
21670
21671        fn two() {}"#
21672            .unindent(),
21673    );
21674    set_up_lsp_handlers(false, &mut cx);
21675    let navigated = cx
21676        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21677        .await
21678        .expect("Failed to navigate to definition");
21679    assert_eq!(
21680        navigated,
21681        Navigated::Yes,
21682        "Should have navigated to definition from the GetDefinition response"
21683    );
21684    cx.assert_editor_state(
21685        &r#"fn one() {
21686            let mut a = two();
21687        }
21688
21689        fn «twoˇ»() {}"#
21690            .unindent(),
21691    );
21692
21693    let editors = cx.update_workspace(|workspace, _, cx| {
21694        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21695    });
21696    cx.update_editor(|_, _, test_editor_cx| {
21697        assert_eq!(
21698            editors.len(),
21699            1,
21700            "Initially, only one, test, editor should be open in the workspace"
21701        );
21702        assert_eq!(
21703            test_editor_cx.entity(),
21704            editors.last().expect("Asserted len is 1").clone()
21705        );
21706    });
21707
21708    set_up_lsp_handlers(true, &mut cx);
21709    let navigated = cx
21710        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21711        .await
21712        .expect("Failed to navigate to lookup references");
21713    assert_eq!(
21714        navigated,
21715        Navigated::Yes,
21716        "Should have navigated to references as a fallback after empty GoToDefinition response"
21717    );
21718    // We should not change the selections in the existing file,
21719    // if opening another milti buffer with the references
21720    cx.assert_editor_state(
21721        &r#"fn one() {
21722            let mut a = two();
21723        }
21724
21725        fn «twoˇ»() {}"#
21726            .unindent(),
21727    );
21728    let editors = cx.update_workspace(|workspace, _, cx| {
21729        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21730    });
21731    cx.update_editor(|_, _, test_editor_cx| {
21732        assert_eq!(
21733            editors.len(),
21734            2,
21735            "After falling back to references search, we open a new editor with the results"
21736        );
21737        let references_fallback_text = editors
21738            .into_iter()
21739            .find(|new_editor| *new_editor != test_editor_cx.entity())
21740            .expect("Should have one non-test editor now")
21741            .read(test_editor_cx)
21742            .text(test_editor_cx);
21743        assert_eq!(
21744            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21745            "Should use the range from the references response and not the GoToDefinition one"
21746        );
21747    });
21748}
21749
21750#[gpui::test]
21751async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21752    init_test(cx, |_| {});
21753    cx.update(|cx| {
21754        let mut editor_settings = EditorSettings::get_global(cx).clone();
21755        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21756        EditorSettings::override_global(editor_settings, cx);
21757    });
21758    let mut cx = EditorLspTestContext::new_rust(
21759        lsp::ServerCapabilities {
21760            definition_provider: Some(lsp::OneOf::Left(true)),
21761            references_provider: Some(lsp::OneOf::Left(true)),
21762            ..lsp::ServerCapabilities::default()
21763        },
21764        cx,
21765    )
21766    .await;
21767    let original_state = r#"fn one() {
21768        let mut a = ˇtwo();
21769    }
21770
21771    fn two() {}"#
21772        .unindent();
21773    cx.set_state(&original_state);
21774
21775    let mut go_to_definition = cx
21776        .lsp
21777        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21778            move |_, _| async move { Ok(None) },
21779        );
21780    let _references = cx
21781        .lsp
21782        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21783            panic!("Should not call for references with no go to definition fallback")
21784        });
21785
21786    let navigated = cx
21787        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21788        .await
21789        .expect("Failed to navigate to lookup references");
21790    go_to_definition
21791        .next()
21792        .await
21793        .expect("Should have called the go_to_definition handler");
21794
21795    assert_eq!(
21796        navigated,
21797        Navigated::No,
21798        "Should have navigated to references as a fallback after empty GoToDefinition response"
21799    );
21800    cx.assert_editor_state(&original_state);
21801    let editors = cx.update_workspace(|workspace, _, cx| {
21802        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21803    });
21804    cx.update_editor(|_, _, _| {
21805        assert_eq!(
21806            editors.len(),
21807            1,
21808            "After unsuccessful fallback, no other editor should have been opened"
21809        );
21810    });
21811}
21812
21813#[gpui::test]
21814async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21815    init_test(cx, |_| {});
21816    let mut cx = EditorLspTestContext::new_rust(
21817        lsp::ServerCapabilities {
21818            references_provider: Some(lsp::OneOf::Left(true)),
21819            ..lsp::ServerCapabilities::default()
21820        },
21821        cx,
21822    )
21823    .await;
21824
21825    cx.set_state(
21826        &r#"
21827        fn one() {
21828            let mut a = two();
21829        }
21830
21831        fn ˇtwo() {}"#
21832            .unindent(),
21833    );
21834    cx.lsp
21835        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21836            Ok(Some(vec![
21837                lsp::Location {
21838                    uri: params.text_document_position.text_document.uri.clone(),
21839                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21840                },
21841                lsp::Location {
21842                    uri: params.text_document_position.text_document.uri,
21843                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21844                },
21845            ]))
21846        });
21847    let navigated = cx
21848        .update_editor(|editor, window, cx| {
21849            editor.find_all_references(&FindAllReferences, window, cx)
21850        })
21851        .unwrap()
21852        .await
21853        .expect("Failed to navigate to references");
21854    assert_eq!(
21855        navigated,
21856        Navigated::Yes,
21857        "Should have navigated to references from the FindAllReferences response"
21858    );
21859    cx.assert_editor_state(
21860        &r#"fn one() {
21861            let mut a = two();
21862        }
21863
21864        fn ˇtwo() {}"#
21865            .unindent(),
21866    );
21867
21868    let editors = cx.update_workspace(|workspace, _, cx| {
21869        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21870    });
21871    cx.update_editor(|_, _, _| {
21872        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21873    });
21874
21875    cx.set_state(
21876        &r#"fn one() {
21877            let mut a = ˇtwo();
21878        }
21879
21880        fn two() {}"#
21881            .unindent(),
21882    );
21883    let navigated = cx
21884        .update_editor(|editor, window, cx| {
21885            editor.find_all_references(&FindAllReferences, window, cx)
21886        })
21887        .unwrap()
21888        .await
21889        .expect("Failed to navigate to references");
21890    assert_eq!(
21891        navigated,
21892        Navigated::Yes,
21893        "Should have navigated to references from the FindAllReferences response"
21894    );
21895    cx.assert_editor_state(
21896        &r#"fn one() {
21897            let mut a = ˇtwo();
21898        }
21899
21900        fn two() {}"#
21901            .unindent(),
21902    );
21903    let editors = cx.update_workspace(|workspace, _, cx| {
21904        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21905    });
21906    cx.update_editor(|_, _, _| {
21907        assert_eq!(
21908            editors.len(),
21909            2,
21910            "should have re-used the previous multibuffer"
21911        );
21912    });
21913
21914    cx.set_state(
21915        &r#"fn one() {
21916            let mut a = ˇtwo();
21917        }
21918        fn three() {}
21919        fn two() {}"#
21920            .unindent(),
21921    );
21922    cx.lsp
21923        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21924            Ok(Some(vec![
21925                lsp::Location {
21926                    uri: params.text_document_position.text_document.uri.clone(),
21927                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21928                },
21929                lsp::Location {
21930                    uri: params.text_document_position.text_document.uri,
21931                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21932                },
21933            ]))
21934        });
21935    let navigated = cx
21936        .update_editor(|editor, window, cx| {
21937            editor.find_all_references(&FindAllReferences, window, cx)
21938        })
21939        .unwrap()
21940        .await
21941        .expect("Failed to navigate to references");
21942    assert_eq!(
21943        navigated,
21944        Navigated::Yes,
21945        "Should have navigated to references from the FindAllReferences response"
21946    );
21947    cx.assert_editor_state(
21948        &r#"fn one() {
21949                let mut a = ˇtwo();
21950            }
21951            fn three() {}
21952            fn two() {}"#
21953            .unindent(),
21954    );
21955    let editors = cx.update_workspace(|workspace, _, cx| {
21956        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21957    });
21958    cx.update_editor(|_, _, _| {
21959        assert_eq!(
21960            editors.len(),
21961            3,
21962            "should have used a new multibuffer as offsets changed"
21963        );
21964    });
21965}
21966#[gpui::test]
21967async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21968    init_test(cx, |_| {});
21969
21970    let language = Arc::new(Language::new(
21971        LanguageConfig::default(),
21972        Some(tree_sitter_rust::LANGUAGE.into()),
21973    ));
21974
21975    let text = r#"
21976        #[cfg(test)]
21977        mod tests() {
21978            #[test]
21979            fn runnable_1() {
21980                let a = 1;
21981            }
21982
21983            #[test]
21984            fn runnable_2() {
21985                let a = 1;
21986                let b = 2;
21987            }
21988        }
21989    "#
21990    .unindent();
21991
21992    let fs = FakeFs::new(cx.executor());
21993    fs.insert_file("/file.rs", Default::default()).await;
21994
21995    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21996    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21997    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21998    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21999    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22000
22001    let editor = cx.new_window_entity(|window, cx| {
22002        Editor::new(
22003            EditorMode::full(),
22004            multi_buffer,
22005            Some(project.clone()),
22006            window,
22007            cx,
22008        )
22009    });
22010
22011    editor.update_in(cx, |editor, window, cx| {
22012        let snapshot = editor.buffer().read(cx).snapshot(cx);
22013        editor.tasks.insert(
22014            (buffer.read(cx).remote_id(), 3),
22015            RunnableTasks {
22016                templates: vec![],
22017                offset: snapshot.anchor_before(43),
22018                column: 0,
22019                extra_variables: HashMap::default(),
22020                context_range: BufferOffset(43)..BufferOffset(85),
22021            },
22022        );
22023        editor.tasks.insert(
22024            (buffer.read(cx).remote_id(), 8),
22025            RunnableTasks {
22026                templates: vec![],
22027                offset: snapshot.anchor_before(86),
22028                column: 0,
22029                extra_variables: HashMap::default(),
22030                context_range: BufferOffset(86)..BufferOffset(191),
22031            },
22032        );
22033
22034        // Test finding task when cursor is inside function body
22035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22036            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22037        });
22038        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22039        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22040
22041        // Test finding task when cursor is on function name
22042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22043            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22044        });
22045        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22046        assert_eq!(row, 8, "Should find task when cursor is on function name");
22047    });
22048}
22049
22050#[gpui::test]
22051async fn test_folding_buffers(cx: &mut TestAppContext) {
22052    init_test(cx, |_| {});
22053
22054    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22055    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22056    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22057
22058    let fs = FakeFs::new(cx.executor());
22059    fs.insert_tree(
22060        path!("/a"),
22061        json!({
22062            "first.rs": sample_text_1,
22063            "second.rs": sample_text_2,
22064            "third.rs": sample_text_3,
22065        }),
22066    )
22067    .await;
22068    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22069    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22070    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22071    let worktree = project.update(cx, |project, cx| {
22072        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22073        assert_eq!(worktrees.len(), 1);
22074        worktrees.pop().unwrap()
22075    });
22076    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22077
22078    let buffer_1 = project
22079        .update(cx, |project, cx| {
22080            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22081        })
22082        .await
22083        .unwrap();
22084    let buffer_2 = project
22085        .update(cx, |project, cx| {
22086            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22087        })
22088        .await
22089        .unwrap();
22090    let buffer_3 = project
22091        .update(cx, |project, cx| {
22092            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22093        })
22094        .await
22095        .unwrap();
22096
22097    let multi_buffer = cx.new(|cx| {
22098        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22099        multi_buffer.push_excerpts(
22100            buffer_1.clone(),
22101            [
22102                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22103                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22104                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22105            ],
22106            cx,
22107        );
22108        multi_buffer.push_excerpts(
22109            buffer_2.clone(),
22110            [
22111                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22112                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22113                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22114            ],
22115            cx,
22116        );
22117        multi_buffer.push_excerpts(
22118            buffer_3.clone(),
22119            [
22120                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22121                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22122                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22123            ],
22124            cx,
22125        );
22126        multi_buffer
22127    });
22128    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22129        Editor::new(
22130            EditorMode::full(),
22131            multi_buffer.clone(),
22132            Some(project.clone()),
22133            window,
22134            cx,
22135        )
22136    });
22137
22138    assert_eq!(
22139        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22140        "\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",
22141    );
22142
22143    multi_buffer_editor.update(cx, |editor, cx| {
22144        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22145    });
22146    assert_eq!(
22147        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22148        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22149        "After folding the first buffer, its text should not be displayed"
22150    );
22151
22152    multi_buffer_editor.update(cx, |editor, cx| {
22153        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22154    });
22155    assert_eq!(
22156        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22157        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22158        "After folding the second buffer, its text should not be displayed"
22159    );
22160
22161    multi_buffer_editor.update(cx, |editor, cx| {
22162        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22163    });
22164    assert_eq!(
22165        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22166        "\n\n\n\n\n",
22167        "After folding the third buffer, its text should not be displayed"
22168    );
22169
22170    // Emulate selection inside the fold logic, that should work
22171    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22172        editor
22173            .snapshot(window, cx)
22174            .next_line_boundary(Point::new(0, 4));
22175    });
22176
22177    multi_buffer_editor.update(cx, |editor, cx| {
22178        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22179    });
22180    assert_eq!(
22181        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22182        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22183        "After unfolding the second buffer, its text should be displayed"
22184    );
22185
22186    // Typing inside of buffer 1 causes that buffer to be unfolded.
22187    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22188        assert_eq!(
22189            multi_buffer
22190                .read(cx)
22191                .snapshot(cx)
22192                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22193                .collect::<String>(),
22194            "bbbb"
22195        );
22196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22197            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22198        });
22199        editor.handle_input("B", window, cx);
22200    });
22201
22202    assert_eq!(
22203        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22204        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22205        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22206    );
22207
22208    multi_buffer_editor.update(cx, |editor, cx| {
22209        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22210    });
22211    assert_eq!(
22212        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22213        "\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",
22214        "After unfolding the all buffers, all original text should be displayed"
22215    );
22216}
22217
22218#[gpui::test]
22219async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22220    init_test(cx, |_| {});
22221
22222    let sample_text_1 = "1111\n2222\n3333".to_string();
22223    let sample_text_2 = "4444\n5555\n6666".to_string();
22224    let sample_text_3 = "7777\n8888\n9999".to_string();
22225
22226    let fs = FakeFs::new(cx.executor());
22227    fs.insert_tree(
22228        path!("/a"),
22229        json!({
22230            "first.rs": sample_text_1,
22231            "second.rs": sample_text_2,
22232            "third.rs": sample_text_3,
22233        }),
22234    )
22235    .await;
22236    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22237    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22238    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22239    let worktree = project.update(cx, |project, cx| {
22240        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22241        assert_eq!(worktrees.len(), 1);
22242        worktrees.pop().unwrap()
22243    });
22244    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22245
22246    let buffer_1 = project
22247        .update(cx, |project, cx| {
22248            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22249        })
22250        .await
22251        .unwrap();
22252    let buffer_2 = project
22253        .update(cx, |project, cx| {
22254            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22255        })
22256        .await
22257        .unwrap();
22258    let buffer_3 = project
22259        .update(cx, |project, cx| {
22260            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22261        })
22262        .await
22263        .unwrap();
22264
22265    let multi_buffer = cx.new(|cx| {
22266        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22267        multi_buffer.push_excerpts(
22268            buffer_1.clone(),
22269            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22270            cx,
22271        );
22272        multi_buffer.push_excerpts(
22273            buffer_2.clone(),
22274            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22275            cx,
22276        );
22277        multi_buffer.push_excerpts(
22278            buffer_3.clone(),
22279            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22280            cx,
22281        );
22282        multi_buffer
22283    });
22284
22285    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22286        Editor::new(
22287            EditorMode::full(),
22288            multi_buffer,
22289            Some(project.clone()),
22290            window,
22291            cx,
22292        )
22293    });
22294
22295    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22296    assert_eq!(
22297        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22298        full_text,
22299    );
22300
22301    multi_buffer_editor.update(cx, |editor, cx| {
22302        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22303    });
22304    assert_eq!(
22305        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22306        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22307        "After folding the first buffer, its text should not be displayed"
22308    );
22309
22310    multi_buffer_editor.update(cx, |editor, cx| {
22311        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22312    });
22313
22314    assert_eq!(
22315        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22316        "\n\n\n\n\n\n7777\n8888\n9999",
22317        "After folding the second buffer, its text should not be displayed"
22318    );
22319
22320    multi_buffer_editor.update(cx, |editor, cx| {
22321        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22322    });
22323    assert_eq!(
22324        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22325        "\n\n\n\n\n",
22326        "After folding the third buffer, its text should not be displayed"
22327    );
22328
22329    multi_buffer_editor.update(cx, |editor, cx| {
22330        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22331    });
22332    assert_eq!(
22333        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22334        "\n\n\n\n4444\n5555\n6666\n\n",
22335        "After unfolding the second buffer, its text should be displayed"
22336    );
22337
22338    multi_buffer_editor.update(cx, |editor, cx| {
22339        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22340    });
22341    assert_eq!(
22342        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22343        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22344        "After unfolding the first buffer, its text should be displayed"
22345    );
22346
22347    multi_buffer_editor.update(cx, |editor, cx| {
22348        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22349    });
22350    assert_eq!(
22351        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22352        full_text,
22353        "After unfolding all buffers, all original text should be displayed"
22354    );
22355}
22356
22357#[gpui::test]
22358async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22359    init_test(cx, |_| {});
22360
22361    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22362
22363    let fs = FakeFs::new(cx.executor());
22364    fs.insert_tree(
22365        path!("/a"),
22366        json!({
22367            "main.rs": sample_text,
22368        }),
22369    )
22370    .await;
22371    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22372    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22373    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22374    let worktree = project.update(cx, |project, cx| {
22375        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22376        assert_eq!(worktrees.len(), 1);
22377        worktrees.pop().unwrap()
22378    });
22379    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22380
22381    let buffer_1 = project
22382        .update(cx, |project, cx| {
22383            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22384        })
22385        .await
22386        .unwrap();
22387
22388    let multi_buffer = cx.new(|cx| {
22389        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22390        multi_buffer.push_excerpts(
22391            buffer_1.clone(),
22392            [ExcerptRange::new(
22393                Point::new(0, 0)
22394                    ..Point::new(
22395                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22396                        0,
22397                    ),
22398            )],
22399            cx,
22400        );
22401        multi_buffer
22402    });
22403    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22404        Editor::new(
22405            EditorMode::full(),
22406            multi_buffer,
22407            Some(project.clone()),
22408            window,
22409            cx,
22410        )
22411    });
22412
22413    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22414    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22415        enum TestHighlight {}
22416        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22417        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22418        editor.highlight_text::<TestHighlight>(
22419            vec![highlight_range.clone()],
22420            HighlightStyle::color(Hsla::green()),
22421            cx,
22422        );
22423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22424            s.select_ranges(Some(highlight_range))
22425        });
22426    });
22427
22428    let full_text = format!("\n\n{sample_text}");
22429    assert_eq!(
22430        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22431        full_text,
22432    );
22433}
22434
22435#[gpui::test]
22436async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22437    init_test(cx, |_| {});
22438    cx.update(|cx| {
22439        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22440            "keymaps/default-linux.json",
22441            cx,
22442        )
22443        .unwrap();
22444        cx.bind_keys(default_key_bindings);
22445    });
22446
22447    let (editor, cx) = cx.add_window_view(|window, cx| {
22448        let multi_buffer = MultiBuffer::build_multi(
22449            [
22450                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22451                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22452                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22453                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22454            ],
22455            cx,
22456        );
22457        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22458
22459        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22460        // fold all but the second buffer, so that we test navigating between two
22461        // adjacent folded buffers, as well as folded buffers at the start and
22462        // end the multibuffer
22463        editor.fold_buffer(buffer_ids[0], cx);
22464        editor.fold_buffer(buffer_ids[2], cx);
22465        editor.fold_buffer(buffer_ids[3], cx);
22466
22467        editor
22468    });
22469    cx.simulate_resize(size(px(1000.), px(1000.)));
22470
22471    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22472    cx.assert_excerpts_with_selections(indoc! {"
22473        [EXCERPT]
22474        ˇ[FOLDED]
22475        [EXCERPT]
22476        a1
22477        b1
22478        [EXCERPT]
22479        [FOLDED]
22480        [EXCERPT]
22481        [FOLDED]
22482        "
22483    });
22484    cx.simulate_keystroke("down");
22485    cx.assert_excerpts_with_selections(indoc! {"
22486        [EXCERPT]
22487        [FOLDED]
22488        [EXCERPT]
22489        ˇa1
22490        b1
22491        [EXCERPT]
22492        [FOLDED]
22493        [EXCERPT]
22494        [FOLDED]
22495        "
22496    });
22497    cx.simulate_keystroke("down");
22498    cx.assert_excerpts_with_selections(indoc! {"
22499        [EXCERPT]
22500        [FOLDED]
22501        [EXCERPT]
22502        a1
22503        ˇb1
22504        [EXCERPT]
22505        [FOLDED]
22506        [EXCERPT]
22507        [FOLDED]
22508        "
22509    });
22510    cx.simulate_keystroke("down");
22511    cx.assert_excerpts_with_selections(indoc! {"
22512        [EXCERPT]
22513        [FOLDED]
22514        [EXCERPT]
22515        a1
22516        b1
22517        ˇ[EXCERPT]
22518        [FOLDED]
22519        [EXCERPT]
22520        [FOLDED]
22521        "
22522    });
22523    cx.simulate_keystroke("down");
22524    cx.assert_excerpts_with_selections(indoc! {"
22525        [EXCERPT]
22526        [FOLDED]
22527        [EXCERPT]
22528        a1
22529        b1
22530        [EXCERPT]
22531        ˇ[FOLDED]
22532        [EXCERPT]
22533        [FOLDED]
22534        "
22535    });
22536    for _ in 0..5 {
22537        cx.simulate_keystroke("down");
22538        cx.assert_excerpts_with_selections(indoc! {"
22539            [EXCERPT]
22540            [FOLDED]
22541            [EXCERPT]
22542            a1
22543            b1
22544            [EXCERPT]
22545            [FOLDED]
22546            [EXCERPT]
22547            ˇ[FOLDED]
22548            "
22549        });
22550    }
22551
22552    cx.simulate_keystroke("up");
22553    cx.assert_excerpts_with_selections(indoc! {"
22554        [EXCERPT]
22555        [FOLDED]
22556        [EXCERPT]
22557        a1
22558        b1
22559        [EXCERPT]
22560        ˇ[FOLDED]
22561        [EXCERPT]
22562        [FOLDED]
22563        "
22564    });
22565    cx.simulate_keystroke("up");
22566    cx.assert_excerpts_with_selections(indoc! {"
22567        [EXCERPT]
22568        [FOLDED]
22569        [EXCERPT]
22570        a1
22571        b1
22572        ˇ[EXCERPT]
22573        [FOLDED]
22574        [EXCERPT]
22575        [FOLDED]
22576        "
22577    });
22578    cx.simulate_keystroke("up");
22579    cx.assert_excerpts_with_selections(indoc! {"
22580        [EXCERPT]
22581        [FOLDED]
22582        [EXCERPT]
22583        a1
22584        ˇb1
22585        [EXCERPT]
22586        [FOLDED]
22587        [EXCERPT]
22588        [FOLDED]
22589        "
22590    });
22591    cx.simulate_keystroke("up");
22592    cx.assert_excerpts_with_selections(indoc! {"
22593        [EXCERPT]
22594        [FOLDED]
22595        [EXCERPT]
22596        ˇa1
22597        b1
22598        [EXCERPT]
22599        [FOLDED]
22600        [EXCERPT]
22601        [FOLDED]
22602        "
22603    });
22604    for _ in 0..5 {
22605        cx.simulate_keystroke("up");
22606        cx.assert_excerpts_with_selections(indoc! {"
22607            [EXCERPT]
22608            ˇ[FOLDED]
22609            [EXCERPT]
22610            a1
22611            b1
22612            [EXCERPT]
22613            [FOLDED]
22614            [EXCERPT]
22615            [FOLDED]
22616            "
22617        });
22618    }
22619}
22620
22621#[gpui::test]
22622async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22623    init_test(cx, |_| {});
22624
22625    // Simple insertion
22626    assert_highlighted_edits(
22627        "Hello, world!",
22628        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22629        true,
22630        cx,
22631        |highlighted_edits, cx| {
22632            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22633            assert_eq!(highlighted_edits.highlights.len(), 1);
22634            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22635            assert_eq!(
22636                highlighted_edits.highlights[0].1.background_color,
22637                Some(cx.theme().status().created_background)
22638            );
22639        },
22640    )
22641    .await;
22642
22643    // Replacement
22644    assert_highlighted_edits(
22645        "This is a test.",
22646        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22647        false,
22648        cx,
22649        |highlighted_edits, cx| {
22650            assert_eq!(highlighted_edits.text, "That is a test.");
22651            assert_eq!(highlighted_edits.highlights.len(), 1);
22652            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22653            assert_eq!(
22654                highlighted_edits.highlights[0].1.background_color,
22655                Some(cx.theme().status().created_background)
22656            );
22657        },
22658    )
22659    .await;
22660
22661    // Multiple edits
22662    assert_highlighted_edits(
22663        "Hello, world!",
22664        vec![
22665            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22666            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22667        ],
22668        false,
22669        cx,
22670        |highlighted_edits, cx| {
22671            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22672            assert_eq!(highlighted_edits.highlights.len(), 2);
22673            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22674            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22675            assert_eq!(
22676                highlighted_edits.highlights[0].1.background_color,
22677                Some(cx.theme().status().created_background)
22678            );
22679            assert_eq!(
22680                highlighted_edits.highlights[1].1.background_color,
22681                Some(cx.theme().status().created_background)
22682            );
22683        },
22684    )
22685    .await;
22686
22687    // Multiple lines with edits
22688    assert_highlighted_edits(
22689        "First line\nSecond line\nThird line\nFourth line",
22690        vec![
22691            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22692            (
22693                Point::new(2, 0)..Point::new(2, 10),
22694                "New third line".to_string(),
22695            ),
22696            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22697        ],
22698        false,
22699        cx,
22700        |highlighted_edits, cx| {
22701            assert_eq!(
22702                highlighted_edits.text,
22703                "Second modified\nNew third line\nFourth updated line"
22704            );
22705            assert_eq!(highlighted_edits.highlights.len(), 3);
22706            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22707            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22708            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22709            for highlight in &highlighted_edits.highlights {
22710                assert_eq!(
22711                    highlight.1.background_color,
22712                    Some(cx.theme().status().created_background)
22713                );
22714            }
22715        },
22716    )
22717    .await;
22718}
22719
22720#[gpui::test]
22721async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22722    init_test(cx, |_| {});
22723
22724    // Deletion
22725    assert_highlighted_edits(
22726        "Hello, world!",
22727        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22728        true,
22729        cx,
22730        |highlighted_edits, cx| {
22731            assert_eq!(highlighted_edits.text, "Hello, world!");
22732            assert_eq!(highlighted_edits.highlights.len(), 1);
22733            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22734            assert_eq!(
22735                highlighted_edits.highlights[0].1.background_color,
22736                Some(cx.theme().status().deleted_background)
22737            );
22738        },
22739    )
22740    .await;
22741
22742    // Insertion
22743    assert_highlighted_edits(
22744        "Hello, world!",
22745        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22746        true,
22747        cx,
22748        |highlighted_edits, cx| {
22749            assert_eq!(highlighted_edits.highlights.len(), 1);
22750            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22751            assert_eq!(
22752                highlighted_edits.highlights[0].1.background_color,
22753                Some(cx.theme().status().created_background)
22754            );
22755        },
22756    )
22757    .await;
22758}
22759
22760async fn assert_highlighted_edits(
22761    text: &str,
22762    edits: Vec<(Range<Point>, String)>,
22763    include_deletions: bool,
22764    cx: &mut TestAppContext,
22765    assertion_fn: impl Fn(HighlightedText, &App),
22766) {
22767    let window = cx.add_window(|window, cx| {
22768        let buffer = MultiBuffer::build_simple(text, cx);
22769        Editor::new(EditorMode::full(), buffer, None, window, cx)
22770    });
22771    let cx = &mut VisualTestContext::from_window(*window, cx);
22772
22773    let (buffer, snapshot) = window
22774        .update(cx, |editor, _window, cx| {
22775            (
22776                editor.buffer().clone(),
22777                editor.buffer().read(cx).snapshot(cx),
22778            )
22779        })
22780        .unwrap();
22781
22782    let edits = edits
22783        .into_iter()
22784        .map(|(range, edit)| {
22785            (
22786                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22787                edit,
22788            )
22789        })
22790        .collect::<Vec<_>>();
22791
22792    let text_anchor_edits = edits
22793        .clone()
22794        .into_iter()
22795        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22796        .collect::<Vec<_>>();
22797
22798    let edit_preview = window
22799        .update(cx, |_, _window, cx| {
22800            buffer
22801                .read(cx)
22802                .as_singleton()
22803                .unwrap()
22804                .read(cx)
22805                .preview_edits(text_anchor_edits.into(), cx)
22806        })
22807        .unwrap()
22808        .await;
22809
22810    cx.update(|_window, cx| {
22811        let highlighted_edits = edit_prediction_edit_text(
22812            snapshot.as_singleton().unwrap().2,
22813            &edits,
22814            &edit_preview,
22815            include_deletions,
22816            cx,
22817        );
22818        assertion_fn(highlighted_edits, cx)
22819    });
22820}
22821
22822#[track_caller]
22823fn assert_breakpoint(
22824    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22825    path: &Arc<Path>,
22826    expected: Vec<(u32, Breakpoint)>,
22827) {
22828    if expected.is_empty() {
22829        assert!(!breakpoints.contains_key(path), "{}", path.display());
22830    } else {
22831        let mut breakpoint = breakpoints
22832            .get(path)
22833            .unwrap()
22834            .iter()
22835            .map(|breakpoint| {
22836                (
22837                    breakpoint.row,
22838                    Breakpoint {
22839                        message: breakpoint.message.clone(),
22840                        state: breakpoint.state,
22841                        condition: breakpoint.condition.clone(),
22842                        hit_condition: breakpoint.hit_condition.clone(),
22843                    },
22844                )
22845            })
22846            .collect::<Vec<_>>();
22847
22848        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22849
22850        assert_eq!(expected, breakpoint);
22851    }
22852}
22853
22854fn add_log_breakpoint_at_cursor(
22855    editor: &mut Editor,
22856    log_message: &str,
22857    window: &mut Window,
22858    cx: &mut Context<Editor>,
22859) {
22860    let (anchor, bp) = editor
22861        .breakpoints_at_cursors(window, cx)
22862        .first()
22863        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22864        .unwrap_or_else(|| {
22865            let snapshot = editor.snapshot(window, cx);
22866            let cursor_position: Point =
22867                editor.selections.newest(&snapshot.display_snapshot).head();
22868
22869            let breakpoint_position = snapshot
22870                .buffer_snapshot()
22871                .anchor_before(Point::new(cursor_position.row, 0));
22872
22873            (breakpoint_position, Breakpoint::new_log(log_message))
22874        });
22875
22876    editor.edit_breakpoint_at_anchor(
22877        anchor,
22878        bp,
22879        BreakpointEditAction::EditLogMessage(log_message.into()),
22880        cx,
22881    );
22882}
22883
22884#[gpui::test]
22885async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22886    init_test(cx, |_| {});
22887
22888    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22889    let fs = FakeFs::new(cx.executor());
22890    fs.insert_tree(
22891        path!("/a"),
22892        json!({
22893            "main.rs": sample_text,
22894        }),
22895    )
22896    .await;
22897    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22899    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22900
22901    let fs = FakeFs::new(cx.executor());
22902    fs.insert_tree(
22903        path!("/a"),
22904        json!({
22905            "main.rs": sample_text,
22906        }),
22907    )
22908    .await;
22909    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22910    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22911    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22912    let worktree_id = workspace
22913        .update(cx, |workspace, _window, cx| {
22914            workspace.project().update(cx, |project, cx| {
22915                project.worktrees(cx).next().unwrap().read(cx).id()
22916            })
22917        })
22918        .unwrap();
22919
22920    let buffer = project
22921        .update(cx, |project, cx| {
22922            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22923        })
22924        .await
22925        .unwrap();
22926
22927    let (editor, cx) = cx.add_window_view(|window, cx| {
22928        Editor::new(
22929            EditorMode::full(),
22930            MultiBuffer::build_from_buffer(buffer, cx),
22931            Some(project.clone()),
22932            window,
22933            cx,
22934        )
22935    });
22936
22937    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22938    let abs_path = project.read_with(cx, |project, cx| {
22939        project
22940            .absolute_path(&project_path, cx)
22941            .map(Arc::from)
22942            .unwrap()
22943    });
22944
22945    // assert we can add breakpoint on the first line
22946    editor.update_in(cx, |editor, window, cx| {
22947        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22948        editor.move_to_end(&MoveToEnd, window, cx);
22949        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22950    });
22951
22952    let breakpoints = editor.update(cx, |editor, cx| {
22953        editor
22954            .breakpoint_store()
22955            .as_ref()
22956            .unwrap()
22957            .read(cx)
22958            .all_source_breakpoints(cx)
22959    });
22960
22961    assert_eq!(1, breakpoints.len());
22962    assert_breakpoint(
22963        &breakpoints,
22964        &abs_path,
22965        vec![
22966            (0, Breakpoint::new_standard()),
22967            (3, Breakpoint::new_standard()),
22968        ],
22969    );
22970
22971    editor.update_in(cx, |editor, window, cx| {
22972        editor.move_to_beginning(&MoveToBeginning, window, cx);
22973        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22974    });
22975
22976    let breakpoints = editor.update(cx, |editor, cx| {
22977        editor
22978            .breakpoint_store()
22979            .as_ref()
22980            .unwrap()
22981            .read(cx)
22982            .all_source_breakpoints(cx)
22983    });
22984
22985    assert_eq!(1, breakpoints.len());
22986    assert_breakpoint(
22987        &breakpoints,
22988        &abs_path,
22989        vec![(3, Breakpoint::new_standard())],
22990    );
22991
22992    editor.update_in(cx, |editor, window, cx| {
22993        editor.move_to_end(&MoveToEnd, window, cx);
22994        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22995    });
22996
22997    let breakpoints = editor.update(cx, |editor, cx| {
22998        editor
22999            .breakpoint_store()
23000            .as_ref()
23001            .unwrap()
23002            .read(cx)
23003            .all_source_breakpoints(cx)
23004    });
23005
23006    assert_eq!(0, breakpoints.len());
23007    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23008}
23009
23010#[gpui::test]
23011async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23012    init_test(cx, |_| {});
23013
23014    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23015
23016    let fs = FakeFs::new(cx.executor());
23017    fs.insert_tree(
23018        path!("/a"),
23019        json!({
23020            "main.rs": sample_text,
23021        }),
23022    )
23023    .await;
23024    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23025    let (workspace, cx) =
23026        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23027
23028    let worktree_id = workspace.update(cx, |workspace, cx| {
23029        workspace.project().update(cx, |project, cx| {
23030            project.worktrees(cx).next().unwrap().read(cx).id()
23031        })
23032    });
23033
23034    let buffer = project
23035        .update(cx, |project, cx| {
23036            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23037        })
23038        .await
23039        .unwrap();
23040
23041    let (editor, cx) = cx.add_window_view(|window, cx| {
23042        Editor::new(
23043            EditorMode::full(),
23044            MultiBuffer::build_from_buffer(buffer, cx),
23045            Some(project.clone()),
23046            window,
23047            cx,
23048        )
23049    });
23050
23051    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23052    let abs_path = project.read_with(cx, |project, cx| {
23053        project
23054            .absolute_path(&project_path, cx)
23055            .map(Arc::from)
23056            .unwrap()
23057    });
23058
23059    editor.update_in(cx, |editor, window, cx| {
23060        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23061    });
23062
23063    let breakpoints = editor.update(cx, |editor, cx| {
23064        editor
23065            .breakpoint_store()
23066            .as_ref()
23067            .unwrap()
23068            .read(cx)
23069            .all_source_breakpoints(cx)
23070    });
23071
23072    assert_breakpoint(
23073        &breakpoints,
23074        &abs_path,
23075        vec![(0, Breakpoint::new_log("hello world"))],
23076    );
23077
23078    // Removing a log message from a log breakpoint should remove it
23079    editor.update_in(cx, |editor, window, cx| {
23080        add_log_breakpoint_at_cursor(editor, "", window, cx);
23081    });
23082
23083    let breakpoints = editor.update(cx, |editor, cx| {
23084        editor
23085            .breakpoint_store()
23086            .as_ref()
23087            .unwrap()
23088            .read(cx)
23089            .all_source_breakpoints(cx)
23090    });
23091
23092    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23093
23094    editor.update_in(cx, |editor, window, cx| {
23095        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23096        editor.move_to_end(&MoveToEnd, window, cx);
23097        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23098        // Not adding a log message to a standard breakpoint shouldn't remove it
23099        add_log_breakpoint_at_cursor(editor, "", window, cx);
23100    });
23101
23102    let breakpoints = editor.update(cx, |editor, cx| {
23103        editor
23104            .breakpoint_store()
23105            .as_ref()
23106            .unwrap()
23107            .read(cx)
23108            .all_source_breakpoints(cx)
23109    });
23110
23111    assert_breakpoint(
23112        &breakpoints,
23113        &abs_path,
23114        vec![
23115            (0, Breakpoint::new_standard()),
23116            (3, Breakpoint::new_standard()),
23117        ],
23118    );
23119
23120    editor.update_in(cx, |editor, window, cx| {
23121        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23122    });
23123
23124    let breakpoints = editor.update(cx, |editor, cx| {
23125        editor
23126            .breakpoint_store()
23127            .as_ref()
23128            .unwrap()
23129            .read(cx)
23130            .all_source_breakpoints(cx)
23131    });
23132
23133    assert_breakpoint(
23134        &breakpoints,
23135        &abs_path,
23136        vec![
23137            (0, Breakpoint::new_standard()),
23138            (3, Breakpoint::new_log("hello world")),
23139        ],
23140    );
23141
23142    editor.update_in(cx, |editor, window, cx| {
23143        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23144    });
23145
23146    let breakpoints = editor.update(cx, |editor, cx| {
23147        editor
23148            .breakpoint_store()
23149            .as_ref()
23150            .unwrap()
23151            .read(cx)
23152            .all_source_breakpoints(cx)
23153    });
23154
23155    assert_breakpoint(
23156        &breakpoints,
23157        &abs_path,
23158        vec![
23159            (0, Breakpoint::new_standard()),
23160            (3, Breakpoint::new_log("hello Earth!!")),
23161        ],
23162    );
23163}
23164
23165/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23166/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23167/// or when breakpoints were placed out of order. This tests for a regression too
23168#[gpui::test]
23169async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23170    init_test(cx, |_| {});
23171
23172    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23173    let fs = FakeFs::new(cx.executor());
23174    fs.insert_tree(
23175        path!("/a"),
23176        json!({
23177            "main.rs": sample_text,
23178        }),
23179    )
23180    .await;
23181    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23182    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23183    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23184
23185    let fs = FakeFs::new(cx.executor());
23186    fs.insert_tree(
23187        path!("/a"),
23188        json!({
23189            "main.rs": sample_text,
23190        }),
23191    )
23192    .await;
23193    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23194    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23195    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23196    let worktree_id = workspace
23197        .update(cx, |workspace, _window, cx| {
23198            workspace.project().update(cx, |project, cx| {
23199                project.worktrees(cx).next().unwrap().read(cx).id()
23200            })
23201        })
23202        .unwrap();
23203
23204    let buffer = project
23205        .update(cx, |project, cx| {
23206            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23207        })
23208        .await
23209        .unwrap();
23210
23211    let (editor, cx) = cx.add_window_view(|window, cx| {
23212        Editor::new(
23213            EditorMode::full(),
23214            MultiBuffer::build_from_buffer(buffer, cx),
23215            Some(project.clone()),
23216            window,
23217            cx,
23218        )
23219    });
23220
23221    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23222    let abs_path = project.read_with(cx, |project, cx| {
23223        project
23224            .absolute_path(&project_path, cx)
23225            .map(Arc::from)
23226            .unwrap()
23227    });
23228
23229    // assert we can add breakpoint on the first line
23230    editor.update_in(cx, |editor, window, cx| {
23231        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23232        editor.move_to_end(&MoveToEnd, window, cx);
23233        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23234        editor.move_up(&MoveUp, window, cx);
23235        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23236    });
23237
23238    let breakpoints = editor.update(cx, |editor, cx| {
23239        editor
23240            .breakpoint_store()
23241            .as_ref()
23242            .unwrap()
23243            .read(cx)
23244            .all_source_breakpoints(cx)
23245    });
23246
23247    assert_eq!(1, breakpoints.len());
23248    assert_breakpoint(
23249        &breakpoints,
23250        &abs_path,
23251        vec![
23252            (0, Breakpoint::new_standard()),
23253            (2, Breakpoint::new_standard()),
23254            (3, Breakpoint::new_standard()),
23255        ],
23256    );
23257
23258    editor.update_in(cx, |editor, window, cx| {
23259        editor.move_to_beginning(&MoveToBeginning, window, cx);
23260        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23261        editor.move_to_end(&MoveToEnd, window, cx);
23262        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23263        // Disabling a breakpoint that doesn't exist should do nothing
23264        editor.move_up(&MoveUp, window, cx);
23265        editor.move_up(&MoveUp, window, cx);
23266        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23267    });
23268
23269    let breakpoints = editor.update(cx, |editor, cx| {
23270        editor
23271            .breakpoint_store()
23272            .as_ref()
23273            .unwrap()
23274            .read(cx)
23275            .all_source_breakpoints(cx)
23276    });
23277
23278    let disable_breakpoint = {
23279        let mut bp = Breakpoint::new_standard();
23280        bp.state = BreakpointState::Disabled;
23281        bp
23282    };
23283
23284    assert_eq!(1, breakpoints.len());
23285    assert_breakpoint(
23286        &breakpoints,
23287        &abs_path,
23288        vec![
23289            (0, disable_breakpoint.clone()),
23290            (2, Breakpoint::new_standard()),
23291            (3, disable_breakpoint.clone()),
23292        ],
23293    );
23294
23295    editor.update_in(cx, |editor, window, cx| {
23296        editor.move_to_beginning(&MoveToBeginning, window, cx);
23297        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23298        editor.move_to_end(&MoveToEnd, window, cx);
23299        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23300        editor.move_up(&MoveUp, window, cx);
23301        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23302    });
23303
23304    let breakpoints = editor.update(cx, |editor, cx| {
23305        editor
23306            .breakpoint_store()
23307            .as_ref()
23308            .unwrap()
23309            .read(cx)
23310            .all_source_breakpoints(cx)
23311    });
23312
23313    assert_eq!(1, breakpoints.len());
23314    assert_breakpoint(
23315        &breakpoints,
23316        &abs_path,
23317        vec![
23318            (0, Breakpoint::new_standard()),
23319            (2, disable_breakpoint),
23320            (3, Breakpoint::new_standard()),
23321        ],
23322    );
23323}
23324
23325#[gpui::test]
23326async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23327    init_test(cx, |_| {});
23328    let capabilities = lsp::ServerCapabilities {
23329        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23330            prepare_provider: Some(true),
23331            work_done_progress_options: Default::default(),
23332        })),
23333        ..Default::default()
23334    };
23335    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23336
23337    cx.set_state(indoc! {"
23338        struct Fˇoo {}
23339    "});
23340
23341    cx.update_editor(|editor, _, cx| {
23342        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23343        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23344        editor.highlight_background::<DocumentHighlightRead>(
23345            &[highlight_range],
23346            |theme| theme.colors().editor_document_highlight_read_background,
23347            cx,
23348        );
23349    });
23350
23351    let mut prepare_rename_handler = cx
23352        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23353            move |_, _, _| async move {
23354                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23355                    start: lsp::Position {
23356                        line: 0,
23357                        character: 7,
23358                    },
23359                    end: lsp::Position {
23360                        line: 0,
23361                        character: 10,
23362                    },
23363                })))
23364            },
23365        );
23366    let prepare_rename_task = cx
23367        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23368        .expect("Prepare rename was not started");
23369    prepare_rename_handler.next().await.unwrap();
23370    prepare_rename_task.await.expect("Prepare rename failed");
23371
23372    let mut rename_handler =
23373        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23374            let edit = lsp::TextEdit {
23375                range: lsp::Range {
23376                    start: lsp::Position {
23377                        line: 0,
23378                        character: 7,
23379                    },
23380                    end: lsp::Position {
23381                        line: 0,
23382                        character: 10,
23383                    },
23384                },
23385                new_text: "FooRenamed".to_string(),
23386            };
23387            Ok(Some(lsp::WorkspaceEdit::new(
23388                // Specify the same edit twice
23389                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23390            )))
23391        });
23392    let rename_task = cx
23393        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23394        .expect("Confirm rename was not started");
23395    rename_handler.next().await.unwrap();
23396    rename_task.await.expect("Confirm rename failed");
23397    cx.run_until_parked();
23398
23399    // Despite two edits, only one is actually applied as those are identical
23400    cx.assert_editor_state(indoc! {"
23401        struct FooRenamedˇ {}
23402    "});
23403}
23404
23405#[gpui::test]
23406async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23407    init_test(cx, |_| {});
23408    // These capabilities indicate that the server does not support prepare rename.
23409    let capabilities = lsp::ServerCapabilities {
23410        rename_provider: Some(lsp::OneOf::Left(true)),
23411        ..Default::default()
23412    };
23413    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23414
23415    cx.set_state(indoc! {"
23416        struct Fˇoo {}
23417    "});
23418
23419    cx.update_editor(|editor, _window, cx| {
23420        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23421        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23422        editor.highlight_background::<DocumentHighlightRead>(
23423            &[highlight_range],
23424            |theme| theme.colors().editor_document_highlight_read_background,
23425            cx,
23426        );
23427    });
23428
23429    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23430        .expect("Prepare rename was not started")
23431        .await
23432        .expect("Prepare rename failed");
23433
23434    let mut rename_handler =
23435        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23436            let edit = lsp::TextEdit {
23437                range: lsp::Range {
23438                    start: lsp::Position {
23439                        line: 0,
23440                        character: 7,
23441                    },
23442                    end: lsp::Position {
23443                        line: 0,
23444                        character: 10,
23445                    },
23446                },
23447                new_text: "FooRenamed".to_string(),
23448            };
23449            Ok(Some(lsp::WorkspaceEdit::new(
23450                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23451            )))
23452        });
23453    let rename_task = cx
23454        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23455        .expect("Confirm rename was not started");
23456    rename_handler.next().await.unwrap();
23457    rename_task.await.expect("Confirm rename failed");
23458    cx.run_until_parked();
23459
23460    // Correct range is renamed, as `surrounding_word` is used to find it.
23461    cx.assert_editor_state(indoc! {"
23462        struct FooRenamedˇ {}
23463    "});
23464}
23465
23466#[gpui::test]
23467async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23468    init_test(cx, |_| {});
23469    let mut cx = EditorTestContext::new(cx).await;
23470
23471    let language = Arc::new(
23472        Language::new(
23473            LanguageConfig::default(),
23474            Some(tree_sitter_html::LANGUAGE.into()),
23475        )
23476        .with_brackets_query(
23477            r#"
23478            ("<" @open "/>" @close)
23479            ("</" @open ">" @close)
23480            ("<" @open ">" @close)
23481            ("\"" @open "\"" @close)
23482            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23483        "#,
23484        )
23485        .unwrap(),
23486    );
23487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23488
23489    cx.set_state(indoc! {"
23490        <span>ˇ</span>
23491    "});
23492    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23493    cx.assert_editor_state(indoc! {"
23494        <span>
23495        ˇ
23496        </span>
23497    "});
23498
23499    cx.set_state(indoc! {"
23500        <span><span></span>ˇ</span>
23501    "});
23502    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23503    cx.assert_editor_state(indoc! {"
23504        <span><span></span>
23505        ˇ</span>
23506    "});
23507
23508    cx.set_state(indoc! {"
23509        <span>ˇ
23510        </span>
23511    "});
23512    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23513    cx.assert_editor_state(indoc! {"
23514        <span>
23515        ˇ
23516        </span>
23517    "});
23518}
23519
23520#[gpui::test(iterations = 10)]
23521async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23522    init_test(cx, |_| {});
23523
23524    let fs = FakeFs::new(cx.executor());
23525    fs.insert_tree(
23526        path!("/dir"),
23527        json!({
23528            "a.ts": "a",
23529        }),
23530    )
23531    .await;
23532
23533    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23534    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23535    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23536
23537    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23538    language_registry.add(Arc::new(Language::new(
23539        LanguageConfig {
23540            name: "TypeScript".into(),
23541            matcher: LanguageMatcher {
23542                path_suffixes: vec!["ts".to_string()],
23543                ..Default::default()
23544            },
23545            ..Default::default()
23546        },
23547        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23548    )));
23549    let mut fake_language_servers = language_registry.register_fake_lsp(
23550        "TypeScript",
23551        FakeLspAdapter {
23552            capabilities: lsp::ServerCapabilities {
23553                code_lens_provider: Some(lsp::CodeLensOptions {
23554                    resolve_provider: Some(true),
23555                }),
23556                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23557                    commands: vec!["_the/command".to_string()],
23558                    ..lsp::ExecuteCommandOptions::default()
23559                }),
23560                ..lsp::ServerCapabilities::default()
23561            },
23562            ..FakeLspAdapter::default()
23563        },
23564    );
23565
23566    let editor = workspace
23567        .update(cx, |workspace, window, cx| {
23568            workspace.open_abs_path(
23569                PathBuf::from(path!("/dir/a.ts")),
23570                OpenOptions::default(),
23571                window,
23572                cx,
23573            )
23574        })
23575        .unwrap()
23576        .await
23577        .unwrap()
23578        .downcast::<Editor>()
23579        .unwrap();
23580    cx.executor().run_until_parked();
23581
23582    let fake_server = fake_language_servers.next().await.unwrap();
23583
23584    let buffer = editor.update(cx, |editor, cx| {
23585        editor
23586            .buffer()
23587            .read(cx)
23588            .as_singleton()
23589            .expect("have opened a single file by path")
23590    });
23591
23592    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23593    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23594    drop(buffer_snapshot);
23595    let actions = cx
23596        .update_window(*workspace, |_, window, cx| {
23597            project.code_actions(&buffer, anchor..anchor, window, cx)
23598        })
23599        .unwrap();
23600
23601    fake_server
23602        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23603            Ok(Some(vec![
23604                lsp::CodeLens {
23605                    range: lsp::Range::default(),
23606                    command: Some(lsp::Command {
23607                        title: "Code lens command".to_owned(),
23608                        command: "_the/command".to_owned(),
23609                        arguments: None,
23610                    }),
23611                    data: None,
23612                },
23613                lsp::CodeLens {
23614                    range: lsp::Range::default(),
23615                    command: Some(lsp::Command {
23616                        title: "Command not in capabilities".to_owned(),
23617                        command: "not in capabilities".to_owned(),
23618                        arguments: None,
23619                    }),
23620                    data: None,
23621                },
23622                lsp::CodeLens {
23623                    range: lsp::Range {
23624                        start: lsp::Position {
23625                            line: 1,
23626                            character: 1,
23627                        },
23628                        end: lsp::Position {
23629                            line: 1,
23630                            character: 1,
23631                        },
23632                    },
23633                    command: Some(lsp::Command {
23634                        title: "Command not in range".to_owned(),
23635                        command: "_the/command".to_owned(),
23636                        arguments: None,
23637                    }),
23638                    data: None,
23639                },
23640            ]))
23641        })
23642        .next()
23643        .await;
23644
23645    let actions = actions.await.unwrap();
23646    assert_eq!(
23647        actions.len(),
23648        1,
23649        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23650    );
23651    let action = actions[0].clone();
23652    let apply = project.update(cx, |project, cx| {
23653        project.apply_code_action(buffer.clone(), action, true, cx)
23654    });
23655
23656    // Resolving the code action does not populate its edits. In absence of
23657    // edits, we must execute the given command.
23658    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23659        |mut lens, _| async move {
23660            let lens_command = lens.command.as_mut().expect("should have a command");
23661            assert_eq!(lens_command.title, "Code lens command");
23662            lens_command.arguments = Some(vec![json!("the-argument")]);
23663            Ok(lens)
23664        },
23665    );
23666
23667    // While executing the command, the language server sends the editor
23668    // a `workspaceEdit` request.
23669    fake_server
23670        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23671            let fake = fake_server.clone();
23672            move |params, _| {
23673                assert_eq!(params.command, "_the/command");
23674                let fake = fake.clone();
23675                async move {
23676                    fake.server
23677                        .request::<lsp::request::ApplyWorkspaceEdit>(
23678                            lsp::ApplyWorkspaceEditParams {
23679                                label: None,
23680                                edit: lsp::WorkspaceEdit {
23681                                    changes: Some(
23682                                        [(
23683                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23684                                            vec![lsp::TextEdit {
23685                                                range: lsp::Range::new(
23686                                                    lsp::Position::new(0, 0),
23687                                                    lsp::Position::new(0, 0),
23688                                                ),
23689                                                new_text: "X".into(),
23690                                            }],
23691                                        )]
23692                                        .into_iter()
23693                                        .collect(),
23694                                    ),
23695                                    ..lsp::WorkspaceEdit::default()
23696                                },
23697                            },
23698                        )
23699                        .await
23700                        .into_response()
23701                        .unwrap();
23702                    Ok(Some(json!(null)))
23703                }
23704            }
23705        })
23706        .next()
23707        .await;
23708
23709    // Applying the code lens command returns a project transaction containing the edits
23710    // sent by the language server in its `workspaceEdit` request.
23711    let transaction = apply.await.unwrap();
23712    assert!(transaction.0.contains_key(&buffer));
23713    buffer.update(cx, |buffer, cx| {
23714        assert_eq!(buffer.text(), "Xa");
23715        buffer.undo(cx);
23716        assert_eq!(buffer.text(), "a");
23717    });
23718
23719    let actions_after_edits = cx
23720        .update_window(*workspace, |_, window, cx| {
23721            project.code_actions(&buffer, anchor..anchor, window, cx)
23722        })
23723        .unwrap()
23724        .await
23725        .unwrap();
23726    assert_eq!(
23727        actions, actions_after_edits,
23728        "For the same selection, same code lens actions should be returned"
23729    );
23730
23731    let _responses =
23732        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23733            panic!("No more code lens requests are expected");
23734        });
23735    editor.update_in(cx, |editor, window, cx| {
23736        editor.select_all(&SelectAll, window, cx);
23737    });
23738    cx.executor().run_until_parked();
23739    let new_actions = cx
23740        .update_window(*workspace, |_, window, cx| {
23741            project.code_actions(&buffer, anchor..anchor, window, cx)
23742        })
23743        .unwrap()
23744        .await
23745        .unwrap();
23746    assert_eq!(
23747        actions, new_actions,
23748        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23749    );
23750}
23751
23752#[gpui::test]
23753async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23754    init_test(cx, |_| {});
23755
23756    let fs = FakeFs::new(cx.executor());
23757    let main_text = r#"fn main() {
23758println!("1");
23759println!("2");
23760println!("3");
23761println!("4");
23762println!("5");
23763}"#;
23764    let lib_text = "mod foo {}";
23765    fs.insert_tree(
23766        path!("/a"),
23767        json!({
23768            "lib.rs": lib_text,
23769            "main.rs": main_text,
23770        }),
23771    )
23772    .await;
23773
23774    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23775    let (workspace, cx) =
23776        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23777    let worktree_id = workspace.update(cx, |workspace, cx| {
23778        workspace.project().update(cx, |project, cx| {
23779            project.worktrees(cx).next().unwrap().read(cx).id()
23780        })
23781    });
23782
23783    let expected_ranges = vec![
23784        Point::new(0, 0)..Point::new(0, 0),
23785        Point::new(1, 0)..Point::new(1, 1),
23786        Point::new(2, 0)..Point::new(2, 2),
23787        Point::new(3, 0)..Point::new(3, 3),
23788    ];
23789
23790    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23791    let editor_1 = workspace
23792        .update_in(cx, |workspace, window, cx| {
23793            workspace.open_path(
23794                (worktree_id, rel_path("main.rs")),
23795                Some(pane_1.downgrade()),
23796                true,
23797                window,
23798                cx,
23799            )
23800        })
23801        .unwrap()
23802        .await
23803        .downcast::<Editor>()
23804        .unwrap();
23805    pane_1.update(cx, |pane, cx| {
23806        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23807        open_editor.update(cx, |editor, cx| {
23808            assert_eq!(
23809                editor.display_text(cx),
23810                main_text,
23811                "Original main.rs text on initial open",
23812            );
23813            assert_eq!(
23814                editor
23815                    .selections
23816                    .all::<Point>(&editor.display_snapshot(cx))
23817                    .into_iter()
23818                    .map(|s| s.range())
23819                    .collect::<Vec<_>>(),
23820                vec![Point::zero()..Point::zero()],
23821                "Default selections on initial open",
23822            );
23823        })
23824    });
23825    editor_1.update_in(cx, |editor, window, cx| {
23826        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23827            s.select_ranges(expected_ranges.clone());
23828        });
23829    });
23830
23831    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23832        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23833    });
23834    let editor_2 = workspace
23835        .update_in(cx, |workspace, window, cx| {
23836            workspace.open_path(
23837                (worktree_id, rel_path("main.rs")),
23838                Some(pane_2.downgrade()),
23839                true,
23840                window,
23841                cx,
23842            )
23843        })
23844        .unwrap()
23845        .await
23846        .downcast::<Editor>()
23847        .unwrap();
23848    pane_2.update(cx, |pane, cx| {
23849        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23850        open_editor.update(cx, |editor, cx| {
23851            assert_eq!(
23852                editor.display_text(cx),
23853                main_text,
23854                "Original main.rs text on initial open in another panel",
23855            );
23856            assert_eq!(
23857                editor
23858                    .selections
23859                    .all::<Point>(&editor.display_snapshot(cx))
23860                    .into_iter()
23861                    .map(|s| s.range())
23862                    .collect::<Vec<_>>(),
23863                vec![Point::zero()..Point::zero()],
23864                "Default selections on initial open in another panel",
23865            );
23866        })
23867    });
23868
23869    editor_2.update_in(cx, |editor, window, cx| {
23870        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23871    });
23872
23873    let _other_editor_1 = workspace
23874        .update_in(cx, |workspace, window, cx| {
23875            workspace.open_path(
23876                (worktree_id, rel_path("lib.rs")),
23877                Some(pane_1.downgrade()),
23878                true,
23879                window,
23880                cx,
23881            )
23882        })
23883        .unwrap()
23884        .await
23885        .downcast::<Editor>()
23886        .unwrap();
23887    pane_1
23888        .update_in(cx, |pane, window, cx| {
23889            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23890        })
23891        .await
23892        .unwrap();
23893    drop(editor_1);
23894    pane_1.update(cx, |pane, cx| {
23895        pane.active_item()
23896            .unwrap()
23897            .downcast::<Editor>()
23898            .unwrap()
23899            .update(cx, |editor, cx| {
23900                assert_eq!(
23901                    editor.display_text(cx),
23902                    lib_text,
23903                    "Other file should be open and active",
23904                );
23905            });
23906        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23907    });
23908
23909    let _other_editor_2 = workspace
23910        .update_in(cx, |workspace, window, cx| {
23911            workspace.open_path(
23912                (worktree_id, rel_path("lib.rs")),
23913                Some(pane_2.downgrade()),
23914                true,
23915                window,
23916                cx,
23917            )
23918        })
23919        .unwrap()
23920        .await
23921        .downcast::<Editor>()
23922        .unwrap();
23923    pane_2
23924        .update_in(cx, |pane, window, cx| {
23925            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23926        })
23927        .await
23928        .unwrap();
23929    drop(editor_2);
23930    pane_2.update(cx, |pane, cx| {
23931        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23932        open_editor.update(cx, |editor, cx| {
23933            assert_eq!(
23934                editor.display_text(cx),
23935                lib_text,
23936                "Other file should be open and active in another panel too",
23937            );
23938        });
23939        assert_eq!(
23940            pane.items().count(),
23941            1,
23942            "No other editors should be open in another pane",
23943        );
23944    });
23945
23946    let _editor_1_reopened = workspace
23947        .update_in(cx, |workspace, window, cx| {
23948            workspace.open_path(
23949                (worktree_id, rel_path("main.rs")),
23950                Some(pane_1.downgrade()),
23951                true,
23952                window,
23953                cx,
23954            )
23955        })
23956        .unwrap()
23957        .await
23958        .downcast::<Editor>()
23959        .unwrap();
23960    let _editor_2_reopened = workspace
23961        .update_in(cx, |workspace, window, cx| {
23962            workspace.open_path(
23963                (worktree_id, rel_path("main.rs")),
23964                Some(pane_2.downgrade()),
23965                true,
23966                window,
23967                cx,
23968            )
23969        })
23970        .unwrap()
23971        .await
23972        .downcast::<Editor>()
23973        .unwrap();
23974    pane_1.update(cx, |pane, cx| {
23975        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23976        open_editor.update(cx, |editor, cx| {
23977            assert_eq!(
23978                editor.display_text(cx),
23979                main_text,
23980                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23981            );
23982            assert_eq!(
23983                editor
23984                    .selections
23985                    .all::<Point>(&editor.display_snapshot(cx))
23986                    .into_iter()
23987                    .map(|s| s.range())
23988                    .collect::<Vec<_>>(),
23989                expected_ranges,
23990                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23991            );
23992        })
23993    });
23994    pane_2.update(cx, |pane, cx| {
23995        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23996        open_editor.update(cx, |editor, cx| {
23997            assert_eq!(
23998                editor.display_text(cx),
23999                r#"fn main() {
24000⋯rintln!("1");
24001⋯intln!("2");
24002⋯ntln!("3");
24003println!("4");
24004println!("5");
24005}"#,
24006                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24007            );
24008            assert_eq!(
24009                editor
24010                    .selections
24011                    .all::<Point>(&editor.display_snapshot(cx))
24012                    .into_iter()
24013                    .map(|s| s.range())
24014                    .collect::<Vec<_>>(),
24015                vec![Point::zero()..Point::zero()],
24016                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24017            );
24018        })
24019    });
24020}
24021
24022#[gpui::test]
24023async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24024    init_test(cx, |_| {});
24025
24026    let fs = FakeFs::new(cx.executor());
24027    let main_text = r#"fn main() {
24028println!("1");
24029println!("2");
24030println!("3");
24031println!("4");
24032println!("5");
24033}"#;
24034    let lib_text = "mod foo {}";
24035    fs.insert_tree(
24036        path!("/a"),
24037        json!({
24038            "lib.rs": lib_text,
24039            "main.rs": main_text,
24040        }),
24041    )
24042    .await;
24043
24044    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24045    let (workspace, cx) =
24046        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24047    let worktree_id = workspace.update(cx, |workspace, cx| {
24048        workspace.project().update(cx, |project, cx| {
24049            project.worktrees(cx).next().unwrap().read(cx).id()
24050        })
24051    });
24052
24053    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24054    let editor = workspace
24055        .update_in(cx, |workspace, window, cx| {
24056            workspace.open_path(
24057                (worktree_id, rel_path("main.rs")),
24058                Some(pane.downgrade()),
24059                true,
24060                window,
24061                cx,
24062            )
24063        })
24064        .unwrap()
24065        .await
24066        .downcast::<Editor>()
24067        .unwrap();
24068    pane.update(cx, |pane, cx| {
24069        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24070        open_editor.update(cx, |editor, cx| {
24071            assert_eq!(
24072                editor.display_text(cx),
24073                main_text,
24074                "Original main.rs text on initial open",
24075            );
24076        })
24077    });
24078    editor.update_in(cx, |editor, window, cx| {
24079        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24080    });
24081
24082    cx.update_global(|store: &mut SettingsStore, cx| {
24083        store.update_user_settings(cx, |s| {
24084            s.workspace.restore_on_file_reopen = Some(false);
24085        });
24086    });
24087    editor.update_in(cx, |editor, window, cx| {
24088        editor.fold_ranges(
24089            vec![
24090                Point::new(1, 0)..Point::new(1, 1),
24091                Point::new(2, 0)..Point::new(2, 2),
24092                Point::new(3, 0)..Point::new(3, 3),
24093            ],
24094            false,
24095            window,
24096            cx,
24097        );
24098    });
24099    pane.update_in(cx, |pane, window, cx| {
24100        pane.close_all_items(&CloseAllItems::default(), window, cx)
24101    })
24102    .await
24103    .unwrap();
24104    pane.update(cx, |pane, _| {
24105        assert!(pane.active_item().is_none());
24106    });
24107    cx.update_global(|store: &mut SettingsStore, cx| {
24108        store.update_user_settings(cx, |s| {
24109            s.workspace.restore_on_file_reopen = Some(true);
24110        });
24111    });
24112
24113    let _editor_reopened = workspace
24114        .update_in(cx, |workspace, window, cx| {
24115            workspace.open_path(
24116                (worktree_id, rel_path("main.rs")),
24117                Some(pane.downgrade()),
24118                true,
24119                window,
24120                cx,
24121            )
24122        })
24123        .unwrap()
24124        .await
24125        .downcast::<Editor>()
24126        .unwrap();
24127    pane.update(cx, |pane, cx| {
24128        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24129        open_editor.update(cx, |editor, cx| {
24130            assert_eq!(
24131                editor.display_text(cx),
24132                main_text,
24133                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24134            );
24135        })
24136    });
24137}
24138
24139#[gpui::test]
24140async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24141    struct EmptyModalView {
24142        focus_handle: gpui::FocusHandle,
24143    }
24144    impl EventEmitter<DismissEvent> for EmptyModalView {}
24145    impl Render for EmptyModalView {
24146        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24147            div()
24148        }
24149    }
24150    impl Focusable for EmptyModalView {
24151        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24152            self.focus_handle.clone()
24153        }
24154    }
24155    impl workspace::ModalView for EmptyModalView {}
24156    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24157        EmptyModalView {
24158            focus_handle: cx.focus_handle(),
24159        }
24160    }
24161
24162    init_test(cx, |_| {});
24163
24164    let fs = FakeFs::new(cx.executor());
24165    let project = Project::test(fs, [], cx).await;
24166    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24167    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24168    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24169    let editor = cx.new_window_entity(|window, cx| {
24170        Editor::new(
24171            EditorMode::full(),
24172            buffer,
24173            Some(project.clone()),
24174            window,
24175            cx,
24176        )
24177    });
24178    workspace
24179        .update(cx, |workspace, window, cx| {
24180            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24181        })
24182        .unwrap();
24183    editor.update_in(cx, |editor, window, cx| {
24184        editor.open_context_menu(&OpenContextMenu, window, cx);
24185        assert!(editor.mouse_context_menu.is_some());
24186    });
24187    workspace
24188        .update(cx, |workspace, window, cx| {
24189            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24190        })
24191        .unwrap();
24192    cx.read(|cx| {
24193        assert!(editor.read(cx).mouse_context_menu.is_none());
24194    });
24195}
24196
24197fn set_linked_edit_ranges(
24198    opening: (Point, Point),
24199    closing: (Point, Point),
24200    editor: &mut Editor,
24201    cx: &mut Context<Editor>,
24202) {
24203    let Some((buffer, _)) = editor
24204        .buffer
24205        .read(cx)
24206        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24207    else {
24208        panic!("Failed to get buffer for selection position");
24209    };
24210    let buffer = buffer.read(cx);
24211    let buffer_id = buffer.remote_id();
24212    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24213    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24214    let mut linked_ranges = HashMap::default();
24215    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24216    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24217}
24218
24219#[gpui::test]
24220async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24221    init_test(cx, |_| {});
24222
24223    let fs = FakeFs::new(cx.executor());
24224    fs.insert_file(path!("/file.html"), Default::default())
24225        .await;
24226
24227    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24228
24229    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24230    let html_language = Arc::new(Language::new(
24231        LanguageConfig {
24232            name: "HTML".into(),
24233            matcher: LanguageMatcher {
24234                path_suffixes: vec!["html".to_string()],
24235                ..LanguageMatcher::default()
24236            },
24237            brackets: BracketPairConfig {
24238                pairs: vec![BracketPair {
24239                    start: "<".into(),
24240                    end: ">".into(),
24241                    close: true,
24242                    ..Default::default()
24243                }],
24244                ..Default::default()
24245            },
24246            ..Default::default()
24247        },
24248        Some(tree_sitter_html::LANGUAGE.into()),
24249    ));
24250    language_registry.add(html_language);
24251    let mut fake_servers = language_registry.register_fake_lsp(
24252        "HTML",
24253        FakeLspAdapter {
24254            capabilities: lsp::ServerCapabilities {
24255                completion_provider: Some(lsp::CompletionOptions {
24256                    resolve_provider: Some(true),
24257                    ..Default::default()
24258                }),
24259                ..Default::default()
24260            },
24261            ..Default::default()
24262        },
24263    );
24264
24265    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24266    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24267
24268    let worktree_id = workspace
24269        .update(cx, |workspace, _window, cx| {
24270            workspace.project().update(cx, |project, cx| {
24271                project.worktrees(cx).next().unwrap().read(cx).id()
24272            })
24273        })
24274        .unwrap();
24275    project
24276        .update(cx, |project, cx| {
24277            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24278        })
24279        .await
24280        .unwrap();
24281    let editor = workspace
24282        .update(cx, |workspace, window, cx| {
24283            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24284        })
24285        .unwrap()
24286        .await
24287        .unwrap()
24288        .downcast::<Editor>()
24289        .unwrap();
24290
24291    let fake_server = fake_servers.next().await.unwrap();
24292    editor.update_in(cx, |editor, window, cx| {
24293        editor.set_text("<ad></ad>", window, cx);
24294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24295            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24296        });
24297        set_linked_edit_ranges(
24298            (Point::new(0, 1), Point::new(0, 3)),
24299            (Point::new(0, 6), Point::new(0, 8)),
24300            editor,
24301            cx,
24302        );
24303    });
24304    let mut completion_handle =
24305        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24306            Ok(Some(lsp::CompletionResponse::Array(vec![
24307                lsp::CompletionItem {
24308                    label: "head".to_string(),
24309                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24310                        lsp::InsertReplaceEdit {
24311                            new_text: "head".to_string(),
24312                            insert: lsp::Range::new(
24313                                lsp::Position::new(0, 1),
24314                                lsp::Position::new(0, 3),
24315                            ),
24316                            replace: lsp::Range::new(
24317                                lsp::Position::new(0, 1),
24318                                lsp::Position::new(0, 3),
24319                            ),
24320                        },
24321                    )),
24322                    ..Default::default()
24323                },
24324            ])))
24325        });
24326    editor.update_in(cx, |editor, window, cx| {
24327        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24328    });
24329    cx.run_until_parked();
24330    completion_handle.next().await.unwrap();
24331    editor.update(cx, |editor, _| {
24332        assert!(
24333            editor.context_menu_visible(),
24334            "Completion menu should be visible"
24335        );
24336    });
24337    editor.update_in(cx, |editor, window, cx| {
24338        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24339    });
24340    cx.executor().run_until_parked();
24341    editor.update(cx, |editor, cx| {
24342        assert_eq!(editor.text(cx), "<head></head>");
24343    });
24344}
24345
24346#[gpui::test]
24347async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24348    init_test(cx, |_| {});
24349
24350    let mut cx = EditorTestContext::new(cx).await;
24351    let language = Arc::new(Language::new(
24352        LanguageConfig {
24353            name: "TSX".into(),
24354            matcher: LanguageMatcher {
24355                path_suffixes: vec!["tsx".to_string()],
24356                ..LanguageMatcher::default()
24357            },
24358            brackets: BracketPairConfig {
24359                pairs: vec![BracketPair {
24360                    start: "<".into(),
24361                    end: ">".into(),
24362                    close: true,
24363                    ..Default::default()
24364                }],
24365                ..Default::default()
24366            },
24367            linked_edit_characters: HashSet::from_iter(['.']),
24368            ..Default::default()
24369        },
24370        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24371    ));
24372    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24373
24374    // Test typing > does not extend linked pair
24375    cx.set_state("<divˇ<div></div>");
24376    cx.update_editor(|editor, _, cx| {
24377        set_linked_edit_ranges(
24378            (Point::new(0, 1), Point::new(0, 4)),
24379            (Point::new(0, 11), Point::new(0, 14)),
24380            editor,
24381            cx,
24382        );
24383    });
24384    cx.update_editor(|editor, window, cx| {
24385        editor.handle_input(">", window, cx);
24386    });
24387    cx.assert_editor_state("<div>ˇ<div></div>");
24388
24389    // Test typing . do extend linked pair
24390    cx.set_state("<Animatedˇ></Animated>");
24391    cx.update_editor(|editor, _, cx| {
24392        set_linked_edit_ranges(
24393            (Point::new(0, 1), Point::new(0, 9)),
24394            (Point::new(0, 12), Point::new(0, 20)),
24395            editor,
24396            cx,
24397        );
24398    });
24399    cx.update_editor(|editor, window, cx| {
24400        editor.handle_input(".", window, cx);
24401    });
24402    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24403    cx.update_editor(|editor, _, cx| {
24404        set_linked_edit_ranges(
24405            (Point::new(0, 1), Point::new(0, 10)),
24406            (Point::new(0, 13), Point::new(0, 21)),
24407            editor,
24408            cx,
24409        );
24410    });
24411    cx.update_editor(|editor, window, cx| {
24412        editor.handle_input("V", window, cx);
24413    });
24414    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24415}
24416
24417#[gpui::test]
24418async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24419    init_test(cx, |_| {});
24420
24421    let fs = FakeFs::new(cx.executor());
24422    fs.insert_tree(
24423        path!("/root"),
24424        json!({
24425            "a": {
24426                "main.rs": "fn main() {}",
24427            },
24428            "foo": {
24429                "bar": {
24430                    "external_file.rs": "pub mod external {}",
24431                }
24432            }
24433        }),
24434    )
24435    .await;
24436
24437    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24438    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24439    language_registry.add(rust_lang());
24440    let _fake_servers = language_registry.register_fake_lsp(
24441        "Rust",
24442        FakeLspAdapter {
24443            ..FakeLspAdapter::default()
24444        },
24445    );
24446    let (workspace, cx) =
24447        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24448    let worktree_id = workspace.update(cx, |workspace, cx| {
24449        workspace.project().update(cx, |project, cx| {
24450            project.worktrees(cx).next().unwrap().read(cx).id()
24451        })
24452    });
24453
24454    let assert_language_servers_count =
24455        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24456            project.update(cx, |project, cx| {
24457                let current = project
24458                    .lsp_store()
24459                    .read(cx)
24460                    .as_local()
24461                    .unwrap()
24462                    .language_servers
24463                    .len();
24464                assert_eq!(expected, current, "{context}");
24465            });
24466        };
24467
24468    assert_language_servers_count(
24469        0,
24470        "No servers should be running before any file is open",
24471        cx,
24472    );
24473    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24474    let main_editor = workspace
24475        .update_in(cx, |workspace, window, cx| {
24476            workspace.open_path(
24477                (worktree_id, rel_path("main.rs")),
24478                Some(pane.downgrade()),
24479                true,
24480                window,
24481                cx,
24482            )
24483        })
24484        .unwrap()
24485        .await
24486        .downcast::<Editor>()
24487        .unwrap();
24488    pane.update(cx, |pane, cx| {
24489        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24490        open_editor.update(cx, |editor, cx| {
24491            assert_eq!(
24492                editor.display_text(cx),
24493                "fn main() {}",
24494                "Original main.rs text on initial open",
24495            );
24496        });
24497        assert_eq!(open_editor, main_editor);
24498    });
24499    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24500
24501    let external_editor = workspace
24502        .update_in(cx, |workspace, window, cx| {
24503            workspace.open_abs_path(
24504                PathBuf::from("/root/foo/bar/external_file.rs"),
24505                OpenOptions::default(),
24506                window,
24507                cx,
24508            )
24509        })
24510        .await
24511        .expect("opening external file")
24512        .downcast::<Editor>()
24513        .expect("downcasted external file's open element to editor");
24514    pane.update(cx, |pane, cx| {
24515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24516        open_editor.update(cx, |editor, cx| {
24517            assert_eq!(
24518                editor.display_text(cx),
24519                "pub mod external {}",
24520                "External file is open now",
24521            );
24522        });
24523        assert_eq!(open_editor, external_editor);
24524    });
24525    assert_language_servers_count(
24526        1,
24527        "Second, external, *.rs file should join the existing server",
24528        cx,
24529    );
24530
24531    pane.update_in(cx, |pane, window, cx| {
24532        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24533    })
24534    .await
24535    .unwrap();
24536    pane.update_in(cx, |pane, window, cx| {
24537        pane.navigate_backward(&Default::default(), window, cx);
24538    });
24539    cx.run_until_parked();
24540    pane.update(cx, |pane, cx| {
24541        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24542        open_editor.update(cx, |editor, cx| {
24543            assert_eq!(
24544                editor.display_text(cx),
24545                "pub mod external {}",
24546                "External file is open now",
24547            );
24548        });
24549    });
24550    assert_language_servers_count(
24551        1,
24552        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24553        cx,
24554    );
24555
24556    cx.update(|_, cx| {
24557        workspace::reload(cx);
24558    });
24559    assert_language_servers_count(
24560        1,
24561        "After reloading the worktree with local and external files opened, only one project should be started",
24562        cx,
24563    );
24564}
24565
24566#[gpui::test]
24567async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24568    init_test(cx, |_| {});
24569
24570    let mut cx = EditorTestContext::new(cx).await;
24571    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24572    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24573
24574    // test cursor move to start of each line on tab
24575    // for `if`, `elif`, `else`, `while`, `with` and `for`
24576    cx.set_state(indoc! {"
24577        def main():
24578        ˇ    for item in items:
24579        ˇ        while item.active:
24580        ˇ            if item.value > 10:
24581        ˇ                continue
24582        ˇ            elif item.value < 0:
24583        ˇ                break
24584        ˇ            else:
24585        ˇ                with item.context() as ctx:
24586        ˇ                    yield count
24587        ˇ        else:
24588        ˇ            log('while else')
24589        ˇ    else:
24590        ˇ        log('for else')
24591    "});
24592    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24593    cx.assert_editor_state(indoc! {"
24594        def main():
24595            ˇfor item in items:
24596                ˇwhile item.active:
24597                    ˇif item.value > 10:
24598                        ˇcontinue
24599                    ˇelif item.value < 0:
24600                        ˇbreak
24601                    ˇelse:
24602                        ˇwith item.context() as ctx:
24603                            ˇyield count
24604                ˇelse:
24605                    ˇlog('while else')
24606            ˇelse:
24607                ˇlog('for else')
24608    "});
24609    // test relative indent is preserved when tab
24610    // for `if`, `elif`, `else`, `while`, `with` and `for`
24611    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24612    cx.assert_editor_state(indoc! {"
24613        def main():
24614                ˇfor item in items:
24615                    ˇwhile item.active:
24616                        ˇif item.value > 10:
24617                            ˇcontinue
24618                        ˇelif item.value < 0:
24619                            ˇbreak
24620                        ˇelse:
24621                            ˇwith item.context() as ctx:
24622                                ˇyield count
24623                    ˇelse:
24624                        ˇlog('while else')
24625                ˇelse:
24626                    ˇlog('for else')
24627    "});
24628
24629    // test cursor move to start of each line on tab
24630    // for `try`, `except`, `else`, `finally`, `match` and `def`
24631    cx.set_state(indoc! {"
24632        def main():
24633        ˇ    try:
24634        ˇ        fetch()
24635        ˇ    except ValueError:
24636        ˇ        handle_error()
24637        ˇ    else:
24638        ˇ        match value:
24639        ˇ            case _:
24640        ˇ    finally:
24641        ˇ        def status():
24642        ˇ            return 0
24643    "});
24644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24645    cx.assert_editor_state(indoc! {"
24646        def main():
24647            ˇtry:
24648                ˇfetch()
24649            ˇexcept ValueError:
24650                ˇhandle_error()
24651            ˇelse:
24652                ˇmatch value:
24653                    ˇcase _:
24654            ˇfinally:
24655                ˇdef status():
24656                    ˇreturn 0
24657    "});
24658    // test relative indent is preserved when tab
24659    // for `try`, `except`, `else`, `finally`, `match` and `def`
24660    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24661    cx.assert_editor_state(indoc! {"
24662        def main():
24663                ˇtry:
24664                    ˇfetch()
24665                ˇexcept ValueError:
24666                    ˇhandle_error()
24667                ˇelse:
24668                    ˇmatch value:
24669                        ˇcase _:
24670                ˇfinally:
24671                    ˇdef status():
24672                        ˇreturn 0
24673    "});
24674}
24675
24676#[gpui::test]
24677async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24678    init_test(cx, |_| {});
24679
24680    let mut cx = EditorTestContext::new(cx).await;
24681    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24682    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24683
24684    // test `else` auto outdents when typed inside `if` block
24685    cx.set_state(indoc! {"
24686        def main():
24687            if i == 2:
24688                return
24689                ˇ
24690    "});
24691    cx.update_editor(|editor, window, cx| {
24692        editor.handle_input("else:", window, cx);
24693    });
24694    cx.assert_editor_state(indoc! {"
24695        def main():
24696            if i == 2:
24697                return
24698            else:ˇ
24699    "});
24700
24701    // test `except` auto outdents when typed inside `try` block
24702    cx.set_state(indoc! {"
24703        def main():
24704            try:
24705                i = 2
24706                ˇ
24707    "});
24708    cx.update_editor(|editor, window, cx| {
24709        editor.handle_input("except:", window, cx);
24710    });
24711    cx.assert_editor_state(indoc! {"
24712        def main():
24713            try:
24714                i = 2
24715            except:ˇ
24716    "});
24717
24718    // test `else` auto outdents when typed inside `except` block
24719    cx.set_state(indoc! {"
24720        def main():
24721            try:
24722                i = 2
24723            except:
24724                j = 2
24725                ˇ
24726    "});
24727    cx.update_editor(|editor, window, cx| {
24728        editor.handle_input("else:", window, cx);
24729    });
24730    cx.assert_editor_state(indoc! {"
24731        def main():
24732            try:
24733                i = 2
24734            except:
24735                j = 2
24736            else:ˇ
24737    "});
24738
24739    // test `finally` auto outdents when typed inside `else` block
24740    cx.set_state(indoc! {"
24741        def main():
24742            try:
24743                i = 2
24744            except:
24745                j = 2
24746            else:
24747                k = 2
24748                ˇ
24749    "});
24750    cx.update_editor(|editor, window, cx| {
24751        editor.handle_input("finally:", window, cx);
24752    });
24753    cx.assert_editor_state(indoc! {"
24754        def main():
24755            try:
24756                i = 2
24757            except:
24758                j = 2
24759            else:
24760                k = 2
24761            finally:ˇ
24762    "});
24763
24764    // test `else` does not outdents when typed inside `except` block right after for block
24765    cx.set_state(indoc! {"
24766        def main():
24767            try:
24768                i = 2
24769            except:
24770                for i in range(n):
24771                    pass
24772                ˇ
24773    "});
24774    cx.update_editor(|editor, window, cx| {
24775        editor.handle_input("else:", window, cx);
24776    });
24777    cx.assert_editor_state(indoc! {"
24778        def main():
24779            try:
24780                i = 2
24781            except:
24782                for i in range(n):
24783                    pass
24784                else:ˇ
24785    "});
24786
24787    // test `finally` auto outdents when typed inside `else` block right after for block
24788    cx.set_state(indoc! {"
24789        def main():
24790            try:
24791                i = 2
24792            except:
24793                j = 2
24794            else:
24795                for i in range(n):
24796                    pass
24797                ˇ
24798    "});
24799    cx.update_editor(|editor, window, cx| {
24800        editor.handle_input("finally:", window, cx);
24801    });
24802    cx.assert_editor_state(indoc! {"
24803        def main():
24804            try:
24805                i = 2
24806            except:
24807                j = 2
24808            else:
24809                for i in range(n):
24810                    pass
24811            finally:ˇ
24812    "});
24813
24814    // test `except` outdents to inner "try" block
24815    cx.set_state(indoc! {"
24816        def main():
24817            try:
24818                i = 2
24819                if i == 2:
24820                    try:
24821                        i = 3
24822                        ˇ
24823    "});
24824    cx.update_editor(|editor, window, cx| {
24825        editor.handle_input("except:", window, cx);
24826    });
24827    cx.assert_editor_state(indoc! {"
24828        def main():
24829            try:
24830                i = 2
24831                if i == 2:
24832                    try:
24833                        i = 3
24834                    except:ˇ
24835    "});
24836
24837    // test `except` outdents to outer "try" block
24838    cx.set_state(indoc! {"
24839        def main():
24840            try:
24841                i = 2
24842                if i == 2:
24843                    try:
24844                        i = 3
24845                ˇ
24846    "});
24847    cx.update_editor(|editor, window, cx| {
24848        editor.handle_input("except:", window, cx);
24849    });
24850    cx.assert_editor_state(indoc! {"
24851        def main():
24852            try:
24853                i = 2
24854                if i == 2:
24855                    try:
24856                        i = 3
24857            except:ˇ
24858    "});
24859
24860    // test `else` stays at correct indent when typed after `for` block
24861    cx.set_state(indoc! {"
24862        def main():
24863            for i in range(10):
24864                if i == 3:
24865                    break
24866            ˇ
24867    "});
24868    cx.update_editor(|editor, window, cx| {
24869        editor.handle_input("else:", window, cx);
24870    });
24871    cx.assert_editor_state(indoc! {"
24872        def main():
24873            for i in range(10):
24874                if i == 3:
24875                    break
24876            else:ˇ
24877    "});
24878
24879    // test does not outdent on typing after line with square brackets
24880    cx.set_state(indoc! {"
24881        def f() -> list[str]:
24882            ˇ
24883    "});
24884    cx.update_editor(|editor, window, cx| {
24885        editor.handle_input("a", window, cx);
24886    });
24887    cx.assert_editor_state(indoc! {"
24888        def f() -> list[str]:
2488924890    "});
24891
24892    // test does not outdent on typing : after case keyword
24893    cx.set_state(indoc! {"
24894        match 1:
24895            caseˇ
24896    "});
24897    cx.update_editor(|editor, window, cx| {
24898        editor.handle_input(":", window, cx);
24899    });
24900    cx.assert_editor_state(indoc! {"
24901        match 1:
24902            case:ˇ
24903    "});
24904}
24905
24906#[gpui::test]
24907async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24908    init_test(cx, |_| {});
24909    update_test_language_settings(cx, |settings| {
24910        settings.defaults.extend_comment_on_newline = Some(false);
24911    });
24912    let mut cx = EditorTestContext::new(cx).await;
24913    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24915
24916    // test correct indent after newline on comment
24917    cx.set_state(indoc! {"
24918        # COMMENT:ˇ
24919    "});
24920    cx.update_editor(|editor, window, cx| {
24921        editor.newline(&Newline, window, cx);
24922    });
24923    cx.assert_editor_state(indoc! {"
24924        # COMMENT:
24925        ˇ
24926    "});
24927
24928    // test correct indent after newline in brackets
24929    cx.set_state(indoc! {"
24930        {ˇ}
24931    "});
24932    cx.update_editor(|editor, window, cx| {
24933        editor.newline(&Newline, window, cx);
24934    });
24935    cx.run_until_parked();
24936    cx.assert_editor_state(indoc! {"
24937        {
24938            ˇ
24939        }
24940    "});
24941
24942    cx.set_state(indoc! {"
24943        (ˇ)
24944    "});
24945    cx.update_editor(|editor, window, cx| {
24946        editor.newline(&Newline, window, cx);
24947    });
24948    cx.run_until_parked();
24949    cx.assert_editor_state(indoc! {"
24950        (
24951            ˇ
24952        )
24953    "});
24954
24955    // do not indent after empty lists or dictionaries
24956    cx.set_state(indoc! {"
24957        a = []ˇ
24958    "});
24959    cx.update_editor(|editor, window, cx| {
24960        editor.newline(&Newline, window, cx);
24961    });
24962    cx.run_until_parked();
24963    cx.assert_editor_state(indoc! {"
24964        a = []
24965        ˇ
24966    "});
24967}
24968
24969#[gpui::test]
24970async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24971    init_test(cx, |_| {});
24972
24973    let mut cx = EditorTestContext::new(cx).await;
24974    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24976
24977    // test cursor move to start of each line on tab
24978    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24979    cx.set_state(indoc! {"
24980        function main() {
24981        ˇ    for item in $items; do
24982        ˇ        while [ -n \"$item\" ]; do
24983        ˇ            if [ \"$value\" -gt 10 ]; then
24984        ˇ                continue
24985        ˇ            elif [ \"$value\" -lt 0 ]; then
24986        ˇ                break
24987        ˇ            else
24988        ˇ                echo \"$item\"
24989        ˇ            fi
24990        ˇ        done
24991        ˇ    done
24992        ˇ}
24993    "});
24994    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24995    cx.assert_editor_state(indoc! {"
24996        function main() {
24997            ˇfor item in $items; do
24998                ˇwhile [ -n \"$item\" ]; do
24999                    ˇif [ \"$value\" -gt 10 ]; then
25000                        ˇcontinue
25001                    ˇelif [ \"$value\" -lt 0 ]; then
25002                        ˇbreak
25003                    ˇelse
25004                        ˇecho \"$item\"
25005                    ˇfi
25006                ˇdone
25007            ˇdone
25008        ˇ}
25009    "});
25010    // test relative indent is preserved when tab
25011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25012    cx.assert_editor_state(indoc! {"
25013        function main() {
25014                ˇfor item in $items; do
25015                    ˇwhile [ -n \"$item\" ]; do
25016                        ˇif [ \"$value\" -gt 10 ]; then
25017                            ˇcontinue
25018                        ˇelif [ \"$value\" -lt 0 ]; then
25019                            ˇbreak
25020                        ˇelse
25021                            ˇecho \"$item\"
25022                        ˇfi
25023                    ˇdone
25024                ˇdone
25025            ˇ}
25026    "});
25027
25028    // test cursor move to start of each line on tab
25029    // for `case` statement with patterns
25030    cx.set_state(indoc! {"
25031        function handle() {
25032        ˇ    case \"$1\" in
25033        ˇ        start)
25034        ˇ            echo \"a\"
25035        ˇ            ;;
25036        ˇ        stop)
25037        ˇ            echo \"b\"
25038        ˇ            ;;
25039        ˇ        *)
25040        ˇ            echo \"c\"
25041        ˇ            ;;
25042        ˇ    esac
25043        ˇ}
25044    "});
25045    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25046    cx.assert_editor_state(indoc! {"
25047        function handle() {
25048            ˇcase \"$1\" in
25049                ˇstart)
25050                    ˇecho \"a\"
25051                    ˇ;;
25052                ˇstop)
25053                    ˇecho \"b\"
25054                    ˇ;;
25055                ˇ*)
25056                    ˇecho \"c\"
25057                    ˇ;;
25058            ˇesac
25059        ˇ}
25060    "});
25061}
25062
25063#[gpui::test]
25064async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25065    init_test(cx, |_| {});
25066
25067    let mut cx = EditorTestContext::new(cx).await;
25068    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25069    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25070
25071    // test indents on comment insert
25072    cx.set_state(indoc! {"
25073        function main() {
25074        ˇ    for item in $items; do
25075        ˇ        while [ -n \"$item\" ]; do
25076        ˇ            if [ \"$value\" -gt 10 ]; then
25077        ˇ                continue
25078        ˇ            elif [ \"$value\" -lt 0 ]; then
25079        ˇ                break
25080        ˇ            else
25081        ˇ                echo \"$item\"
25082        ˇ            fi
25083        ˇ        done
25084        ˇ    done
25085        ˇ}
25086    "});
25087    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25088    cx.assert_editor_state(indoc! {"
25089        function main() {
25090        #ˇ    for item in $items; do
25091        #ˇ        while [ -n \"$item\" ]; do
25092        #ˇ            if [ \"$value\" -gt 10 ]; then
25093        #ˇ                continue
25094        #ˇ            elif [ \"$value\" -lt 0 ]; then
25095        #ˇ                break
25096        #ˇ            else
25097        #ˇ                echo \"$item\"
25098        #ˇ            fi
25099        #ˇ        done
25100        #ˇ    done
25101        #ˇ}
25102    "});
25103}
25104
25105#[gpui::test]
25106async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25107    init_test(cx, |_| {});
25108
25109    let mut cx = EditorTestContext::new(cx).await;
25110    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25112
25113    // test `else` auto outdents when typed inside `if` block
25114    cx.set_state(indoc! {"
25115        if [ \"$1\" = \"test\" ]; then
25116            echo \"foo bar\"
25117            ˇ
25118    "});
25119    cx.update_editor(|editor, window, cx| {
25120        editor.handle_input("else", window, cx);
25121    });
25122    cx.assert_editor_state(indoc! {"
25123        if [ \"$1\" = \"test\" ]; then
25124            echo \"foo bar\"
25125        elseˇ
25126    "});
25127
25128    // test `elif` auto outdents when typed inside `if` block
25129    cx.set_state(indoc! {"
25130        if [ \"$1\" = \"test\" ]; then
25131            echo \"foo bar\"
25132            ˇ
25133    "});
25134    cx.update_editor(|editor, window, cx| {
25135        editor.handle_input("elif", window, cx);
25136    });
25137    cx.assert_editor_state(indoc! {"
25138        if [ \"$1\" = \"test\" ]; then
25139            echo \"foo bar\"
25140        elifˇ
25141    "});
25142
25143    // test `fi` auto outdents when typed inside `else` block
25144    cx.set_state(indoc! {"
25145        if [ \"$1\" = \"test\" ]; then
25146            echo \"foo bar\"
25147        else
25148            echo \"bar baz\"
25149            ˇ
25150    "});
25151    cx.update_editor(|editor, window, cx| {
25152        editor.handle_input("fi", window, cx);
25153    });
25154    cx.assert_editor_state(indoc! {"
25155        if [ \"$1\" = \"test\" ]; then
25156            echo \"foo bar\"
25157        else
25158            echo \"bar baz\"
25159        fiˇ
25160    "});
25161
25162    // test `done` auto outdents when typed inside `while` block
25163    cx.set_state(indoc! {"
25164        while read line; do
25165            echo \"$line\"
25166            ˇ
25167    "});
25168    cx.update_editor(|editor, window, cx| {
25169        editor.handle_input("done", window, cx);
25170    });
25171    cx.assert_editor_state(indoc! {"
25172        while read line; do
25173            echo \"$line\"
25174        doneˇ
25175    "});
25176
25177    // test `done` auto outdents when typed inside `for` block
25178    cx.set_state(indoc! {"
25179        for file in *.txt; do
25180            cat \"$file\"
25181            ˇ
25182    "});
25183    cx.update_editor(|editor, window, cx| {
25184        editor.handle_input("done", window, cx);
25185    });
25186    cx.assert_editor_state(indoc! {"
25187        for file in *.txt; do
25188            cat \"$file\"
25189        doneˇ
25190    "});
25191
25192    // test `esac` auto outdents when typed inside `case` block
25193    cx.set_state(indoc! {"
25194        case \"$1\" in
25195            start)
25196                echo \"foo bar\"
25197                ;;
25198            stop)
25199                echo \"bar baz\"
25200                ;;
25201            ˇ
25202    "});
25203    cx.update_editor(|editor, window, cx| {
25204        editor.handle_input("esac", window, cx);
25205    });
25206    cx.assert_editor_state(indoc! {"
25207        case \"$1\" in
25208            start)
25209                echo \"foo bar\"
25210                ;;
25211            stop)
25212                echo \"bar baz\"
25213                ;;
25214        esacˇ
25215    "});
25216
25217    // test `*)` auto outdents when typed inside `case` block
25218    cx.set_state(indoc! {"
25219        case \"$1\" in
25220            start)
25221                echo \"foo bar\"
25222                ;;
25223                ˇ
25224    "});
25225    cx.update_editor(|editor, window, cx| {
25226        editor.handle_input("*)", window, cx);
25227    });
25228    cx.assert_editor_state(indoc! {"
25229        case \"$1\" in
25230            start)
25231                echo \"foo bar\"
25232                ;;
25233            *)ˇ
25234    "});
25235
25236    // test `fi` outdents to correct level with nested if blocks
25237    cx.set_state(indoc! {"
25238        if [ \"$1\" = \"test\" ]; then
25239            echo \"outer if\"
25240            if [ \"$2\" = \"debug\" ]; then
25241                echo \"inner if\"
25242                ˇ
25243    "});
25244    cx.update_editor(|editor, window, cx| {
25245        editor.handle_input("fi", window, cx);
25246    });
25247    cx.assert_editor_state(indoc! {"
25248        if [ \"$1\" = \"test\" ]; then
25249            echo \"outer if\"
25250            if [ \"$2\" = \"debug\" ]; then
25251                echo \"inner if\"
25252            fiˇ
25253    "});
25254}
25255
25256#[gpui::test]
25257async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25258    init_test(cx, |_| {});
25259    update_test_language_settings(cx, |settings| {
25260        settings.defaults.extend_comment_on_newline = Some(false);
25261    });
25262    let mut cx = EditorTestContext::new(cx).await;
25263    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25264    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25265
25266    // test correct indent after newline on comment
25267    cx.set_state(indoc! {"
25268        # COMMENT:ˇ
25269    "});
25270    cx.update_editor(|editor, window, cx| {
25271        editor.newline(&Newline, window, cx);
25272    });
25273    cx.assert_editor_state(indoc! {"
25274        # COMMENT:
25275        ˇ
25276    "});
25277
25278    // test correct indent after newline after `then`
25279    cx.set_state(indoc! {"
25280
25281        if [ \"$1\" = \"test\" ]; thenˇ
25282    "});
25283    cx.update_editor(|editor, window, cx| {
25284        editor.newline(&Newline, window, cx);
25285    });
25286    cx.run_until_parked();
25287    cx.assert_editor_state(indoc! {"
25288
25289        if [ \"$1\" = \"test\" ]; then
25290            ˇ
25291    "});
25292
25293    // test correct indent after newline after `else`
25294    cx.set_state(indoc! {"
25295        if [ \"$1\" = \"test\" ]; then
25296        elseˇ
25297    "});
25298    cx.update_editor(|editor, window, cx| {
25299        editor.newline(&Newline, window, cx);
25300    });
25301    cx.run_until_parked();
25302    cx.assert_editor_state(indoc! {"
25303        if [ \"$1\" = \"test\" ]; then
25304        else
25305            ˇ
25306    "});
25307
25308    // test correct indent after newline after `elif`
25309    cx.set_state(indoc! {"
25310        if [ \"$1\" = \"test\" ]; then
25311        elifˇ
25312    "});
25313    cx.update_editor(|editor, window, cx| {
25314        editor.newline(&Newline, window, cx);
25315    });
25316    cx.run_until_parked();
25317    cx.assert_editor_state(indoc! {"
25318        if [ \"$1\" = \"test\" ]; then
25319        elif
25320            ˇ
25321    "});
25322
25323    // test correct indent after newline after `do`
25324    cx.set_state(indoc! {"
25325        for file in *.txt; doˇ
25326    "});
25327    cx.update_editor(|editor, window, cx| {
25328        editor.newline(&Newline, window, cx);
25329    });
25330    cx.run_until_parked();
25331    cx.assert_editor_state(indoc! {"
25332        for file in *.txt; do
25333            ˇ
25334    "});
25335
25336    // test correct indent after newline after case pattern
25337    cx.set_state(indoc! {"
25338        case \"$1\" in
25339            start)ˇ
25340    "});
25341    cx.update_editor(|editor, window, cx| {
25342        editor.newline(&Newline, window, cx);
25343    });
25344    cx.run_until_parked();
25345    cx.assert_editor_state(indoc! {"
25346        case \"$1\" in
25347            start)
25348                ˇ
25349    "});
25350
25351    // test correct indent after newline after case pattern
25352    cx.set_state(indoc! {"
25353        case \"$1\" in
25354            start)
25355                ;;
25356            *)ˇ
25357    "});
25358    cx.update_editor(|editor, window, cx| {
25359        editor.newline(&Newline, window, cx);
25360    });
25361    cx.run_until_parked();
25362    cx.assert_editor_state(indoc! {"
25363        case \"$1\" in
25364            start)
25365                ;;
25366            *)
25367                ˇ
25368    "});
25369
25370    // test correct indent after newline after function opening brace
25371    cx.set_state(indoc! {"
25372        function test() {ˇ}
25373    "});
25374    cx.update_editor(|editor, window, cx| {
25375        editor.newline(&Newline, window, cx);
25376    });
25377    cx.run_until_parked();
25378    cx.assert_editor_state(indoc! {"
25379        function test() {
25380            ˇ
25381        }
25382    "});
25383
25384    // test no extra indent after semicolon on same line
25385    cx.set_state(indoc! {"
25386        echo \"test\"25387    "});
25388    cx.update_editor(|editor, window, cx| {
25389        editor.newline(&Newline, window, cx);
25390    });
25391    cx.run_until_parked();
25392    cx.assert_editor_state(indoc! {"
25393        echo \"test\";
25394        ˇ
25395    "});
25396}
25397
25398fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25399    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25400    point..point
25401}
25402
25403#[track_caller]
25404fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25405    let (text, ranges) = marked_text_ranges(marked_text, true);
25406    assert_eq!(editor.text(cx), text);
25407    assert_eq!(
25408        editor.selections.ranges(&editor.display_snapshot(cx)),
25409        ranges,
25410        "Assert selections are {}",
25411        marked_text
25412    );
25413}
25414
25415pub fn handle_signature_help_request(
25416    cx: &mut EditorLspTestContext,
25417    mocked_response: lsp::SignatureHelp,
25418) -> impl Future<Output = ()> + use<> {
25419    let mut request =
25420        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25421            let mocked_response = mocked_response.clone();
25422            async move { Ok(Some(mocked_response)) }
25423        });
25424
25425    async move {
25426        request.next().await;
25427    }
25428}
25429
25430#[track_caller]
25431pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25432    cx.update_editor(|editor, _, _| {
25433        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25434            let entries = menu.entries.borrow();
25435            let entries = entries
25436                .iter()
25437                .map(|entry| entry.string.as_str())
25438                .collect::<Vec<_>>();
25439            assert_eq!(entries, expected);
25440        } else {
25441            panic!("Expected completions menu");
25442        }
25443    });
25444}
25445
25446/// Handle completion request passing a marked string specifying where the completion
25447/// should be triggered from using '|' character, what range should be replaced, and what completions
25448/// should be returned using '<' and '>' to delimit the range.
25449///
25450/// Also see `handle_completion_request_with_insert_and_replace`.
25451#[track_caller]
25452pub fn handle_completion_request(
25453    marked_string: &str,
25454    completions: Vec<&'static str>,
25455    is_incomplete: bool,
25456    counter: Arc<AtomicUsize>,
25457    cx: &mut EditorLspTestContext,
25458) -> impl Future<Output = ()> {
25459    let complete_from_marker: TextRangeMarker = '|'.into();
25460    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25461    let (_, mut marked_ranges) = marked_text_ranges_by(
25462        marked_string,
25463        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25464    );
25465
25466    let complete_from_position =
25467        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25468    let replace_range =
25469        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25470
25471    let mut request =
25472        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25473            let completions = completions.clone();
25474            counter.fetch_add(1, atomic::Ordering::Release);
25475            async move {
25476                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25477                assert_eq!(
25478                    params.text_document_position.position,
25479                    complete_from_position
25480                );
25481                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25482                    is_incomplete,
25483                    item_defaults: None,
25484                    items: completions
25485                        .iter()
25486                        .map(|completion_text| lsp::CompletionItem {
25487                            label: completion_text.to_string(),
25488                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25489                                range: replace_range,
25490                                new_text: completion_text.to_string(),
25491                            })),
25492                            ..Default::default()
25493                        })
25494                        .collect(),
25495                })))
25496            }
25497        });
25498
25499    async move {
25500        request.next().await;
25501    }
25502}
25503
25504/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25505/// given instead, which also contains an `insert` range.
25506///
25507/// This function uses markers to define ranges:
25508/// - `|` marks the cursor position
25509/// - `<>` marks the replace range
25510/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25511pub fn handle_completion_request_with_insert_and_replace(
25512    cx: &mut EditorLspTestContext,
25513    marked_string: &str,
25514    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25515    counter: Arc<AtomicUsize>,
25516) -> impl Future<Output = ()> {
25517    let complete_from_marker: TextRangeMarker = '|'.into();
25518    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25519    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25520
25521    let (_, mut marked_ranges) = marked_text_ranges_by(
25522        marked_string,
25523        vec![
25524            complete_from_marker.clone(),
25525            replace_range_marker.clone(),
25526            insert_range_marker.clone(),
25527        ],
25528    );
25529
25530    let complete_from_position =
25531        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25532    let replace_range =
25533        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25534
25535    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25536        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25537        _ => lsp::Range {
25538            start: replace_range.start,
25539            end: complete_from_position,
25540        },
25541    };
25542
25543    let mut request =
25544        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25545            let completions = completions.clone();
25546            counter.fetch_add(1, atomic::Ordering::Release);
25547            async move {
25548                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25549                assert_eq!(
25550                    params.text_document_position.position, complete_from_position,
25551                    "marker `|` position doesn't match",
25552                );
25553                Ok(Some(lsp::CompletionResponse::Array(
25554                    completions
25555                        .iter()
25556                        .map(|(label, new_text)| lsp::CompletionItem {
25557                            label: label.to_string(),
25558                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25559                                lsp::InsertReplaceEdit {
25560                                    insert: insert_range,
25561                                    replace: replace_range,
25562                                    new_text: new_text.to_string(),
25563                                },
25564                            )),
25565                            ..Default::default()
25566                        })
25567                        .collect(),
25568                )))
25569            }
25570        });
25571
25572    async move {
25573        request.next().await;
25574    }
25575}
25576
25577fn handle_resolve_completion_request(
25578    cx: &mut EditorLspTestContext,
25579    edits: Option<Vec<(&'static str, &'static str)>>,
25580) -> impl Future<Output = ()> {
25581    let edits = edits.map(|edits| {
25582        edits
25583            .iter()
25584            .map(|(marked_string, new_text)| {
25585                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25586                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25587                lsp::TextEdit::new(replace_range, new_text.to_string())
25588            })
25589            .collect::<Vec<_>>()
25590    });
25591
25592    let mut request =
25593        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25594            let edits = edits.clone();
25595            async move {
25596                Ok(lsp::CompletionItem {
25597                    additional_text_edits: edits,
25598                    ..Default::default()
25599                })
25600            }
25601        });
25602
25603    async move {
25604        request.next().await;
25605    }
25606}
25607
25608pub(crate) fn update_test_language_settings(
25609    cx: &mut TestAppContext,
25610    f: impl Fn(&mut AllLanguageSettingsContent),
25611) {
25612    cx.update(|cx| {
25613        SettingsStore::update_global(cx, |store, cx| {
25614            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25615        });
25616    });
25617}
25618
25619pub(crate) fn update_test_project_settings(
25620    cx: &mut TestAppContext,
25621    f: impl Fn(&mut ProjectSettingsContent),
25622) {
25623    cx.update(|cx| {
25624        SettingsStore::update_global(cx, |store, cx| {
25625            store.update_user_settings(cx, |settings| f(&mut settings.project));
25626        });
25627    });
25628}
25629
25630pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25631    cx.update(|cx| {
25632        assets::Assets.load_test_fonts(cx);
25633        let store = SettingsStore::test(cx);
25634        cx.set_global(store);
25635        theme::init(theme::LoadThemes::JustBase, cx);
25636        release_channel::init(SemanticVersion::default(), cx);
25637        client::init_settings(cx);
25638        language::init(cx);
25639        Project::init_settings(cx);
25640        workspace::init_settings(cx);
25641        crate::init(cx);
25642    });
25643    zlog::init_test();
25644    update_test_language_settings(cx, f);
25645}
25646
25647#[track_caller]
25648fn assert_hunk_revert(
25649    not_reverted_text_with_selections: &str,
25650    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25651    expected_reverted_text_with_selections: &str,
25652    base_text: &str,
25653    cx: &mut EditorLspTestContext,
25654) {
25655    cx.set_state(not_reverted_text_with_selections);
25656    cx.set_head_text(base_text);
25657    cx.executor().run_until_parked();
25658
25659    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25660        let snapshot = editor.snapshot(window, cx);
25661        let reverted_hunk_statuses = snapshot
25662            .buffer_snapshot()
25663            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25664            .map(|hunk| hunk.status().kind)
25665            .collect::<Vec<_>>();
25666
25667        editor.git_restore(&Default::default(), window, cx);
25668        reverted_hunk_statuses
25669    });
25670    cx.executor().run_until_parked();
25671    cx.assert_editor_state(expected_reverted_text_with_selections);
25672    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25673}
25674
25675#[gpui::test(iterations = 10)]
25676async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25677    init_test(cx, |_| {});
25678
25679    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25680    let counter = diagnostic_requests.clone();
25681
25682    let fs = FakeFs::new(cx.executor());
25683    fs.insert_tree(
25684        path!("/a"),
25685        json!({
25686            "first.rs": "fn main() { let a = 5; }",
25687            "second.rs": "// Test file",
25688        }),
25689    )
25690    .await;
25691
25692    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25693    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25694    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25695
25696    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25697    language_registry.add(rust_lang());
25698    let mut fake_servers = language_registry.register_fake_lsp(
25699        "Rust",
25700        FakeLspAdapter {
25701            capabilities: lsp::ServerCapabilities {
25702                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25703                    lsp::DiagnosticOptions {
25704                        identifier: None,
25705                        inter_file_dependencies: true,
25706                        workspace_diagnostics: true,
25707                        work_done_progress_options: Default::default(),
25708                    },
25709                )),
25710                ..Default::default()
25711            },
25712            ..Default::default()
25713        },
25714    );
25715
25716    let editor = workspace
25717        .update(cx, |workspace, window, cx| {
25718            workspace.open_abs_path(
25719                PathBuf::from(path!("/a/first.rs")),
25720                OpenOptions::default(),
25721                window,
25722                cx,
25723            )
25724        })
25725        .unwrap()
25726        .await
25727        .unwrap()
25728        .downcast::<Editor>()
25729        .unwrap();
25730    let fake_server = fake_servers.next().await.unwrap();
25731    let server_id = fake_server.server.server_id();
25732    let mut first_request = fake_server
25733        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25734            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25735            let result_id = Some(new_result_id.to_string());
25736            assert_eq!(
25737                params.text_document.uri,
25738                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25739            );
25740            async move {
25741                Ok(lsp::DocumentDiagnosticReportResult::Report(
25742                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25743                        related_documents: None,
25744                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25745                            items: Vec::new(),
25746                            result_id,
25747                        },
25748                    }),
25749                ))
25750            }
25751        });
25752
25753    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25754        project.update(cx, |project, cx| {
25755            let buffer_id = editor
25756                .read(cx)
25757                .buffer()
25758                .read(cx)
25759                .as_singleton()
25760                .expect("created a singleton buffer")
25761                .read(cx)
25762                .remote_id();
25763            let buffer_result_id = project
25764                .lsp_store()
25765                .read(cx)
25766                .result_id(server_id, buffer_id, cx);
25767            assert_eq!(expected, buffer_result_id);
25768        });
25769    };
25770
25771    ensure_result_id(None, cx);
25772    cx.executor().advance_clock(Duration::from_millis(60));
25773    cx.executor().run_until_parked();
25774    assert_eq!(
25775        diagnostic_requests.load(atomic::Ordering::Acquire),
25776        1,
25777        "Opening file should trigger diagnostic request"
25778    );
25779    first_request
25780        .next()
25781        .await
25782        .expect("should have sent the first diagnostics pull request");
25783    ensure_result_id(Some("1".to_string()), cx);
25784
25785    // Editing should trigger diagnostics
25786    editor.update_in(cx, |editor, window, cx| {
25787        editor.handle_input("2", window, cx)
25788    });
25789    cx.executor().advance_clock(Duration::from_millis(60));
25790    cx.executor().run_until_parked();
25791    assert_eq!(
25792        diagnostic_requests.load(atomic::Ordering::Acquire),
25793        2,
25794        "Editing should trigger diagnostic request"
25795    );
25796    ensure_result_id(Some("2".to_string()), cx);
25797
25798    // Moving cursor should not trigger diagnostic request
25799    editor.update_in(cx, |editor, window, cx| {
25800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25801            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25802        });
25803    });
25804    cx.executor().advance_clock(Duration::from_millis(60));
25805    cx.executor().run_until_parked();
25806    assert_eq!(
25807        diagnostic_requests.load(atomic::Ordering::Acquire),
25808        2,
25809        "Cursor movement should not trigger diagnostic request"
25810    );
25811    ensure_result_id(Some("2".to_string()), cx);
25812    // Multiple rapid edits should be debounced
25813    for _ in 0..5 {
25814        editor.update_in(cx, |editor, window, cx| {
25815            editor.handle_input("x", window, cx)
25816        });
25817    }
25818    cx.executor().advance_clock(Duration::from_millis(60));
25819    cx.executor().run_until_parked();
25820
25821    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25822    assert!(
25823        final_requests <= 4,
25824        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25825    );
25826    ensure_result_id(Some(final_requests.to_string()), cx);
25827}
25828
25829#[gpui::test]
25830async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25831    // Regression test for issue #11671
25832    // Previously, adding a cursor after moving multiple cursors would reset
25833    // the cursor count instead of adding to the existing cursors.
25834    init_test(cx, |_| {});
25835    let mut cx = EditorTestContext::new(cx).await;
25836
25837    // Create a simple buffer with cursor at start
25838    cx.set_state(indoc! {"
25839        ˇaaaa
25840        bbbb
25841        cccc
25842        dddd
25843        eeee
25844        ffff
25845        gggg
25846        hhhh"});
25847
25848    // Add 2 cursors below (so we have 3 total)
25849    cx.update_editor(|editor, window, cx| {
25850        editor.add_selection_below(&Default::default(), window, cx);
25851        editor.add_selection_below(&Default::default(), window, cx);
25852    });
25853
25854    // Verify we have 3 cursors
25855    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25856    assert_eq!(
25857        initial_count, 3,
25858        "Should have 3 cursors after adding 2 below"
25859    );
25860
25861    // Move down one line
25862    cx.update_editor(|editor, window, cx| {
25863        editor.move_down(&MoveDown, window, cx);
25864    });
25865
25866    // Add another cursor below
25867    cx.update_editor(|editor, window, cx| {
25868        editor.add_selection_below(&Default::default(), window, cx);
25869    });
25870
25871    // Should now have 4 cursors (3 original + 1 new)
25872    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25873    assert_eq!(
25874        final_count, 4,
25875        "Should have 4 cursors after moving and adding another"
25876    );
25877}
25878
25879#[gpui::test]
25880async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25881    init_test(cx, |_| {});
25882
25883    let mut cx = EditorTestContext::new(cx).await;
25884
25885    cx.set_state(indoc!(
25886        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25887           Second line here"#
25888    ));
25889
25890    cx.update_editor(|editor, window, cx| {
25891        // Enable soft wrapping with a narrow width to force soft wrapping and
25892        // confirm that more than 2 rows are being displayed.
25893        editor.set_wrap_width(Some(100.0.into()), cx);
25894        assert!(editor.display_text(cx).lines().count() > 2);
25895
25896        editor.add_selection_below(
25897            &AddSelectionBelow {
25898                skip_soft_wrap: true,
25899            },
25900            window,
25901            cx,
25902        );
25903
25904        assert_eq!(
25905            editor.selections.display_ranges(cx),
25906            &[
25907                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25908                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25909            ]
25910        );
25911
25912        editor.add_selection_above(
25913            &AddSelectionAbove {
25914                skip_soft_wrap: true,
25915            },
25916            window,
25917            cx,
25918        );
25919
25920        assert_eq!(
25921            editor.selections.display_ranges(cx),
25922            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25923        );
25924
25925        editor.add_selection_below(
25926            &AddSelectionBelow {
25927                skip_soft_wrap: false,
25928            },
25929            window,
25930            cx,
25931        );
25932
25933        assert_eq!(
25934            editor.selections.display_ranges(cx),
25935            &[
25936                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25937                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25938            ]
25939        );
25940
25941        editor.add_selection_above(
25942            &AddSelectionAbove {
25943                skip_soft_wrap: false,
25944            },
25945            window,
25946            cx,
25947        );
25948
25949        assert_eq!(
25950            editor.selections.display_ranges(cx),
25951            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25952        );
25953    });
25954}
25955
25956#[gpui::test(iterations = 10)]
25957async fn test_document_colors(cx: &mut TestAppContext) {
25958    let expected_color = Rgba {
25959        r: 0.33,
25960        g: 0.33,
25961        b: 0.33,
25962        a: 0.33,
25963    };
25964
25965    init_test(cx, |_| {});
25966
25967    let fs = FakeFs::new(cx.executor());
25968    fs.insert_tree(
25969        path!("/a"),
25970        json!({
25971            "first.rs": "fn main() { let a = 5; }",
25972        }),
25973    )
25974    .await;
25975
25976    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25977    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25978    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25979
25980    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25981    language_registry.add(rust_lang());
25982    let mut fake_servers = language_registry.register_fake_lsp(
25983        "Rust",
25984        FakeLspAdapter {
25985            capabilities: lsp::ServerCapabilities {
25986                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25987                ..lsp::ServerCapabilities::default()
25988            },
25989            name: "rust-analyzer",
25990            ..FakeLspAdapter::default()
25991        },
25992    );
25993    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25994        "Rust",
25995        FakeLspAdapter {
25996            capabilities: lsp::ServerCapabilities {
25997                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25998                ..lsp::ServerCapabilities::default()
25999            },
26000            name: "not-rust-analyzer",
26001            ..FakeLspAdapter::default()
26002        },
26003    );
26004
26005    let editor = workspace
26006        .update(cx, |workspace, window, cx| {
26007            workspace.open_abs_path(
26008                PathBuf::from(path!("/a/first.rs")),
26009                OpenOptions::default(),
26010                window,
26011                cx,
26012            )
26013        })
26014        .unwrap()
26015        .await
26016        .unwrap()
26017        .downcast::<Editor>()
26018        .unwrap();
26019    let fake_language_server = fake_servers.next().await.unwrap();
26020    let fake_language_server_without_capabilities =
26021        fake_servers_without_capabilities.next().await.unwrap();
26022    let requests_made = Arc::new(AtomicUsize::new(0));
26023    let closure_requests_made = Arc::clone(&requests_made);
26024    let mut color_request_handle = fake_language_server
26025        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26026            let requests_made = Arc::clone(&closure_requests_made);
26027            async move {
26028                assert_eq!(
26029                    params.text_document.uri,
26030                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26031                );
26032                requests_made.fetch_add(1, atomic::Ordering::Release);
26033                Ok(vec![
26034                    lsp::ColorInformation {
26035                        range: lsp::Range {
26036                            start: lsp::Position {
26037                                line: 0,
26038                                character: 0,
26039                            },
26040                            end: lsp::Position {
26041                                line: 0,
26042                                character: 1,
26043                            },
26044                        },
26045                        color: lsp::Color {
26046                            red: 0.33,
26047                            green: 0.33,
26048                            blue: 0.33,
26049                            alpha: 0.33,
26050                        },
26051                    },
26052                    lsp::ColorInformation {
26053                        range: lsp::Range {
26054                            start: lsp::Position {
26055                                line: 0,
26056                                character: 0,
26057                            },
26058                            end: lsp::Position {
26059                                line: 0,
26060                                character: 1,
26061                            },
26062                        },
26063                        color: lsp::Color {
26064                            red: 0.33,
26065                            green: 0.33,
26066                            blue: 0.33,
26067                            alpha: 0.33,
26068                        },
26069                    },
26070                ])
26071            }
26072        });
26073
26074    let _handle = fake_language_server_without_capabilities
26075        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26076            panic!("Should not be called");
26077        });
26078    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26079    color_request_handle.next().await.unwrap();
26080    cx.run_until_parked();
26081    assert_eq!(
26082        1,
26083        requests_made.load(atomic::Ordering::Acquire),
26084        "Should query for colors once per editor open"
26085    );
26086    editor.update_in(cx, |editor, _, cx| {
26087        assert_eq!(
26088            vec![expected_color],
26089            extract_color_inlays(editor, cx),
26090            "Should have an initial inlay"
26091        );
26092    });
26093
26094    // opening another file in a split should not influence the LSP query counter
26095    workspace
26096        .update(cx, |workspace, window, cx| {
26097            assert_eq!(
26098                workspace.panes().len(),
26099                1,
26100                "Should have one pane with one editor"
26101            );
26102            workspace.move_item_to_pane_in_direction(
26103                &MoveItemToPaneInDirection {
26104                    direction: SplitDirection::Right,
26105                    focus: false,
26106                    clone: true,
26107                },
26108                window,
26109                cx,
26110            );
26111        })
26112        .unwrap();
26113    cx.run_until_parked();
26114    workspace
26115        .update(cx, |workspace, _, cx| {
26116            let panes = workspace.panes();
26117            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26118            for pane in panes {
26119                let editor = pane
26120                    .read(cx)
26121                    .active_item()
26122                    .and_then(|item| item.downcast::<Editor>())
26123                    .expect("Should have opened an editor in each split");
26124                let editor_file = editor
26125                    .read(cx)
26126                    .buffer()
26127                    .read(cx)
26128                    .as_singleton()
26129                    .expect("test deals with singleton buffers")
26130                    .read(cx)
26131                    .file()
26132                    .expect("test buffese should have a file")
26133                    .path();
26134                assert_eq!(
26135                    editor_file.as_ref(),
26136                    rel_path("first.rs"),
26137                    "Both editors should be opened for the same file"
26138                )
26139            }
26140        })
26141        .unwrap();
26142
26143    cx.executor().advance_clock(Duration::from_millis(500));
26144    let save = editor.update_in(cx, |editor, window, cx| {
26145        editor.move_to_end(&MoveToEnd, window, cx);
26146        editor.handle_input("dirty", window, cx);
26147        editor.save(
26148            SaveOptions {
26149                format: true,
26150                autosave: true,
26151            },
26152            project.clone(),
26153            window,
26154            cx,
26155        )
26156    });
26157    save.await.unwrap();
26158
26159    color_request_handle.next().await.unwrap();
26160    cx.run_until_parked();
26161    assert_eq!(
26162        2,
26163        requests_made.load(atomic::Ordering::Acquire),
26164        "Should query for colors once per save (deduplicated) and once per formatting after save"
26165    );
26166
26167    drop(editor);
26168    let close = workspace
26169        .update(cx, |workspace, window, cx| {
26170            workspace.active_pane().update(cx, |pane, cx| {
26171                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26172            })
26173        })
26174        .unwrap();
26175    close.await.unwrap();
26176    let close = workspace
26177        .update(cx, |workspace, window, cx| {
26178            workspace.active_pane().update(cx, |pane, cx| {
26179                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26180            })
26181        })
26182        .unwrap();
26183    close.await.unwrap();
26184    assert_eq!(
26185        2,
26186        requests_made.load(atomic::Ordering::Acquire),
26187        "After saving and closing all editors, no extra requests should be made"
26188    );
26189    workspace
26190        .update(cx, |workspace, _, cx| {
26191            assert!(
26192                workspace.active_item(cx).is_none(),
26193                "Should close all editors"
26194            )
26195        })
26196        .unwrap();
26197
26198    workspace
26199        .update(cx, |workspace, window, cx| {
26200            workspace.active_pane().update(cx, |pane, cx| {
26201                pane.navigate_backward(&workspace::GoBack, window, cx);
26202            })
26203        })
26204        .unwrap();
26205    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26206    cx.run_until_parked();
26207    let editor = workspace
26208        .update(cx, |workspace, _, cx| {
26209            workspace
26210                .active_item(cx)
26211                .expect("Should have reopened the editor again after navigating back")
26212                .downcast::<Editor>()
26213                .expect("Should be an editor")
26214        })
26215        .unwrap();
26216
26217    assert_eq!(
26218        2,
26219        requests_made.load(atomic::Ordering::Acquire),
26220        "Cache should be reused on buffer close and reopen"
26221    );
26222    editor.update(cx, |editor, cx| {
26223        assert_eq!(
26224            vec![expected_color],
26225            extract_color_inlays(editor, cx),
26226            "Should have an initial inlay"
26227        );
26228    });
26229
26230    drop(color_request_handle);
26231    let closure_requests_made = Arc::clone(&requests_made);
26232    let mut empty_color_request_handle = fake_language_server
26233        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26234            let requests_made = Arc::clone(&closure_requests_made);
26235            async move {
26236                assert_eq!(
26237                    params.text_document.uri,
26238                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26239                );
26240                requests_made.fetch_add(1, atomic::Ordering::Release);
26241                Ok(Vec::new())
26242            }
26243        });
26244    let save = editor.update_in(cx, |editor, window, cx| {
26245        editor.move_to_end(&MoveToEnd, window, cx);
26246        editor.handle_input("dirty_again", window, cx);
26247        editor.save(
26248            SaveOptions {
26249                format: false,
26250                autosave: true,
26251            },
26252            project.clone(),
26253            window,
26254            cx,
26255        )
26256    });
26257    save.await.unwrap();
26258
26259    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26260    empty_color_request_handle.next().await.unwrap();
26261    cx.run_until_parked();
26262    assert_eq!(
26263        3,
26264        requests_made.load(atomic::Ordering::Acquire),
26265        "Should query for colors once per save only, as formatting was not requested"
26266    );
26267    editor.update(cx, |editor, cx| {
26268        assert_eq!(
26269            Vec::<Rgba>::new(),
26270            extract_color_inlays(editor, cx),
26271            "Should clear all colors when the server returns an empty response"
26272        );
26273    });
26274}
26275
26276#[gpui::test]
26277async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26278    init_test(cx, |_| {});
26279    let (editor, cx) = cx.add_window_view(Editor::single_line);
26280    editor.update_in(cx, |editor, window, cx| {
26281        editor.set_text("oops\n\nwow\n", window, cx)
26282    });
26283    cx.run_until_parked();
26284    editor.update(cx, |editor, cx| {
26285        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26286    });
26287    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26288    cx.run_until_parked();
26289    editor.update(cx, |editor, cx| {
26290        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26291    });
26292}
26293
26294#[gpui::test]
26295async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26296    init_test(cx, |_| {});
26297
26298    cx.update(|cx| {
26299        register_project_item::<Editor>(cx);
26300    });
26301
26302    let fs = FakeFs::new(cx.executor());
26303    fs.insert_tree("/root1", json!({})).await;
26304    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26305        .await;
26306
26307    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26308    let (workspace, cx) =
26309        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26310
26311    let worktree_id = project.update(cx, |project, cx| {
26312        project.worktrees(cx).next().unwrap().read(cx).id()
26313    });
26314
26315    let handle = workspace
26316        .update_in(cx, |workspace, window, cx| {
26317            let project_path = (worktree_id, rel_path("one.pdf"));
26318            workspace.open_path(project_path, None, true, window, cx)
26319        })
26320        .await
26321        .unwrap();
26322
26323    assert_eq!(
26324        handle.to_any().entity_type(),
26325        TypeId::of::<InvalidItemView>()
26326    );
26327}
26328
26329#[gpui::test]
26330async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26331    init_test(cx, |_| {});
26332
26333    let language = Arc::new(Language::new(
26334        LanguageConfig::default(),
26335        Some(tree_sitter_rust::LANGUAGE.into()),
26336    ));
26337
26338    // Test hierarchical sibling navigation
26339    let text = r#"
26340        fn outer() {
26341            if condition {
26342                let a = 1;
26343            }
26344            let b = 2;
26345        }
26346
26347        fn another() {
26348            let c = 3;
26349        }
26350    "#;
26351
26352    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26353    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26354    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26355
26356    // Wait for parsing to complete
26357    editor
26358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26359        .await;
26360
26361    editor.update_in(cx, |editor, window, cx| {
26362        // Start by selecting "let a = 1;" inside the if block
26363        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26364            s.select_display_ranges([
26365                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26366            ]);
26367        });
26368
26369        let initial_selection = editor.selections.display_ranges(cx);
26370        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26371
26372        // Test select next sibling - should move up levels to find the next sibling
26373        // Since "let a = 1;" has no siblings in the if block, it should move up
26374        // to find "let b = 2;" which is a sibling of the if block
26375        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26376        let next_selection = editor.selections.display_ranges(cx);
26377
26378        // Should have a selection and it should be different from the initial
26379        assert_eq!(
26380            next_selection.len(),
26381            1,
26382            "Should have one selection after next"
26383        );
26384        assert_ne!(
26385            next_selection[0], initial_selection[0],
26386            "Next sibling selection should be different"
26387        );
26388
26389        // Test hierarchical navigation by going to the end of the current function
26390        // and trying to navigate to the next function
26391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26392            s.select_display_ranges([
26393                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26394            ]);
26395        });
26396
26397        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26398        let function_next_selection = editor.selections.display_ranges(cx);
26399
26400        // Should move to the next function
26401        assert_eq!(
26402            function_next_selection.len(),
26403            1,
26404            "Should have one selection after function next"
26405        );
26406
26407        // Test select previous sibling navigation
26408        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26409        let prev_selection = editor.selections.display_ranges(cx);
26410
26411        // Should have a selection and it should be different
26412        assert_eq!(
26413            prev_selection.len(),
26414            1,
26415            "Should have one selection after prev"
26416        );
26417        assert_ne!(
26418            prev_selection[0], function_next_selection[0],
26419            "Previous sibling selection should be different from next"
26420        );
26421    });
26422}
26423
26424#[gpui::test]
26425async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26426    init_test(cx, |_| {});
26427
26428    let mut cx = EditorTestContext::new(cx).await;
26429    cx.set_state(
26430        "let ˇvariable = 42;
26431let another = variable + 1;
26432let result = variable * 2;",
26433    );
26434
26435    // Set up document highlights manually (simulating LSP response)
26436    cx.update_editor(|editor, _window, cx| {
26437        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26438
26439        // Create highlights for "variable" occurrences
26440        let highlight_ranges = [
26441            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26442            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26443            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26444        ];
26445
26446        let anchor_ranges: Vec<_> = highlight_ranges
26447            .iter()
26448            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26449            .collect();
26450
26451        editor.highlight_background::<DocumentHighlightRead>(
26452            &anchor_ranges,
26453            |theme| theme.colors().editor_document_highlight_read_background,
26454            cx,
26455        );
26456    });
26457
26458    // Go to next highlight - should move to second "variable"
26459    cx.update_editor(|editor, window, cx| {
26460        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26461    });
26462    cx.assert_editor_state(
26463        "let variable = 42;
26464let another = ˇvariable + 1;
26465let result = variable * 2;",
26466    );
26467
26468    // Go to next highlight - should move to third "variable"
26469    cx.update_editor(|editor, window, cx| {
26470        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26471    });
26472    cx.assert_editor_state(
26473        "let variable = 42;
26474let another = variable + 1;
26475let result = ˇvariable * 2;",
26476    );
26477
26478    // Go to next highlight - should stay at third "variable" (no wrap-around)
26479    cx.update_editor(|editor, window, cx| {
26480        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26481    });
26482    cx.assert_editor_state(
26483        "let variable = 42;
26484let another = variable + 1;
26485let result = ˇvariable * 2;",
26486    );
26487
26488    // Now test going backwards from third position
26489    cx.update_editor(|editor, window, cx| {
26490        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26491    });
26492    cx.assert_editor_state(
26493        "let variable = 42;
26494let another = ˇvariable + 1;
26495let result = variable * 2;",
26496    );
26497
26498    // Go to previous highlight - should move to first "variable"
26499    cx.update_editor(|editor, window, cx| {
26500        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26501    });
26502    cx.assert_editor_state(
26503        "let ˇvariable = 42;
26504let another = variable + 1;
26505let result = variable * 2;",
26506    );
26507
26508    // Go to previous highlight - should stay on first "variable"
26509    cx.update_editor(|editor, window, cx| {
26510        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26511    });
26512    cx.assert_editor_state(
26513        "let ˇvariable = 42;
26514let another = variable + 1;
26515let result = variable * 2;",
26516    );
26517}
26518
26519#[gpui::test]
26520async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
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("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
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!(
26545        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26546    ));
26547}
26548
26549#[gpui::test]
26550async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26551    cx: &mut gpui::TestAppContext,
26552) {
26553    init_test(cx, |_| {});
26554
26555    let url = "https://zed.dev";
26556
26557    let markdown_language = Arc::new(Language::new(
26558        LanguageConfig {
26559            name: "Markdown".into(),
26560            ..LanguageConfig::default()
26561        },
26562        None,
26563    ));
26564
26565    let mut cx = EditorTestContext::new(cx).await;
26566    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26567    cx.set_state(&format!(
26568        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26569    ));
26570
26571    cx.update_editor(|editor, window, cx| {
26572        editor.copy(&Copy, window, cx);
26573    });
26574
26575    cx.set_state(&format!(
26576        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26577    ));
26578
26579    cx.update_editor(|editor, window, cx| {
26580        editor.paste(&Paste, window, cx);
26581    });
26582
26583    cx.assert_editor_state(&format!(
26584        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26585    ));
26586}
26587
26588#[gpui::test]
26589async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26590    cx: &mut gpui::TestAppContext,
26591) {
26592    init_test(cx, |_| {});
26593
26594    let url = "https://zed.dev";
26595
26596    let markdown_language = Arc::new(Language::new(
26597        LanguageConfig {
26598            name: "Markdown".into(),
26599            ..LanguageConfig::default()
26600        },
26601        None,
26602    ));
26603
26604    let mut cx = EditorTestContext::new(cx).await;
26605    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26606    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26607
26608    cx.update_editor(|editor, window, cx| {
26609        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26610        editor.paste(&Paste, window, cx);
26611    });
26612
26613    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26614}
26615
26616#[gpui::test]
26617async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26618    cx: &mut gpui::TestAppContext,
26619) {
26620    init_test(cx, |_| {});
26621
26622    let text = "Awesome";
26623
26624    let markdown_language = Arc::new(Language::new(
26625        LanguageConfig {
26626            name: "Markdown".into(),
26627            ..LanguageConfig::default()
26628        },
26629        None,
26630    ));
26631
26632    let mut cx = EditorTestContext::new(cx).await;
26633    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26634    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26635
26636    cx.update_editor(|editor, window, cx| {
26637        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26638        editor.paste(&Paste, window, cx);
26639    });
26640
26641    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26642}
26643
26644#[gpui::test]
26645async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26646    cx: &mut gpui::TestAppContext,
26647) {
26648    init_test(cx, |_| {});
26649
26650    let url = "https://zed.dev";
26651
26652    let markdown_language = Arc::new(Language::new(
26653        LanguageConfig {
26654            name: "Rust".into(),
26655            ..LanguageConfig::default()
26656        },
26657        None,
26658    ));
26659
26660    let mut cx = EditorTestContext::new(cx).await;
26661    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26662    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26663
26664    cx.update_editor(|editor, window, cx| {
26665        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26666        editor.paste(&Paste, window, cx);
26667    });
26668
26669    cx.assert_editor_state(&format!(
26670        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26671    ));
26672}
26673
26674#[gpui::test]
26675async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26676    cx: &mut TestAppContext,
26677) {
26678    init_test(cx, |_| {});
26679
26680    let url = "https://zed.dev";
26681
26682    let markdown_language = Arc::new(Language::new(
26683        LanguageConfig {
26684            name: "Markdown".into(),
26685            ..LanguageConfig::default()
26686        },
26687        None,
26688    ));
26689
26690    let (editor, cx) = cx.add_window_view(|window, cx| {
26691        let multi_buffer = MultiBuffer::build_multi(
26692            [
26693                ("this will embed -> link", vec![Point::row_range(0..1)]),
26694                ("this will replace -> link", vec![Point::row_range(0..1)]),
26695            ],
26696            cx,
26697        );
26698        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26699        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26700            s.select_ranges(vec![
26701                Point::new(0, 19)..Point::new(0, 23),
26702                Point::new(1, 21)..Point::new(1, 25),
26703            ])
26704        });
26705        let first_buffer_id = multi_buffer
26706            .read(cx)
26707            .excerpt_buffer_ids()
26708            .into_iter()
26709            .next()
26710            .unwrap();
26711        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26712        first_buffer.update(cx, |buffer, cx| {
26713            buffer.set_language(Some(markdown_language.clone()), cx);
26714        });
26715
26716        editor
26717    });
26718    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26719
26720    cx.update_editor(|editor, window, cx| {
26721        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26722        editor.paste(&Paste, window, cx);
26723    });
26724
26725    cx.assert_editor_state(&format!(
26726        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26727    ));
26728}
26729
26730#[gpui::test]
26731async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26732    init_test(cx, |_| {});
26733
26734    let fs = FakeFs::new(cx.executor());
26735    fs.insert_tree(
26736        path!("/project"),
26737        json!({
26738            "first.rs": "# First Document\nSome content here.",
26739            "second.rs": "Plain text content for second file.",
26740        }),
26741    )
26742    .await;
26743
26744    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26745    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26746    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26747
26748    let language = rust_lang();
26749    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26750    language_registry.add(language.clone());
26751    let mut fake_servers = language_registry.register_fake_lsp(
26752        "Rust",
26753        FakeLspAdapter {
26754            ..FakeLspAdapter::default()
26755        },
26756    );
26757
26758    let buffer1 = project
26759        .update(cx, |project, cx| {
26760            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26761        })
26762        .await
26763        .unwrap();
26764    let buffer2 = project
26765        .update(cx, |project, cx| {
26766            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26767        })
26768        .await
26769        .unwrap();
26770
26771    let multi_buffer = cx.new(|cx| {
26772        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26773        multi_buffer.set_excerpts_for_path(
26774            PathKey::for_buffer(&buffer1, cx),
26775            buffer1.clone(),
26776            [Point::zero()..buffer1.read(cx).max_point()],
26777            3,
26778            cx,
26779        );
26780        multi_buffer.set_excerpts_for_path(
26781            PathKey::for_buffer(&buffer2, cx),
26782            buffer2.clone(),
26783            [Point::zero()..buffer1.read(cx).max_point()],
26784            3,
26785            cx,
26786        );
26787        multi_buffer
26788    });
26789
26790    let (editor, cx) = cx.add_window_view(|window, cx| {
26791        Editor::new(
26792            EditorMode::full(),
26793            multi_buffer,
26794            Some(project.clone()),
26795            window,
26796            cx,
26797        )
26798    });
26799
26800    let fake_language_server = fake_servers.next().await.unwrap();
26801
26802    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26803
26804    let save = editor.update_in(cx, |editor, window, cx| {
26805        assert!(editor.is_dirty(cx));
26806
26807        editor.save(
26808            SaveOptions {
26809                format: true,
26810                autosave: true,
26811            },
26812            project,
26813            window,
26814            cx,
26815        )
26816    });
26817    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26818    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26819    let mut done_edit_rx = Some(done_edit_rx);
26820    let mut start_edit_tx = Some(start_edit_tx);
26821
26822    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26823        start_edit_tx.take().unwrap().send(()).unwrap();
26824        let done_edit_rx = done_edit_rx.take().unwrap();
26825        async move {
26826            done_edit_rx.await.unwrap();
26827            Ok(None)
26828        }
26829    });
26830
26831    start_edit_rx.await.unwrap();
26832    buffer2
26833        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26834        .unwrap();
26835
26836    done_edit_tx.send(()).unwrap();
26837
26838    save.await.unwrap();
26839    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26840}
26841
26842#[track_caller]
26843fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26844    editor
26845        .all_inlays(cx)
26846        .into_iter()
26847        .filter_map(|inlay| inlay.get_color())
26848        .map(Rgba::from)
26849        .collect()
26850}
26851
26852#[gpui::test]
26853fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26854    init_test(cx, |_| {});
26855
26856    let editor = cx.add_window(|window, cx| {
26857        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26858        build_editor(buffer, window, cx)
26859    });
26860
26861    editor
26862        .update(cx, |editor, window, cx| {
26863            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26864                s.select_display_ranges([
26865                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26866                ])
26867            });
26868
26869            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26870
26871            assert_eq!(
26872                editor.display_text(cx),
26873                "line1\nline2\nline2",
26874                "Duplicating last line upward should create duplicate above, not on same line"
26875            );
26876
26877            assert_eq!(
26878                editor.selections.display_ranges(cx),
26879                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26880                "Selection should move to the duplicated line"
26881            );
26882        })
26883        .unwrap();
26884}
26885
26886#[gpui::test]
26887async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26888    init_test(cx, |_| {});
26889
26890    let mut cx = EditorTestContext::new(cx).await;
26891
26892    cx.set_state("line1\nline2ˇ");
26893
26894    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26895
26896    let clipboard_text = cx
26897        .read_from_clipboard()
26898        .and_then(|item| item.text().as_deref().map(str::to_string));
26899
26900    assert_eq!(
26901        clipboard_text,
26902        Some("line2\n".to_string()),
26903        "Copying a line without trailing newline should include a newline"
26904    );
26905
26906    cx.set_state("line1\nˇ");
26907
26908    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26909
26910    cx.assert_editor_state("line1\nline2\nˇ");
26911}
26912
26913#[gpui::test]
26914async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26915    init_test(cx, |_| {});
26916
26917    let mut cx = EditorTestContext::new(cx).await;
26918
26919    cx.set_state("line1\nline2ˇ");
26920    cx.update_editor(|e, window, cx| {
26921        e.set_mode(EditorMode::SingleLine);
26922        assert!(e.key_context(window, cx).contains("end_of_input"));
26923    });
26924    cx.set_state("ˇline1\nline2");
26925    cx.update_editor(|e, window, cx| {
26926        assert!(!e.key_context(window, cx).contains("end_of_input"));
26927    });
26928    cx.set_state("line1ˇ\nline2");
26929    cx.update_editor(|e, window, cx| {
26930        assert!(!e.key_context(window, cx).contains("end_of_input"));
26931    });
26932}
26933
26934#[gpui::test]
26935async fn test_next_prev_reference(cx: &mut TestAppContext) {
26936    const CYCLE_POSITIONS: &[&'static str] = &[
26937        indoc! {"
26938            fn foo() {
26939                let ˇabc = 123;
26940                let x = abc + 1;
26941                let y = abc + 2;
26942                let z = abc + 2;
26943            }
26944        "},
26945        indoc! {"
26946            fn foo() {
26947                let abc = 123;
26948                let x = ˇabc + 1;
26949                let y = abc + 2;
26950                let z = abc + 2;
26951            }
26952        "},
26953        indoc! {"
26954            fn foo() {
26955                let abc = 123;
26956                let x = abc + 1;
26957                let y = ˇabc + 2;
26958                let z = abc + 2;
26959            }
26960        "},
26961        indoc! {"
26962            fn foo() {
26963                let abc = 123;
26964                let x = abc + 1;
26965                let y = abc + 2;
26966                let z = ˇabc + 2;
26967            }
26968        "},
26969    ];
26970
26971    init_test(cx, |_| {});
26972
26973    let mut cx = EditorLspTestContext::new_rust(
26974        lsp::ServerCapabilities {
26975            references_provider: Some(lsp::OneOf::Left(true)),
26976            ..Default::default()
26977        },
26978        cx,
26979    )
26980    .await;
26981
26982    // importantly, the cursor is in the middle
26983    cx.set_state(indoc! {"
26984        fn foo() {
26985            let aˇbc = 123;
26986            let x = abc + 1;
26987            let y = abc + 2;
26988            let z = abc + 2;
26989        }
26990    "});
26991
26992    let reference_ranges = [
26993        lsp::Position::new(1, 8),
26994        lsp::Position::new(2, 12),
26995        lsp::Position::new(3, 12),
26996        lsp::Position::new(4, 12),
26997    ]
26998    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
26999
27000    cx.lsp
27001        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27002            Ok(Some(
27003                reference_ranges
27004                    .map(|range| lsp::Location {
27005                        uri: params.text_document_position.text_document.uri.clone(),
27006                        range,
27007                    })
27008                    .to_vec(),
27009            ))
27010        });
27011
27012    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27013        cx.update_editor(|editor, window, cx| {
27014            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27015        })
27016        .unwrap()
27017        .await
27018        .unwrap()
27019    };
27020
27021    _move(Direction::Next, 1, &mut cx).await;
27022    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27023
27024    _move(Direction::Next, 1, &mut cx).await;
27025    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27026
27027    _move(Direction::Next, 1, &mut cx).await;
27028    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27029
27030    // loops back to the start
27031    _move(Direction::Next, 1, &mut cx).await;
27032    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27033
27034    // loops back to the end
27035    _move(Direction::Prev, 1, &mut cx).await;
27036    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27037
27038    _move(Direction::Prev, 1, &mut cx).await;
27039    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27040
27041    _move(Direction::Prev, 1, &mut cx).await;
27042    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27043
27044    _move(Direction::Prev, 1, &mut cx).await;
27045    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27046
27047    _move(Direction::Next, 3, &mut cx).await;
27048    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27049
27050    _move(Direction::Prev, 2, &mut cx).await;
27051    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27052}