editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::build_editor_with_project;
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_item_view::InvalidItemView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(
  224            editor.selections.ranges(&editor.display_snapshot(cx)),
  225            vec![4..4]
  226        );
  227
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([4..5])
  231        });
  232        editor.insert("e", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![5..5]
  238        );
  239
  240        now += group_interval + Duration::from_millis(1);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([2..2])
  243        });
  244
  245        // Simulate an edit in another editor
  246        buffer.update(cx, |buffer, cx| {
  247            buffer.start_transaction_at(now, cx);
  248            buffer.edit([(0..1, "a")], None, cx);
  249            buffer.edit([(1..1, "b")], None, cx);
  250            buffer.end_transaction_at(now, cx);
  251        });
  252
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(
  255            editor.selections.ranges(&editor.display_snapshot(cx)),
  256            vec![3..3]
  257        );
  258
  259        // Last transaction happened past the group interval in a different editor.
  260        // Undo it individually and don't restore selections.
  261        editor.undo(&Undo, window, cx);
  262        assert_eq!(editor.text(cx), "12cde6");
  263        assert_eq!(
  264            editor.selections.ranges(&editor.display_snapshot(cx)),
  265            vec![2..2]
  266        );
  267
  268        // First two transactions happened within the group interval in this editor.
  269        // Undo them together and restore selections.
  270        editor.undo(&Undo, window, cx);
  271        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  272        assert_eq!(editor.text(cx), "123456");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![0..0]
  276        );
  277
  278        // Redo the first two transactions together.
  279        editor.redo(&Redo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![5..5]
  284        );
  285
  286        // Redo the last transaction on its own.
  287        editor.redo(&Redo, window, cx);
  288        assert_eq!(editor.text(cx), "ab2cde6");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![6..6]
  292        );
  293
  294        // Test empty transactions.
  295        editor.start_transaction_at(now, window, cx);
  296        editor.end_transaction_at(now, cx);
  297        editor.undo(&Undo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299    });
  300}
  301
  302#[gpui::test]
  303fn test_ime_composition(cx: &mut TestAppContext) {
  304    init_test(cx, |_| {});
  305
  306    let buffer = cx.new(|cx| {
  307        let mut buffer = language::Buffer::local("abcde", cx);
  308        // Ensure automatic grouping doesn't occur.
  309        buffer.set_group_interval(Duration::ZERO);
  310        buffer
  311    });
  312
  313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  314    cx.add_window(|window, cx| {
  315        let mut editor = build_editor(buffer.clone(), window, cx);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  320        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  321        assert_eq!(editor.text(cx), "äbcde");
  322        assert_eq!(
  323            editor.marked_text_ranges(cx),
  324            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  325        );
  326
  327        // Finalize IME composition.
  328        editor.replace_text_in_range(None, "ā", window, cx);
  329        assert_eq!(editor.text(cx), "ābcde");
  330        assert_eq!(editor.marked_text_ranges(cx), None);
  331
  332        // IME composition edits are grouped and are undone/redone at once.
  333        editor.undo(&Default::default(), window, cx);
  334        assert_eq!(editor.text(cx), "abcde");
  335        assert_eq!(editor.marked_text_ranges(cx), None);
  336        editor.redo(&Default::default(), window, cx);
  337        assert_eq!(editor.text(cx), "ābcde");
  338        assert_eq!(editor.marked_text_ranges(cx), None);
  339
  340        // Start a new IME composition.
  341        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  345        );
  346
  347        // Undoing during an IME composition cancels it.
  348        editor.undo(&Default::default(), window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  353        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  354        assert_eq!(editor.text(cx), "ābcdè");
  355        assert_eq!(
  356            editor.marked_text_ranges(cx),
  357            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  358        );
  359
  360        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  361        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  362        assert_eq!(editor.text(cx), "ābcdę");
  363        assert_eq!(editor.marked_text_ranges(cx), None);
  364
  365        // Start a new IME composition with multiple cursors.
  366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  367            s.select_ranges([
  368                OffsetUtf16(1)..OffsetUtf16(1),
  369                OffsetUtf16(3)..OffsetUtf16(3),
  370                OffsetUtf16(5)..OffsetUtf16(5),
  371            ])
  372        });
  373        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  374        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                OffsetUtf16(0)..OffsetUtf16(3),
  379                OffsetUtf16(4)..OffsetUtf16(7),
  380                OffsetUtf16(8)..OffsetUtf16(11)
  381            ])
  382        );
  383
  384        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  385        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  386        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  387        assert_eq!(
  388            editor.marked_text_ranges(cx),
  389            Some(vec![
  390                OffsetUtf16(1)..OffsetUtf16(2),
  391                OffsetUtf16(5)..OffsetUtf16(6),
  392                OffsetUtf16(9)..OffsetUtf16(10)
  393            ])
  394        );
  395
  396        // Finalize IME composition with multiple cursors.
  397        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  398        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  399        assert_eq!(editor.marked_text_ranges(cx), None);
  400
  401        editor
  402    });
  403}
  404
  405#[gpui::test]
  406fn test_selection_with_mouse(cx: &mut TestAppContext) {
  407    init_test(cx, |_| {});
  408
  409    let editor = cx.add_window(|window, cx| {
  410        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  411        build_editor(buffer, window, cx)
  412    });
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  416    });
  417    assert_eq!(
  418        editor
  419            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  420            .unwrap(),
  421        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  422    );
  423
  424    _ = editor.update(cx, |editor, window, cx| {
  425        editor.update_selection(
  426            DisplayPoint::new(DisplayRow(3), 3),
  427            0,
  428            gpui::Point::<f32>::default(),
  429            window,
  430            cx,
  431        );
  432    });
  433
  434    assert_eq!(
  435        editor
  436            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  437            .unwrap(),
  438        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  439    );
  440
  441    _ = editor.update(cx, |editor, window, cx| {
  442        editor.update_selection(
  443            DisplayPoint::new(DisplayRow(1), 1),
  444            0,
  445            gpui::Point::<f32>::default(),
  446            window,
  447            cx,
  448        );
  449    });
  450
  451    assert_eq!(
  452        editor
  453            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  454            .unwrap(),
  455        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460        editor.update_selection(
  461            DisplayPoint::new(DisplayRow(3), 3),
  462            0,
  463            gpui::Point::<f32>::default(),
  464            window,
  465            cx,
  466        );
  467    });
  468
  469    assert_eq!(
  470        editor
  471            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  472            .unwrap(),
  473        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  478        editor.update_selection(
  479            DisplayPoint::new(DisplayRow(0), 0),
  480            0,
  481            gpui::Point::<f32>::default(),
  482            window,
  483            cx,
  484        );
  485    });
  486
  487    assert_eq!(
  488        editor
  489            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  490            .unwrap(),
  491        [
  492            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  493            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  494        ]
  495    );
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.end_selection(window, cx);
  499    });
  500
  501    assert_eq!(
  502        editor
  503            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  504            .unwrap(),
  505        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  506    );
  507}
  508
  509#[gpui::test]
  510fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  511    init_test(cx, |_| {});
  512
  513    let editor = cx.add_window(|window, cx| {
  514        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  515        build_editor(buffer, window, cx)
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.end_selection(window, cx);
  532    });
  533
  534    assert_eq!(
  535        editor
  536            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  537            .unwrap(),
  538        [
  539            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  540            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  541        ]
  542    );
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    assert_eq!(
  553        editor
  554            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  555            .unwrap(),
  556        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  557    );
  558}
  559
  560#[gpui::test]
  561fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  562    init_test(cx, |_| {});
  563
  564    let editor = cx.add_window(|window, cx| {
  565        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  566        build_editor(buffer, window, cx)
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  574        );
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.update_selection(
  579            DisplayPoint::new(DisplayRow(3), 3),
  580            0,
  581            gpui::Point::<f32>::default(),
  582            window,
  583            cx,
  584        );
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  588        );
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.cancel(&Cancel, window, cx);
  593        editor.update_selection(
  594            DisplayPoint::new(DisplayRow(1), 1),
  595            0,
  596            gpui::Point::<f32>::default(),
  597            window,
  598            cx,
  599        );
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  603        );
  604    });
  605}
  606
  607#[gpui::test]
  608fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  609    init_test(cx, |_| {});
  610
  611    let editor = cx.add_window(|window, cx| {
  612        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  613        build_editor(buffer, window, cx)
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  618        assert_eq!(
  619            editor.selections.display_ranges(cx),
  620            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  621        );
  622
  623        editor.move_down(&Default::default(), window, cx);
  624        assert_eq!(
  625            editor.selections.display_ranges(cx),
  626            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  627        );
  628
  629        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  630        assert_eq!(
  631            editor.selections.display_ranges(cx),
  632            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  633        );
  634
  635        editor.move_up(&Default::default(), window, cx);
  636        assert_eq!(
  637            editor.selections.display_ranges(cx),
  638            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  639        );
  640    });
  641}
  642
  643#[gpui::test]
  644fn test_extending_selection(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    let editor = cx.add_window(|window, cx| {
  648        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  649        build_editor(buffer, window, cx)
  650    });
  651
  652    _ = editor.update(cx, |editor, window, cx| {
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  654        editor.end_selection(window, cx);
  655        assert_eq!(
  656            editor.selections.display_ranges(cx),
  657            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  658        );
  659
  660        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            editor.selections.display_ranges(cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  673        );
  674
  675        editor.update_selection(
  676            DisplayPoint::new(DisplayRow(0), 1),
  677            0,
  678            gpui::Point::<f32>::default(),
  679            window,
  680            cx,
  681        );
  682        editor.end_selection(window, cx);
  683        assert_eq!(
  684            editor.selections.display_ranges(cx),
  685            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  686        );
  687
  688        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  689        editor.end_selection(window, cx);
  690        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  691        editor.end_selection(window, cx);
  692        assert_eq!(
  693            editor.selections.display_ranges(cx),
  694            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  695        );
  696
  697        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  698        assert_eq!(
  699            editor.selections.display_ranges(cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  701        );
  702
  703        editor.update_selection(
  704            DisplayPoint::new(DisplayRow(0), 6),
  705            0,
  706            gpui::Point::<f32>::default(),
  707            window,
  708            cx,
  709        );
  710        assert_eq!(
  711            editor.selections.display_ranges(cx),
  712            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  713        );
  714
  715        editor.update_selection(
  716            DisplayPoint::new(DisplayRow(0), 1),
  717            0,
  718            gpui::Point::<f32>::default(),
  719            window,
  720            cx,
  721        );
  722        editor.end_selection(window, cx);
  723        assert_eq!(
  724            editor.selections.display_ranges(cx),
  725            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  726        );
  727    });
  728}
  729
  730#[gpui::test]
  731fn test_clone(cx: &mut TestAppContext) {
  732    init_test(cx, |_| {});
  733
  734    let (text, selection_ranges) = marked_text_ranges(
  735        indoc! {"
  736            one
  737            two
  738            threeˇ
  739            four
  740            fiveˇ
  741        "},
  742        true,
  743    );
  744
  745    let editor = cx.add_window(|window, cx| {
  746        let buffer = MultiBuffer::build_simple(&text, cx);
  747        build_editor(buffer, window, cx)
  748    });
  749
  750    _ = editor.update(cx, |editor, window, cx| {
  751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  752            s.select_ranges(selection_ranges.clone())
  753        });
  754        editor.fold_creases(
  755            vec![
  756                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  757                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  758            ],
  759            true,
  760            window,
  761            cx,
  762        );
  763    });
  764
  765    let cloned_editor = editor
  766        .update(cx, |editor, _, cx| {
  767            cx.open_window(Default::default(), |window, cx| {
  768                cx.new(|cx| editor.clone(window, cx))
  769            })
  770        })
  771        .unwrap()
  772        .unwrap();
  773
  774    let snapshot = editor
  775        .update(cx, |e, window, cx| e.snapshot(window, cx))
  776        .unwrap();
  777    let cloned_snapshot = cloned_editor
  778        .update(cx, |e, window, cx| e.snapshot(window, cx))
  779        .unwrap();
  780
  781    assert_eq!(
  782        cloned_editor
  783            .update(cx, |e, _, cx| e.display_text(cx))
  784            .unwrap(),
  785        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  786    );
  787    assert_eq!(
  788        cloned_snapshot
  789            .folds_in_range(0..text.len())
  790            .collect::<Vec<_>>(),
  791        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  792    );
  793    assert_set_eq!(
  794        cloned_editor
  795            .update(cx, |editor, _, cx| editor
  796                .selections
  797                .ranges::<Point>(&editor.display_snapshot(cx)))
  798            .unwrap(),
  799        editor
  800            .update(cx, |editor, _, cx| editor
  801                .selections
  802                .ranges(&editor.display_snapshot(cx)))
  803            .unwrap()
  804    );
  805    assert_set_eq!(
  806        cloned_editor
  807            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  808            .unwrap(),
  809        editor
  810            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  811            .unwrap()
  812    );
  813}
  814
  815#[gpui::test]
  816async fn test_navigation_history(cx: &mut TestAppContext) {
  817    init_test(cx, |_| {});
  818
  819    use workspace::item::Item;
  820
  821    let fs = FakeFs::new(cx.executor());
  822    let project = Project::test(fs, [], cx).await;
  823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  824    let pane = workspace
  825        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  826        .unwrap();
  827
  828    _ = workspace.update(cx, |_v, window, cx| {
  829        cx.new(|cx| {
  830            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  831            let mut editor = build_editor(buffer, window, cx);
  832            let handle = cx.entity();
  833            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  834
  835            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  836                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  837            }
  838
  839            // Move the cursor a small distance.
  840            // Nothing is added to the navigation history.
  841            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  842                s.select_display_ranges([
  843                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  844                ])
  845            });
  846            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  847                s.select_display_ranges([
  848                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  849                ])
  850            });
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance.
  854            // The history can jump back to the previous position.
  855            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  856                s.select_display_ranges([
  857                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  858                ])
  859            });
  860            let nav_entry = pop_history(&mut editor, cx).unwrap();
  861            editor.navigate(nav_entry.data.unwrap(), window, cx);
  862            assert_eq!(nav_entry.item.id(), cx.entity_id());
  863            assert_eq!(
  864                editor.selections.display_ranges(cx),
  865                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  866            );
  867            assert!(pop_history(&mut editor, cx).is_none());
  868
  869            // Move the cursor a small distance via the mouse.
  870            // Nothing is added to the navigation history.
  871            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  872            editor.end_selection(window, cx);
  873            assert_eq!(
  874                editor.selections.display_ranges(cx),
  875                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  876            );
  877            assert!(pop_history(&mut editor, cx).is_none());
  878
  879            // Move the cursor a large distance via the mouse.
  880            // The history can jump back to the previous position.
  881            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  882            editor.end_selection(window, cx);
  883            assert_eq!(
  884                editor.selections.display_ranges(cx),
  885                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  886            );
  887            let nav_entry = pop_history(&mut editor, cx).unwrap();
  888            editor.navigate(nav_entry.data.unwrap(), window, cx);
  889            assert_eq!(nav_entry.item.id(), cx.entity_id());
  890            assert_eq!(
  891                editor.selections.display_ranges(cx),
  892                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  893            );
  894            assert!(pop_history(&mut editor, cx).is_none());
  895
  896            // Set scroll position to check later
  897            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  898            let original_scroll_position = editor.scroll_manager.anchor();
  899
  900            // Jump to the end of the document and adjust scroll
  901            editor.move_to_end(&MoveToEnd, window, cx);
  902            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  903            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  904
  905            let nav_entry = pop_history(&mut editor, cx).unwrap();
  906            editor.navigate(nav_entry.data.unwrap(), window, cx);
  907            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  908
  909            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  910            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  911            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  912            let invalid_point = Point::new(9999, 0);
  913            editor.navigate(
  914                Box::new(NavigationData {
  915                    cursor_anchor: invalid_anchor,
  916                    cursor_position: invalid_point,
  917                    scroll_anchor: ScrollAnchor {
  918                        anchor: invalid_anchor,
  919                        offset: Default::default(),
  920                    },
  921                    scroll_top_row: invalid_point.row,
  922                }),
  923                window,
  924                cx,
  925            );
  926            assert_eq!(
  927                editor.selections.display_ranges(cx),
  928                &[editor.max_point(cx)..editor.max_point(cx)]
  929            );
  930            assert_eq!(
  931                editor.scroll_position(cx),
  932                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  933            );
  934
  935            editor
  936        })
  937    });
  938}
  939
  940#[gpui::test]
  941fn test_cancel(cx: &mut TestAppContext) {
  942    init_test(cx, |_| {});
  943
  944    let editor = cx.add_window(|window, cx| {
  945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  946        build_editor(buffer, window, cx)
  947    });
  948
  949    _ = editor.update(cx, |editor, window, cx| {
  950        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  951        editor.update_selection(
  952            DisplayPoint::new(DisplayRow(1), 1),
  953            0,
  954            gpui::Point::<f32>::default(),
  955            window,
  956            cx,
  957        );
  958        editor.end_selection(window, cx);
  959
  960        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  961        editor.update_selection(
  962            DisplayPoint::new(DisplayRow(0), 3),
  963            0,
  964            gpui::Point::<f32>::default(),
  965            window,
  966            cx,
  967        );
  968        editor.end_selection(window, cx);
  969        assert_eq!(
  970            editor.selections.display_ranges(cx),
  971            [
  972                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  973                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  974            ]
  975        );
  976    });
  977
  978    _ = editor.update(cx, |editor, window, cx| {
  979        editor.cancel(&Cancel, window, cx);
  980        assert_eq!(
  981            editor.selections.display_ranges(cx),
  982            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  983        );
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.cancel(&Cancel, window, cx);
  988        assert_eq!(
  989            editor.selections.display_ranges(cx),
  990            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_fold_action(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let editor = cx.add_window(|window, cx| {
 1000        let buffer = MultiBuffer::build_simple(
 1001            &"
 1002                impl Foo {
 1003                    // Hello!
 1004
 1005                    fn a() {
 1006                        1
 1007                    }
 1008
 1009                    fn b() {
 1010                        2
 1011                    }
 1012
 1013                    fn c() {
 1014                        3
 1015                    }
 1016                }
 1017            "
 1018            .unindent(),
 1019            cx,
 1020        );
 1021        build_editor(buffer, window, cx)
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1026            s.select_display_ranges([
 1027                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1028            ]);
 1029        });
 1030        editor.fold(&Fold, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                impl Foo {
 1035                    // Hello!
 1036
 1037                    fn a() {
 1038                        1
 1039                    }
 1040
 1041                    fn b() {⋯
 1042                    }
 1043
 1044                    fn c() {⋯
 1045                    }
 1046                }
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {⋯
 1056                }
 1057            "
 1058            .unindent(),
 1059        );
 1060
 1061        editor.unfold_lines(&UnfoldLines, window, cx);
 1062        assert_eq!(
 1063            editor.display_text(cx),
 1064            "
 1065                impl Foo {
 1066                    // Hello!
 1067
 1068                    fn a() {
 1069                        1
 1070                    }
 1071
 1072                    fn b() {⋯
 1073                    }
 1074
 1075                    fn c() {⋯
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            editor.buffer.read(cx).read(cx).text()
 1086        );
 1087    });
 1088}
 1089
 1090#[gpui::test]
 1091fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1092    init_test(cx, |_| {});
 1093
 1094    let editor = cx.add_window(|window, cx| {
 1095        let buffer = MultiBuffer::build_simple(
 1096            &"
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():
 1104                        print(2)
 1105
 1106                    def c():
 1107                        print(3)
 1108            "
 1109            .unindent(),
 1110            cx,
 1111        );
 1112        build_editor(buffer, window, cx)
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1117            s.select_display_ranges([
 1118                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1119            ]);
 1120        });
 1121        editor.fold(&Fold, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.fold(&Fold, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            "
 1142                class Foo:⋯
 1143            "
 1144            .unindent(),
 1145        );
 1146
 1147        editor.unfold_lines(&UnfoldLines, window, cx);
 1148        assert_eq!(
 1149            editor.display_text(cx),
 1150            "
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():⋯
 1158
 1159                    def c():⋯
 1160            "
 1161            .unindent(),
 1162        );
 1163
 1164        editor.unfold_lines(&UnfoldLines, window, cx);
 1165        assert_eq!(
 1166            editor.display_text(cx),
 1167            editor.buffer.read(cx).read(cx).text()
 1168        );
 1169    });
 1170}
 1171
 1172#[gpui::test]
 1173fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1174    init_test(cx, |_| {});
 1175
 1176    let editor = cx.add_window(|window, cx| {
 1177        let buffer = MultiBuffer::build_simple(
 1178            &"
 1179                class Foo:
 1180                    # Hello!
 1181
 1182                    def a():
 1183                        print(1)
 1184
 1185                    def b():
 1186                        print(2)
 1187
 1188
 1189                    def c():
 1190                        print(3)
 1191
 1192
 1193            "
 1194            .unindent(),
 1195            cx,
 1196        );
 1197        build_editor(buffer, window, cx)
 1198    });
 1199
 1200    _ = editor.update(cx, |editor, window, cx| {
 1201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1202            s.select_display_ranges([
 1203                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1204            ]);
 1205        });
 1206        editor.fold(&Fold, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218
 1219                    def c():⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:⋯
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        editor.unfold_lines(&UnfoldLines, window, cx);
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            "
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():⋯
 1248
 1249
 1250                    def c():⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            editor.buffer.read(cx).read(cx).text()
 1261        );
 1262    });
 1263}
 1264
 1265#[gpui::test]
 1266fn test_fold_at_level(cx: &mut TestAppContext) {
 1267    init_test(cx, |_| {});
 1268
 1269    let editor = cx.add_window(|window, cx| {
 1270        let buffer = MultiBuffer::build_simple(
 1271            &"
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():
 1279                        print(2)
 1280
 1281
 1282                class Bar:
 1283                    # World!
 1284
 1285                    def a():
 1286                        print(1)
 1287
 1288                    def b():
 1289                        print(2)
 1290
 1291
 1292            "
 1293            .unindent(),
 1294            cx,
 1295        );
 1296        build_editor(buffer, window, cx)
 1297    });
 1298
 1299    _ = editor.update(cx, |editor, window, cx| {
 1300        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1301        assert_eq!(
 1302            editor.display_text(cx),
 1303            "
 1304                class Foo:
 1305                    # Hello!
 1306
 1307                    def a():⋯
 1308
 1309                    def b():⋯
 1310
 1311
 1312                class Bar:
 1313                    # World!
 1314
 1315                    def a():⋯
 1316
 1317                    def b():⋯
 1318
 1319
 1320            "
 1321            .unindent(),
 1322        );
 1323
 1324        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1325        assert_eq!(
 1326            editor.display_text(cx),
 1327            "
 1328                class Foo:⋯
 1329
 1330
 1331                class Bar:⋯
 1332
 1333
 1334            "
 1335            .unindent(),
 1336        );
 1337
 1338        editor.unfold_all(&UnfoldAll, window, cx);
 1339        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1340        assert_eq!(
 1341            editor.display_text(cx),
 1342            "
 1343                class Foo:
 1344                    # Hello!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353                class Bar:
 1354                    # World!
 1355
 1356                    def a():
 1357                        print(1)
 1358
 1359                    def b():
 1360                        print(2)
 1361
 1362
 1363            "
 1364            .unindent(),
 1365        );
 1366
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            editor.buffer.read(cx).read(cx).text()
 1370        );
 1371        let (_, positions) = marked_text_ranges(
 1372            &"
 1373                       class Foo:
 1374                           # Hello!
 1375
 1376                           def a():
 1377                              print(1)
 1378
 1379                           def b():
 1380                               p«riˇ»nt(2)
 1381
 1382
 1383                       class Bar:
 1384                           # World!
 1385
 1386                           def a():
 1387                               «ˇprint(1)
 1388
 1389                           def b():
 1390                               print(2)»
 1391
 1392
 1393                   "
 1394            .unindent(),
 1395            true,
 1396        );
 1397
 1398        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1399            s.select_ranges(positions)
 1400        });
 1401
 1402        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1403        assert_eq!(
 1404            editor.display_text(cx),
 1405            "
 1406                class Foo:
 1407                    # Hello!
 1408
 1409                    def a():⋯
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428    });
 1429}
 1430
 1431#[gpui::test]
 1432fn test_move_cursor(cx: &mut TestAppContext) {
 1433    init_test(cx, |_| {});
 1434
 1435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1436    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1437
 1438    buffer.update(cx, |buffer, cx| {
 1439        buffer.edit(
 1440            vec![
 1441                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1442                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1443            ],
 1444            None,
 1445            cx,
 1446        );
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1452        );
 1453
 1454        editor.move_down(&MoveDown, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1458        );
 1459
 1460        editor.move_right(&MoveRight, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1464        );
 1465
 1466        editor.move_left(&MoveLeft, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1470        );
 1471
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1476        );
 1477
 1478        editor.move_to_end(&MoveToEnd, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1482        );
 1483
 1484        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1488        );
 1489
 1490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1491            s.select_display_ranges([
 1492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1493            ]);
 1494        });
 1495        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1499        );
 1500
 1501        editor.select_to_end(&SelectToEnd, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1505        );
 1506    });
 1507}
 1508
 1509#[gpui::test]
 1510fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1511    init_test(cx, |_| {});
 1512
 1513    let editor = cx.add_window(|window, cx| {
 1514        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1515        build_editor(buffer, window, cx)
 1516    });
 1517
 1518    assert_eq!('🟥'.len_utf8(), 4);
 1519    assert_eq!('α'.len_utf8(), 2);
 1520
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.fold_creases(
 1523            vec![
 1524                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1525                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1526                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1527            ],
 1528            true,
 1529            window,
 1530            cx,
 1531        );
 1532        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1533
 1534        editor.move_right(&MoveRight, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(0, "🟥".len())]
 1538        );
 1539        editor.move_right(&MoveRight, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(0, "🟥🟧".len())]
 1543        );
 1544        editor.move_right(&MoveRight, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[empty_range(0, "🟥🟧⋯".len())]
 1548        );
 1549
 1550        editor.move_down(&MoveDown, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(1, "ab⋯e".len())]
 1554        );
 1555        editor.move_left(&MoveLeft, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(1, "ab⋯".len())]
 1559        );
 1560        editor.move_left(&MoveLeft, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(1, "ab".len())]
 1564        );
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(1, "a".len())]
 1569        );
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "α".len())]
 1575        );
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(2, "αβ".len())]
 1580        );
 1581        editor.move_right(&MoveRight, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[empty_range(2, "αβ⋯".len())]
 1585        );
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[empty_range(2, "αβ⋯ε".len())]
 1590        );
 1591
 1592        editor.move_up(&MoveUp, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(1, "ab⋯e".len())]
 1596        );
 1597        editor.move_down(&MoveDown, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[empty_range(2, "αβ⋯ε".len())]
 1601        );
 1602        editor.move_up(&MoveUp, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[empty_range(1, "ab⋯e".len())]
 1606        );
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[empty_range(0, "🟥🟧".len())]
 1612        );
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[empty_range(0, "🟥".len())]
 1617        );
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(
 1620            editor.selections.display_ranges(cx),
 1621            &[empty_range(0, "".len())]
 1622        );
 1623    });
 1624}
 1625
 1626#[gpui::test]
 1627fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1628    init_test(cx, |_| {});
 1629
 1630    let editor = cx.add_window(|window, cx| {
 1631        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1632        build_editor(buffer, window, cx)
 1633    });
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1636            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1637        });
 1638
 1639        // moving above start of document should move selection to start of document,
 1640        // but the next move down should still be at the original goal_x
 1641        editor.move_up(&MoveUp, window, cx);
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[empty_range(0, "".len())]
 1645        );
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(1, "abcd".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(2, "αβγ".len())]
 1657        );
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(3, "abcd".len())]
 1663        );
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1669        );
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(
 1674            editor.selections.display_ranges(cx),
 1675            &[empty_range(5, "".len())]
 1676        );
 1677
 1678        editor.move_down(&MoveDown, window, cx);
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[empty_range(5, "".len())]
 1682        );
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(
 1686            editor.selections.display_ranges(cx),
 1687            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1688        );
 1689
 1690        editor.move_up(&MoveUp, window, cx);
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[empty_range(3, "abcd".len())]
 1694        );
 1695
 1696        editor.move_up(&MoveUp, window, cx);
 1697        assert_eq!(
 1698            editor.selections.display_ranges(cx),
 1699            &[empty_range(2, "αβγ".len())]
 1700        );
 1701    });
 1702}
 1703
 1704#[gpui::test]
 1705fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1706    init_test(cx, |_| {});
 1707    let move_to_beg = MoveToBeginningOfLine {
 1708        stop_at_soft_wraps: true,
 1709        stop_at_indent: true,
 1710    };
 1711
 1712    let delete_to_beg = DeleteToBeginningOfLine {
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: true,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1726            s.select_display_ranges([
 1727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1728                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1729            ]);
 1730        });
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1735        assert_eq!(
 1736            editor.selections.display_ranges(cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1740            ]
 1741        );
 1742    });
 1743
 1744    _ = editor.update(cx, |editor, window, cx| {
 1745        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1746        assert_eq!(
 1747            editor.selections.display_ranges(cx),
 1748            &[
 1749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1751            ]
 1752        );
 1753    });
 1754
 1755    _ = editor.update(cx, |editor, window, cx| {
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            editor.selections.display_ranges(cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.move_to_end_of_line(&move_to_end, window, cx);
 1768        assert_eq!(
 1769            editor.selections.display_ranges(cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1772                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1773            ]
 1774        );
 1775    });
 1776
 1777    // Moving to the end of line again is a no-op.
 1778    _ = editor.update(cx, |editor, window, cx| {
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            editor.selections.display_ranges(cx),
 1782            &[
 1783                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1784                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1785            ]
 1786        );
 1787    });
 1788
 1789    _ = editor.update(cx, |editor, window, cx| {
 1790        editor.move_left(&MoveLeft, window, cx);
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            editor.selections.display_ranges(cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            editor.selections.display_ranges(cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_beginning_of_line(
 1828            &SelectToBeginningOfLine {
 1829                stop_at_soft_wraps: true,
 1830                stop_at_indent: true,
 1831            },
 1832            window,
 1833            cx,
 1834        );
 1835        assert_eq!(
 1836            editor.selections.display_ranges(cx),
 1837            &[
 1838                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1839                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1840            ]
 1841        );
 1842    });
 1843
 1844    _ = editor.update(cx, |editor, window, cx| {
 1845        editor.select_to_end_of_line(
 1846            &SelectToEndOfLine {
 1847                stop_at_soft_wraps: true,
 1848            },
 1849            window,
 1850            cx,
 1851        );
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1856                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1857            ]
 1858        );
 1859    });
 1860
 1861    _ = editor.update(cx, |editor, window, cx| {
 1862        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1863        assert_eq!(editor.display_text(cx), "ab\n  de");
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1869            ]
 1870        );
 1871    });
 1872
 1873    _ = editor.update(cx, |editor, window, cx| {
 1874        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1875        assert_eq!(editor.display_text(cx), "\n");
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889    let move_to_beg = MoveToBeginningOfLine {
 1890        stop_at_soft_wraps: false,
 1891        stop_at_indent: false,
 1892    };
 1893
 1894    let move_to_end = MoveToEndOfLine {
 1895        stop_at_soft_wraps: false,
 1896    };
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.set_wrap_width(Some(140.0.into()), cx);
 1905
 1906        // We expect the following lines after wrapping
 1907        // ```
 1908        // thequickbrownfox
 1909        // jumpedoverthelazydo
 1910        // gs
 1911        // ```
 1912        // The final `gs` was soft-wrapped onto a new line.
 1913        assert_eq!(
 1914            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1915            editor.display_text(cx),
 1916        );
 1917
 1918        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1919        // Start the cursor at the `k` on the first line
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1923            ]);
 1924        });
 1925
 1926        // Moving to the beginning of the line should put us at the beginning of the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1930            editor.selections.display_ranges(cx)
 1931        );
 1932
 1933        // Moving to the end of the line should put us at the end of the line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1937            editor.selections.display_ranges(cx)
 1938        );
 1939
 1940        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1941        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1943            s.select_display_ranges([
 1944                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1945            ]);
 1946        });
 1947
 1948        // Moving to the beginning of the line should put us at the start of the second line of
 1949        // display text, i.e., the `j`.
 1950        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1951        assert_eq!(
 1952            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1953            editor.selections.display_ranges(cx)
 1954        );
 1955
 1956        // Moving to the beginning of the line again should be a no-op.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1960            editor.selections.display_ranges(cx)
 1961        );
 1962
 1963        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1964        // next display line.
 1965        editor.move_to_end_of_line(&move_to_end, window, cx);
 1966        assert_eq!(
 1967            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1968            editor.selections.display_ranges(cx)
 1969        );
 1970
 1971        // Moving to the end of the line again should be a no-op.
 1972        editor.move_to_end_of_line(&move_to_end, window, cx);
 1973        assert_eq!(
 1974            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1975            editor.selections.display_ranges(cx)
 1976        );
 1977    });
 1978}
 1979
 1980#[gpui::test]
 1981fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1982    init_test(cx, |_| {});
 1983
 1984    let move_to_beg = MoveToBeginningOfLine {
 1985        stop_at_soft_wraps: true,
 1986        stop_at_indent: true,
 1987    };
 1988
 1989    let select_to_beg = SelectToBeginningOfLine {
 1990        stop_at_soft_wraps: true,
 1991        stop_at_indent: true,
 1992    };
 1993
 1994    let delete_to_beg = DeleteToBeginningOfLine {
 1995        stop_at_indent: true,
 1996    };
 1997
 1998    let move_to_end = MoveToEndOfLine {
 1999        stop_at_soft_wraps: false,
 2000    };
 2001
 2002    let editor = cx.add_window(|window, cx| {
 2003        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2004        build_editor(buffer, window, cx)
 2005    });
 2006
 2007    _ = editor.update(cx, |editor, window, cx| {
 2008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2009            s.select_display_ranges([
 2010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2011                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2012            ]);
 2013        });
 2014
 2015        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2016        // and the second cursor at the first non-whitespace character in the line.
 2017        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2018        assert_eq!(
 2019            editor.selections.display_ranges(cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should move the second cursor to the beginning of the line.
 2028        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2029        assert_eq!(
 2030            editor.selections.display_ranges(cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2038        // and should move the second cursor back to the first non-whitespace character in the line.
 2039        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2040        assert_eq!(
 2041            editor.selections.display_ranges(cx),
 2042            &[
 2043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2044                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2045            ]
 2046        );
 2047
 2048        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2049        // and to the first non-whitespace character in the line for the second cursor.
 2050        editor.move_to_end_of_line(&move_to_end, window, cx);
 2051        editor.move_left(&MoveLeft, window, cx);
 2052        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should select to the beginning of the line for the second cursor.
 2063        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2064        assert_eq!(
 2065            editor.selections.display_ranges(cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2073        // and should delete to the first non-whitespace character in the line for the second cursor.
 2074        editor.move_to_end_of_line(&move_to_end, window, cx);
 2075        editor.move_left(&MoveLeft, window, cx);
 2076        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2077        assert_eq!(editor.text(cx), "c\n  f");
 2078    });
 2079}
 2080
 2081#[gpui::test]
 2082fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2083    init_test(cx, |_| {});
 2084
 2085    let move_to_beg = MoveToBeginningOfLine {
 2086        stop_at_soft_wraps: true,
 2087        stop_at_indent: true,
 2088    };
 2089
 2090    let editor = cx.add_window(|window, cx| {
 2091        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2092        build_editor(buffer, window, cx)
 2093    });
 2094
 2095    _ = editor.update(cx, |editor, window, cx| {
 2096        // test cursor between line_start and indent_start
 2097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2098            s.select_display_ranges([
 2099                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2100            ]);
 2101        });
 2102
 2103        // cursor should move to line_start
 2104        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2108        );
 2109
 2110        // cursor should move to indent_start
 2111        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2115        );
 2116
 2117        // cursor should move to back to line_start
 2118        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2119        assert_eq!(
 2120            editor.selections.display_ranges(cx),
 2121            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2122        );
 2123    });
 2124}
 2125
 2126#[gpui::test]
 2127fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2128    init_test(cx, |_| {});
 2129
 2130    let editor = cx.add_window(|window, cx| {
 2131        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2132        build_editor(buffer, window, cx)
 2133    });
 2134    _ = editor.update(cx, |editor, window, cx| {
 2135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2136            s.select_display_ranges([
 2137                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2138                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2139            ])
 2140        });
 2141        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2142        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2143
 2144        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2145        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2148        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2149
 2150        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2151        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2152
 2153        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2154        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2158
 2159        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2160        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2164
 2165        editor.move_right(&MoveRight, window, cx);
 2166        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2167        assert_selection_ranges(
 2168            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2169            editor,
 2170            cx,
 2171        );
 2172
 2173        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2174        assert_selection_ranges(
 2175            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2176            editor,
 2177            cx,
 2178        );
 2179
 2180        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2181        assert_selection_ranges(
 2182            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2183            editor,
 2184            cx,
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192
 2193    let editor = cx.add_window(|window, cx| {
 2194        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2195        build_editor(buffer, window, cx)
 2196    });
 2197
 2198    _ = editor.update(cx, |editor, window, cx| {
 2199        editor.set_wrap_width(Some(140.0.into()), cx);
 2200        assert_eq!(
 2201            editor.display_text(cx),
 2202            "use one::{\n    two::three::\n    four::five\n};"
 2203        );
 2204
 2205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2206            s.select_display_ranges([
 2207                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2208            ]);
 2209        });
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            editor.selections.display_ranges(cx),
 2214            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2215        );
 2216
 2217        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2218        assert_eq!(
 2219            editor.selections.display_ranges(cx),
 2220            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2221        );
 2222
 2223        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2224        assert_eq!(
 2225            editor.selections.display_ranges(cx),
 2226            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2227        );
 2228
 2229        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2230        assert_eq!(
 2231            editor.selections.display_ranges(cx),
 2232            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2233        );
 2234
 2235        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2236        assert_eq!(
 2237            editor.selections.display_ranges(cx),
 2238            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2239        );
 2240
 2241        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2242        assert_eq!(
 2243            editor.selections.display_ranges(cx),
 2244            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2245        );
 2246    });
 2247}
 2248
 2249#[gpui::test]
 2250async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2251    init_test(cx, |_| {});
 2252    let mut cx = EditorTestContext::new(cx).await;
 2253
 2254    let line_height = cx.editor(|editor, window, _| {
 2255        editor
 2256            .style()
 2257            .unwrap()
 2258            .text
 2259            .line_height_in_pixels(window.rem_size())
 2260    });
 2261    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2262
 2263    cx.set_state(
 2264        &r#"ˇone
 2265        two
 2266
 2267        three
 2268        fourˇ
 2269        five
 2270
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285        ˇ
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"one
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300        ˇ
 2301        sixˇ"#
 2302            .unindent(),
 2303    );
 2304
 2305    cx.update_editor(|editor, window, cx| {
 2306        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2307    });
 2308    cx.assert_editor_state(
 2309        &r#"one
 2310        two
 2311
 2312        three
 2313        four
 2314        five
 2315
 2316        sixˇ"#
 2317            .unindent(),
 2318    );
 2319
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2322    });
 2323    cx.assert_editor_state(
 2324        &r#"one
 2325        two
 2326
 2327        three
 2328        four
 2329        five
 2330        ˇ
 2331        six"#
 2332            .unindent(),
 2333    );
 2334
 2335    cx.update_editor(|editor, window, cx| {
 2336        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2337    });
 2338    cx.assert_editor_state(
 2339        &r#"one
 2340        two
 2341        ˇ
 2342        three
 2343        four
 2344        five
 2345
 2346        six"#
 2347            .unindent(),
 2348    );
 2349
 2350    cx.update_editor(|editor, window, cx| {
 2351        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2352    });
 2353    cx.assert_editor_state(
 2354        &r#"ˇone
 2355        two
 2356
 2357        three
 2358        four
 2359        five
 2360
 2361        six"#
 2362            .unindent(),
 2363    );
 2364}
 2365
 2366#[gpui::test]
 2367async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2368    init_test(cx, |_| {});
 2369    let mut cx = EditorTestContext::new(cx).await;
 2370    let line_height = cx.editor(|editor, window, _| {
 2371        editor
 2372            .style()
 2373            .unwrap()
 2374            .text
 2375            .line_height_in_pixels(window.rem_size())
 2376    });
 2377    let window = cx.window;
 2378    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2379
 2380    cx.set_state(
 2381        r#"ˇone
 2382        two
 2383        three
 2384        four
 2385        five
 2386        six
 2387        seven
 2388        eight
 2389        nine
 2390        ten
 2391        "#,
 2392    );
 2393
 2394    cx.update_editor(|editor, window, cx| {
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 0.)
 2398        );
 2399        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2400        assert_eq!(
 2401            editor.snapshot(window, cx).scroll_position(),
 2402            gpui::Point::new(0., 3.)
 2403        );
 2404        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2405        assert_eq!(
 2406            editor.snapshot(window, cx).scroll_position(),
 2407            gpui::Point::new(0., 6.)
 2408        );
 2409        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2410        assert_eq!(
 2411            editor.snapshot(window, cx).scroll_position(),
 2412            gpui::Point::new(0., 3.)
 2413        );
 2414
 2415        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 1.)
 2419        );
 2420        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2421        assert_eq!(
 2422            editor.snapshot(window, cx).scroll_position(),
 2423            gpui::Point::new(0., 3.)
 2424        );
 2425    });
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_autoscroll(cx: &mut TestAppContext) {
 2430    init_test(cx, |_| {});
 2431    let mut cx = EditorTestContext::new(cx).await;
 2432
 2433    let line_height = cx.update_editor(|editor, window, cx| {
 2434        editor.set_vertical_scroll_margin(2, cx);
 2435        editor
 2436            .style()
 2437            .unwrap()
 2438            .text
 2439            .line_height_in_pixels(window.rem_size())
 2440    });
 2441    let window = cx.window;
 2442    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2443
 2444    cx.set_state(
 2445        r#"ˇone
 2446            two
 2447            three
 2448            four
 2449            five
 2450            six
 2451            seven
 2452            eight
 2453            nine
 2454            ten
 2455        "#,
 2456    );
 2457    cx.update_editor(|editor, window, cx| {
 2458        assert_eq!(
 2459            editor.snapshot(window, cx).scroll_position(),
 2460            gpui::Point::new(0., 0.0)
 2461        );
 2462    });
 2463
 2464    // Add a cursor below the visible area. Since both cursors cannot fit
 2465    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2466    // allows the vertical scroll margin below that cursor.
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.change_selections(Default::default(), window, cx, |selections| {
 2469            selections.select_ranges([
 2470                Point::new(0, 0)..Point::new(0, 0),
 2471                Point::new(6, 0)..Point::new(6, 0),
 2472            ]);
 2473        })
 2474    });
 2475    cx.update_editor(|editor, window, cx| {
 2476        assert_eq!(
 2477            editor.snapshot(window, cx).scroll_position(),
 2478            gpui::Point::new(0., 3.0)
 2479        );
 2480    });
 2481
 2482    // Move down. The editor cursor scrolls down to track the newest cursor.
 2483    cx.update_editor(|editor, window, cx| {
 2484        editor.move_down(&Default::default(), window, cx);
 2485    });
 2486    cx.update_editor(|editor, window, cx| {
 2487        assert_eq!(
 2488            editor.snapshot(window, cx).scroll_position(),
 2489            gpui::Point::new(0., 4.0)
 2490        );
 2491    });
 2492
 2493    // Add a cursor above the visible area. Since both cursors fit on screen,
 2494    // the editor scrolls to show both.
 2495    cx.update_editor(|editor, window, cx| {
 2496        editor.change_selections(Default::default(), window, cx, |selections| {
 2497            selections.select_ranges([
 2498                Point::new(1, 0)..Point::new(1, 0),
 2499                Point::new(6, 0)..Point::new(6, 0),
 2500            ]);
 2501        })
 2502    });
 2503    cx.update_editor(|editor, window, cx| {
 2504        assert_eq!(
 2505            editor.snapshot(window, cx).scroll_position(),
 2506            gpui::Point::new(0., 1.0)
 2507        );
 2508    });
 2509}
 2510
 2511#[gpui::test]
 2512async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2513    init_test(cx, |_| {});
 2514    let mut cx = EditorTestContext::new(cx).await;
 2515
 2516    let line_height = cx.editor(|editor, window, _cx| {
 2517        editor
 2518            .style()
 2519            .unwrap()
 2520            .text
 2521            .line_height_in_pixels(window.rem_size())
 2522    });
 2523    let window = cx.window;
 2524    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2525    cx.set_state(
 2526        &r#"
 2527        ˇone
 2528        two
 2529        threeˇ
 2530        four
 2531        five
 2532        six
 2533        seven
 2534        eight
 2535        nine
 2536        ten
 2537        "#
 2538        .unindent(),
 2539    );
 2540
 2541    cx.update_editor(|editor, window, cx| {
 2542        editor.move_page_down(&MovePageDown::default(), window, cx)
 2543    });
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| {
 2561        editor.move_page_down(&MovePageDown::default(), window, cx)
 2562    });
 2563    cx.assert_editor_state(
 2564        &r#"
 2565        one
 2566        two
 2567        three
 2568        four
 2569        five
 2570        six
 2571        ˇseven
 2572        eight
 2573        nineˇ
 2574        ten
 2575        "#
 2576        .unindent(),
 2577    );
 2578
 2579    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2580    cx.assert_editor_state(
 2581        &r#"
 2582        one
 2583        two
 2584        three
 2585        ˇfour
 2586        five
 2587        sixˇ
 2588        seven
 2589        eight
 2590        nine
 2591        ten
 2592        "#
 2593        .unindent(),
 2594    );
 2595
 2596    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        ˇone
 2600        two
 2601        threeˇ
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ten
 2609        "#
 2610        .unindent(),
 2611    );
 2612
 2613    // Test select collapsing
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.move_page_down(&MovePageDown::default(), window, cx);
 2616        editor.move_page_down(&MovePageDown::default(), window, cx);
 2617        editor.move_page_down(&MovePageDown::default(), window, cx);
 2618    });
 2619    cx.assert_editor_state(
 2620        &r#"
 2621        one
 2622        two
 2623        three
 2624        four
 2625        five
 2626        six
 2627        seven
 2628        eight
 2629        nine
 2630        ˇten
 2631        ˇ"#
 2632        .unindent(),
 2633    );
 2634}
 2635
 2636#[gpui::test]
 2637async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2638    init_test(cx, |_| {});
 2639    let mut cx = EditorTestContext::new(cx).await;
 2640    cx.set_state("one «two threeˇ» four");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_beginning_of_line(
 2643            &DeleteToBeginningOfLine {
 2644                stop_at_indent: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649        assert_eq!(editor.text(cx), " four");
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    // For an empty selection, the preceding word fragment is deleted.
 2660    // For non-empty selections, only selected characters are deleted.
 2661    cx.set_state("onˇe two t«hreˇ»e four");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: false,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    cx.assert_editor_state("ˇe two tˇe four");
 2673
 2674    cx.set_state("e tˇwo te «fˇ»our");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("e tˇ te ˇour");
 2686}
 2687
 2688#[gpui::test]
 2689async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2690    init_test(cx, |_| {});
 2691
 2692    let mut cx = EditorTestContext::new(cx).await;
 2693
 2694    cx.set_state("here is some text    ˇwith a space");
 2695    cx.update_editor(|editor, window, cx| {
 2696        editor.delete_to_previous_word_start(
 2697            &DeleteToPreviousWordStart {
 2698                ignore_newlines: false,
 2699                ignore_brackets: true,
 2700            },
 2701            window,
 2702            cx,
 2703        );
 2704    });
 2705    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2706    cx.assert_editor_state("here is some textˇwith a space");
 2707
 2708    cx.set_state("here is some text    ˇwith a space");
 2709    cx.update_editor(|editor, window, cx| {
 2710        editor.delete_to_previous_word_start(
 2711            &DeleteToPreviousWordStart {
 2712                ignore_newlines: false,
 2713                ignore_brackets: false,
 2714            },
 2715            window,
 2716            cx,
 2717        );
 2718    });
 2719    cx.assert_editor_state("here is some textˇwith a space");
 2720
 2721    cx.set_state("here is some textˇ    with a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_next_word_end(
 2724            &DeleteToNextWordEnd {
 2725                ignore_newlines: false,
 2726                ignore_brackets: true,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    // Same happens in the other direction.
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734
 2735    cx.set_state("here is some textˇ    with a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_next_word_end(
 2738            &DeleteToNextWordEnd {
 2739                ignore_newlines: false,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    cx.assert_editor_state("here is some textˇwith a space");
 2747
 2748    cx.set_state("here is some textˇ    with a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_next_word_end(
 2751            &DeleteToNextWordEnd {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("here is some textˇwith a space");
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    cx.assert_editor_state("here is some ˇwith a space");
 2771    cx.update_editor(|editor, window, cx| {
 2772        editor.delete_to_previous_word_start(
 2773            &DeleteToPreviousWordStart {
 2774                ignore_newlines: true,
 2775                ignore_brackets: false,
 2776            },
 2777            window,
 2778            cx,
 2779        );
 2780    });
 2781    // Single whitespaces are removed with the word behind them.
 2782    cx.assert_editor_state("here is ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("here ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_previous_word_start(
 2796            &DeleteToPreviousWordStart {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    cx.assert_editor_state("ˇwith a space");
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    cx.assert_editor_state("ˇwith a space");
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Same happens in the other direction.
 2827    cx.assert_editor_state("ˇ a space");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ space");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_next_word_end(
 2841            &DeleteToNextWordEnd {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850    cx.update_editor(|editor, window, cx| {
 2851        editor.delete_to_next_word_end(
 2852            &DeleteToNextWordEnd {
 2853                ignore_newlines: true,
 2854                ignore_brackets: false,
 2855            },
 2856            window,
 2857            cx,
 2858        );
 2859    });
 2860    cx.assert_editor_state("ˇ");
 2861    cx.update_editor(|editor, window, cx| {
 2862        editor.delete_to_previous_word_start(
 2863            &DeleteToPreviousWordStart {
 2864                ignore_newlines: true,
 2865                ignore_brackets: false,
 2866            },
 2867            window,
 2868            cx,
 2869        );
 2870    });
 2871    cx.assert_editor_state("ˇ");
 2872}
 2873
 2874#[gpui::test]
 2875async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2876    init_test(cx, |_| {});
 2877
 2878    let language = Arc::new(
 2879        Language::new(
 2880            LanguageConfig {
 2881                brackets: BracketPairConfig {
 2882                    pairs: vec![
 2883                        BracketPair {
 2884                            start: "\"".to_string(),
 2885                            end: "\"".to_string(),
 2886                            close: true,
 2887                            surround: true,
 2888                            newline: false,
 2889                        },
 2890                        BracketPair {
 2891                            start: "(".to_string(),
 2892                            end: ")".to_string(),
 2893                            close: true,
 2894                            surround: true,
 2895                            newline: true,
 2896                        },
 2897                    ],
 2898                    ..BracketPairConfig::default()
 2899                },
 2900                ..LanguageConfig::default()
 2901            },
 2902            Some(tree_sitter_rust::LANGUAGE.into()),
 2903        )
 2904        .with_brackets_query(
 2905            r#"
 2906                ("(" @open ")" @close)
 2907                ("\"" @open "\"" @close)
 2908            "#,
 2909        )
 2910        .unwrap(),
 2911    );
 2912
 2913    let mut cx = EditorTestContext::new(cx).await;
 2914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2915
 2916    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Deletion stops before brackets if asked to not ignore them.
 2928    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    // Deletion has to remove a single bracket and then stop again.
 2940    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_previous_word_start(
 2944            &DeleteToPreviousWordStart {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_previous_word_start(
 2956            &DeleteToPreviousWordStart {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_previous_word_start(
 2968            &DeleteToPreviousWordStart {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2977
 2978    cx.update_editor(|editor, window, cx| {
 2979        editor.delete_to_next_word_end(
 2980            &DeleteToNextWordEnd {
 2981                ignore_newlines: true,
 2982                ignore_brackets: false,
 2983            },
 2984            window,
 2985            cx,
 2986        );
 2987    });
 2988    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2989    cx.assert_editor_state(r#"ˇ");"#);
 2990
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_next_word_end(
 2993            &DeleteToNextWordEnd {
 2994                ignore_newlines: true,
 2995                ignore_brackets: false,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"ˇ"#);
 3002
 3003    cx.update_editor(|editor, window, cx| {
 3004        editor.delete_to_next_word_end(
 3005            &DeleteToNextWordEnd {
 3006                ignore_newlines: true,
 3007                ignore_brackets: false,
 3008            },
 3009            window,
 3010            cx,
 3011        );
 3012    });
 3013    cx.assert_editor_state(r#"ˇ"#);
 3014
 3015    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3016    cx.update_editor(|editor, window, cx| {
 3017        editor.delete_to_previous_word_start(
 3018            &DeleteToPreviousWordStart {
 3019                ignore_newlines: true,
 3020                ignore_brackets: true,
 3021            },
 3022            window,
 3023            cx,
 3024        );
 3025    });
 3026    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3027}
 3028
 3029#[gpui::test]
 3030fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    let editor = cx.add_window(|window, cx| {
 3034        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3035        build_editor(buffer, window, cx)
 3036    });
 3037    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3038        ignore_newlines: false,
 3039        ignore_brackets: false,
 3040    };
 3041    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3042        ignore_newlines: true,
 3043        ignore_brackets: false,
 3044    };
 3045
 3046    _ = editor.update(cx, |editor, window, cx| {
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_display_ranges([
 3049                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3050            ])
 3051        });
 3052        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3053        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3054        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3056        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3058        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3060        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3062        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3064    });
 3065}
 3066
 3067#[gpui::test]
 3068fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3069    init_test(cx, |_| {});
 3070
 3071    let editor = cx.add_window(|window, cx| {
 3072        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3073        build_editor(buffer, window, cx)
 3074    });
 3075    let del_to_next_word_end = DeleteToNextWordEnd {
 3076        ignore_newlines: false,
 3077        ignore_brackets: false,
 3078    };
 3079    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3080        ignore_newlines: true,
 3081        ignore_brackets: false,
 3082    };
 3083
 3084    _ = editor.update(cx, |editor, window, cx| {
 3085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3086            s.select_display_ranges([
 3087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3088            ])
 3089        });
 3090        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3091        assert_eq!(
 3092            editor.buffer.read(cx).read(cx).text(),
 3093            "one\n   two\nthree\n   four"
 3094        );
 3095        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3096        assert_eq!(
 3097            editor.buffer.read(cx).read(cx).text(),
 3098            "\n   two\nthree\n   four"
 3099        );
 3100        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3101        assert_eq!(
 3102            editor.buffer.read(cx).read(cx).text(),
 3103            "two\nthree\n   four"
 3104        );
 3105        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3107        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3108        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3109        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3110        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3111        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let editor = cx.add_window(|window, cx| {
 3144        let buffer = MultiBuffer::build_simple(
 3145            "
 3146                a
 3147                b(
 3148                    X
 3149                )
 3150                c(
 3151                    X
 3152                )
 3153            "
 3154            .unindent()
 3155            .as_str(),
 3156            cx,
 3157        );
 3158        let mut editor = build_editor(buffer, window, cx);
 3159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3160            s.select_ranges([
 3161                Point::new(2, 4)..Point::new(2, 5),
 3162                Point::new(5, 4)..Point::new(5, 5),
 3163            ])
 3164        });
 3165        editor
 3166    });
 3167
 3168    _ = editor.update(cx, |editor, window, cx| {
 3169        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3170        editor.buffer.update(cx, |buffer, cx| {
 3171            buffer.edit(
 3172                [
 3173                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3174                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3175                ],
 3176                None,
 3177                cx,
 3178            );
 3179            assert_eq!(
 3180                buffer.read(cx).text(),
 3181                "
 3182                    a
 3183                    b()
 3184                    c()
 3185                "
 3186                .unindent()
 3187            );
 3188        });
 3189        assert_eq!(
 3190            editor.selections.ranges(&editor.display_snapshot(cx)),
 3191            &[
 3192                Point::new(1, 2)..Point::new(1, 2),
 3193                Point::new(2, 2)..Point::new(2, 2),
 3194            ],
 3195        );
 3196
 3197        editor.newline(&Newline, window, cx);
 3198        assert_eq!(
 3199            editor.text(cx),
 3200            "
 3201                a
 3202                b(
 3203                )
 3204                c(
 3205                )
 3206            "
 3207            .unindent()
 3208        );
 3209
 3210        // The selections are moved after the inserted newlines
 3211        assert_eq!(
 3212            editor.selections.ranges(&editor.display_snapshot(cx)),
 3213            &[
 3214                Point::new(2, 0)..Point::new(2, 0),
 3215                Point::new(4, 0)..Point::new(4, 0),
 3216            ],
 3217        );
 3218    });
 3219}
 3220
 3221#[gpui::test]
 3222async fn test_newline_above(cx: &mut TestAppContext) {
 3223    init_test(cx, |settings| {
 3224        settings.defaults.tab_size = NonZeroU32::new(4)
 3225    });
 3226
 3227    let language = Arc::new(
 3228        Language::new(
 3229            LanguageConfig::default(),
 3230            Some(tree_sitter_rust::LANGUAGE.into()),
 3231        )
 3232        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3233        .unwrap(),
 3234    );
 3235
 3236    let mut cx = EditorTestContext::new(cx).await;
 3237    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3238    cx.set_state(indoc! {"
 3239        const a: ˇA = (
 3240 3241                «const_functionˇ»(ˇ),
 3242                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3243 3244        ˇ);ˇ
 3245    "});
 3246
 3247    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3248    cx.assert_editor_state(indoc! {"
 3249        ˇ
 3250        const a: A = (
 3251            ˇ
 3252            (
 3253                ˇ
 3254                ˇ
 3255                const_function(),
 3256                ˇ
 3257                ˇ
 3258                ˇ
 3259                ˇ
 3260                something_else,
 3261                ˇ
 3262            )
 3263            ˇ
 3264            ˇ
 3265        );
 3266    "});
 3267}
 3268
 3269#[gpui::test]
 3270async fn test_newline_below(cx: &mut TestAppContext) {
 3271    init_test(cx, |settings| {
 3272        settings.defaults.tab_size = NonZeroU32::new(4)
 3273    });
 3274
 3275    let language = Arc::new(
 3276        Language::new(
 3277            LanguageConfig::default(),
 3278            Some(tree_sitter_rust::LANGUAGE.into()),
 3279        )
 3280        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3281        .unwrap(),
 3282    );
 3283
 3284    let mut cx = EditorTestContext::new(cx).await;
 3285    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3286    cx.set_state(indoc! {"
 3287        const a: ˇA = (
 3288 3289                «const_functionˇ»(ˇ),
 3290                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3291 3292        ˇ);ˇ
 3293    "});
 3294
 3295    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: A = (
 3298            ˇ
 3299            (
 3300                ˇ
 3301                const_function(),
 3302                ˇ
 3303                ˇ
 3304                something_else,
 3305                ˇ
 3306                ˇ
 3307                ˇ
 3308                ˇ
 3309            )
 3310            ˇ
 3311        );
 3312        ˇ
 3313        ˇ
 3314    "});
 3315}
 3316
 3317#[gpui::test]
 3318async fn test_newline_comments(cx: &mut TestAppContext) {
 3319    init_test(cx, |settings| {
 3320        settings.defaults.tab_size = NonZeroU32::new(4)
 3321    });
 3322
 3323    let language = Arc::new(Language::new(
 3324        LanguageConfig {
 3325            line_comments: vec!["// ".into()],
 3326            ..LanguageConfig::default()
 3327        },
 3328        None,
 3329    ));
 3330    {
 3331        let mut cx = EditorTestContext::new(cx).await;
 3332        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3333        cx.set_state(indoc! {"
 3334        // Fooˇ
 3335    "});
 3336
 3337        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3338        cx.assert_editor_state(indoc! {"
 3339        // Foo
 3340        // ˇ
 3341    "});
 3342        // Ensure that we add comment prefix when existing line contains space
 3343        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3344        cx.assert_editor_state(
 3345            indoc! {"
 3346        // Foo
 3347        //s
 3348        // ˇ
 3349    "}
 3350            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3351            .as_str(),
 3352        );
 3353        // Ensure that we add comment prefix when existing line does not contain space
 3354        cx.set_state(indoc! {"
 3355        // Foo
 3356        //ˇ
 3357    "});
 3358        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3359        cx.assert_editor_state(indoc! {"
 3360        // Foo
 3361        //
 3362        // ˇ
 3363    "});
 3364        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3365        cx.set_state(indoc! {"
 3366        ˇ// Foo
 3367    "});
 3368        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3369        cx.assert_editor_state(indoc! {"
 3370
 3371        ˇ// Foo
 3372    "});
 3373    }
 3374    // Ensure that comment continuations can be disabled.
 3375    update_test_language_settings(cx, |settings| {
 3376        settings.defaults.extend_comment_on_newline = Some(false);
 3377    });
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.set_state(indoc! {"
 3380        // Fooˇ
 3381    "});
 3382    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383    cx.assert_editor_state(indoc! {"
 3384        // Foo
 3385        ˇ
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4)
 3393    });
 3394
 3395    let language = Arc::new(Language::new(
 3396        LanguageConfig {
 3397            line_comments: vec!["// ".into(), "/// ".into()],
 3398            ..LanguageConfig::default()
 3399        },
 3400        None,
 3401    ));
 3402    {
 3403        let mut cx = EditorTestContext::new(cx).await;
 3404        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3405        cx.set_state(indoc! {"
 3406        //ˇ
 3407    "});
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        //
 3411        // ˇ
 3412    "});
 3413
 3414        cx.set_state(indoc! {"
 3415        ///ˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        ///
 3420        /// ˇ
 3421    "});
 3422    }
 3423}
 3424
 3425#[gpui::test]
 3426async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3427    init_test(cx, |settings| {
 3428        settings.defaults.tab_size = NonZeroU32::new(4)
 3429    });
 3430
 3431    let language = Arc::new(
 3432        Language::new(
 3433            LanguageConfig {
 3434                documentation_comment: Some(language::BlockCommentConfig {
 3435                    start: "/**".into(),
 3436                    end: "*/".into(),
 3437                    prefix: "* ".into(),
 3438                    tab_size: 1,
 3439                }),
 3440
 3441                ..LanguageConfig::default()
 3442            },
 3443            Some(tree_sitter_rust::LANGUAGE.into()),
 3444        )
 3445        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3446        .unwrap(),
 3447    );
 3448
 3449    {
 3450        let mut cx = EditorTestContext::new(cx).await;
 3451        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3452        cx.set_state(indoc! {"
 3453        /**ˇ
 3454    "});
 3455
 3456        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3457        cx.assert_editor_state(indoc! {"
 3458        /**
 3459         * ˇ
 3460    "});
 3461        // Ensure that if cursor is before the comment start,
 3462        // we do not actually insert a comment prefix.
 3463        cx.set_state(indoc! {"
 3464        ˇ/**
 3465    "});
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468
 3469        ˇ/**
 3470    "});
 3471        // Ensure that if cursor is between it doesn't add comment prefix.
 3472        cx.set_state(indoc! {"
 3473        /*ˇ*
 3474    "});
 3475        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3476        cx.assert_editor_state(indoc! {"
 3477        /*
 3478        ˇ*
 3479    "});
 3480        // Ensure that if suffix exists on same line after cursor it adds new line.
 3481        cx.set_state(indoc! {"
 3482        /**ˇ*/
 3483    "});
 3484        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3485        cx.assert_editor_state(indoc! {"
 3486        /**
 3487         * ˇ
 3488         */
 3489    "});
 3490        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3491        cx.set_state(indoc! {"
 3492        /**ˇ */
 3493    "});
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498         */
 3499    "});
 3500        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3501        cx.set_state(indoc! {"
 3502        /** ˇ*/
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(
 3506            indoc! {"
 3507        /**s
 3508         * ˇ
 3509         */
 3510    "}
 3511            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3512            .as_str(),
 3513        );
 3514        // Ensure that delimiter space is preserved when newline on already
 3515        // spaced delimiter.
 3516        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3517        cx.assert_editor_state(
 3518            indoc! {"
 3519        /**s
 3520         *s
 3521         * ˇ
 3522         */
 3523    "}
 3524            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3525            .as_str(),
 3526        );
 3527        // Ensure that delimiter space is preserved when space is not
 3528        // on existing delimiter.
 3529        cx.set_state(indoc! {"
 3530        /**
 3531 3532         */
 3533    "});
 3534        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3535        cx.assert_editor_state(indoc! {"
 3536        /**
 3537         *
 3538         * ˇ
 3539         */
 3540    "});
 3541        // Ensure that if suffix exists on same line after cursor it
 3542        // doesn't add extra new line if prefix is not on same line.
 3543        cx.set_state(indoc! {"
 3544        /**
 3545        ˇ*/
 3546    "});
 3547        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3548        cx.assert_editor_state(indoc! {"
 3549        /**
 3550
 3551        ˇ*/
 3552    "});
 3553        // Ensure that it detects suffix after existing prefix.
 3554        cx.set_state(indoc! {"
 3555        /**ˇ/
 3556    "});
 3557        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3558        cx.assert_editor_state(indoc! {"
 3559        /**
 3560        ˇ/
 3561    "});
 3562        // Ensure that if suffix exists on same line before
 3563        // cursor it does not add comment prefix.
 3564        cx.set_state(indoc! {"
 3565        /** */ˇ
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /** */
 3570        ˇ
 3571    "});
 3572        // Ensure that if suffix exists on same line before
 3573        // cursor it does not add comment prefix.
 3574        cx.set_state(indoc! {"
 3575        /**
 3576         *
 3577         */ˇ
 3578    "});
 3579        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3580        cx.assert_editor_state(indoc! {"
 3581        /**
 3582         *
 3583         */
 3584         ˇ
 3585    "});
 3586
 3587        // Ensure that inline comment followed by code
 3588        // doesn't add comment prefix on newline
 3589        cx.set_state(indoc! {"
 3590        /** */ textˇ
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        /** */ text
 3595        ˇ
 3596    "});
 3597
 3598        // Ensure that text after comment end tag
 3599        // doesn't add comment prefix on newline
 3600        cx.set_state(indoc! {"
 3601        /**
 3602         *
 3603         */ˇtext
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         */
 3610         ˇtext
 3611    "});
 3612
 3613        // Ensure if not comment block it doesn't
 3614        // add comment prefix on newline
 3615        cx.set_state(indoc! {"
 3616        * textˇ
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        * text
 3621        ˇ
 3622    "});
 3623    }
 3624    // Ensure that comment continuations can be disabled.
 3625    update_test_language_settings(cx, |settings| {
 3626        settings.defaults.extend_comment_on_newline = Some(false);
 3627    });
 3628    let mut cx = EditorTestContext::new(cx).await;
 3629    cx.set_state(indoc! {"
 3630        /**ˇ
 3631    "});
 3632    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634        /**
 3635        ˇ
 3636    "});
 3637}
 3638
 3639#[gpui::test]
 3640async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3641    init_test(cx, |settings| {
 3642        settings.defaults.tab_size = NonZeroU32::new(4)
 3643    });
 3644
 3645    let lua_language = Arc::new(Language::new(
 3646        LanguageConfig {
 3647            line_comments: vec!["--".into()],
 3648            block_comment: Some(language::BlockCommentConfig {
 3649                start: "--[[".into(),
 3650                prefix: "".into(),
 3651                end: "]]".into(),
 3652                tab_size: 0,
 3653            }),
 3654            ..LanguageConfig::default()
 3655        },
 3656        None,
 3657    ));
 3658
 3659    let mut cx = EditorTestContext::new(cx).await;
 3660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3661
 3662    // Line with line comment should extend
 3663    cx.set_state(indoc! {"
 3664        --ˇ
 3665    "});
 3666    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3667    cx.assert_editor_state(indoc! {"
 3668        --
 3669        --ˇ
 3670    "});
 3671
 3672    // Line with block comment that matches line comment should not extend
 3673    cx.set_state(indoc! {"
 3674        --[[ˇ
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        --[[
 3679        ˇ
 3680    "});
 3681}
 3682
 3683#[gpui::test]
 3684fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3685    init_test(cx, |_| {});
 3686
 3687    let editor = cx.add_window(|window, cx| {
 3688        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3689        let mut editor = build_editor(buffer, window, cx);
 3690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3691            s.select_ranges([3..4, 11..12, 19..20])
 3692        });
 3693        editor
 3694    });
 3695
 3696    _ = editor.update(cx, |editor, window, cx| {
 3697        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3698        editor.buffer.update(cx, |buffer, cx| {
 3699            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3700            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3701        });
 3702        assert_eq!(
 3703            editor.selections.ranges(&editor.display_snapshot(cx)),
 3704            &[2..2, 7..7, 12..12],
 3705        );
 3706
 3707        editor.insert("Z", window, cx);
 3708        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3709
 3710        // The selections are moved after the inserted characters
 3711        assert_eq!(
 3712            editor.selections.ranges(&editor.display_snapshot(cx)),
 3713            &[3..3, 9..9, 15..15],
 3714        );
 3715    });
 3716}
 3717
 3718#[gpui::test]
 3719async fn test_tab(cx: &mut TestAppContext) {
 3720    init_test(cx, |settings| {
 3721        settings.defaults.tab_size = NonZeroU32::new(3)
 3722    });
 3723
 3724    let mut cx = EditorTestContext::new(cx).await;
 3725    cx.set_state(indoc! {"
 3726        ˇabˇc
 3727        ˇ🏀ˇ🏀ˇefg
 3728 3729    "});
 3730    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3731    cx.assert_editor_state(indoc! {"
 3732           ˇab ˇc
 3733           ˇ🏀  ˇ🏀  ˇefg
 3734        d  ˇ
 3735    "});
 3736
 3737    cx.set_state(indoc! {"
 3738        a
 3739        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3740    "});
 3741    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3742    cx.assert_editor_state(indoc! {"
 3743        a
 3744           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3745    "});
 3746}
 3747
 3748#[gpui::test]
 3749async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3750    init_test(cx, |_| {});
 3751
 3752    let mut cx = EditorTestContext::new(cx).await;
 3753    let language = Arc::new(
 3754        Language::new(
 3755            LanguageConfig::default(),
 3756            Some(tree_sitter_rust::LANGUAGE.into()),
 3757        )
 3758        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3759        .unwrap(),
 3760    );
 3761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3762
 3763    // test when all cursors are not at suggested indent
 3764    // then simply move to their suggested indent location
 3765    cx.set_state(indoc! {"
 3766        const a: B = (
 3767            c(
 3768        ˇ
 3769        ˇ    )
 3770        );
 3771    "});
 3772    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3773    cx.assert_editor_state(indoc! {"
 3774        const a: B = (
 3775            c(
 3776                ˇ
 3777            ˇ)
 3778        );
 3779    "});
 3780
 3781    // test cursor already at suggested indent not moving when
 3782    // other cursors are yet to reach their suggested indents
 3783    cx.set_state(indoc! {"
 3784        ˇ
 3785        const a: B = (
 3786            c(
 3787                d(
 3788        ˇ
 3789                )
 3790        ˇ
 3791        ˇ    )
 3792        );
 3793    "});
 3794    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3795    cx.assert_editor_state(indoc! {"
 3796        ˇ
 3797        const a: B = (
 3798            c(
 3799                d(
 3800                    ˇ
 3801                )
 3802                ˇ
 3803            ˇ)
 3804        );
 3805    "});
 3806    // test when all cursors are at suggested indent then tab is inserted
 3807    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3808    cx.assert_editor_state(indoc! {"
 3809            ˇ
 3810        const a: B = (
 3811            c(
 3812                d(
 3813                        ˇ
 3814                )
 3815                    ˇ
 3816                ˇ)
 3817        );
 3818    "});
 3819
 3820    // test when current indent is less than suggested indent,
 3821    // we adjust line to match suggested indent and move cursor to it
 3822    //
 3823    // when no other cursor is at word boundary, all of them should move
 3824    cx.set_state(indoc! {"
 3825        const a: B = (
 3826            c(
 3827                d(
 3828        ˇ
 3829        ˇ   )
 3830        ˇ   )
 3831        );
 3832    "});
 3833    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3834    cx.assert_editor_state(indoc! {"
 3835        const a: B = (
 3836            c(
 3837                d(
 3838                    ˇ
 3839                ˇ)
 3840            ˇ)
 3841        );
 3842    "});
 3843
 3844    // test when current indent is less than suggested indent,
 3845    // we adjust line to match suggested indent and move cursor to it
 3846    //
 3847    // when some other cursor is at word boundary, it should not move
 3848    cx.set_state(indoc! {"
 3849        const a: B = (
 3850            c(
 3851                d(
 3852        ˇ
 3853        ˇ   )
 3854           ˇ)
 3855        );
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        const a: B = (
 3860            c(
 3861                d(
 3862                    ˇ
 3863                ˇ)
 3864            ˇ)
 3865        );
 3866    "});
 3867
 3868    // test when current indent is more than suggested indent,
 3869    // we just move cursor to current indent instead of suggested indent
 3870    //
 3871    // when no other cursor is at word boundary, all of them should move
 3872    cx.set_state(indoc! {"
 3873        const a: B = (
 3874            c(
 3875                d(
 3876        ˇ
 3877        ˇ                )
 3878        ˇ   )
 3879        );
 3880    "});
 3881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3882    cx.assert_editor_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886                    ˇ
 3887                        ˇ)
 3888            ˇ)
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                        ˇ
 3897                            ˇ)
 3898                ˇ)
 3899        );
 3900    "});
 3901
 3902    // test when current indent is more than suggested indent,
 3903    // we just move cursor to current indent instead of suggested indent
 3904    //
 3905    // when some other cursor is at word boundary, it doesn't move
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909                d(
 3910        ˇ
 3911        ˇ                )
 3912            ˇ)
 3913        );
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920                    ˇ
 3921                        ˇ)
 3922            ˇ)
 3923        );
 3924    "});
 3925
 3926    // handle auto-indent when there are multiple cursors on the same line
 3927    cx.set_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930        ˇ    ˇ
 3931        ˇ    )
 3932        );
 3933    "});
 3934    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3935    cx.assert_editor_state(indoc! {"
 3936        const a: B = (
 3937            c(
 3938                ˇ
 3939            ˇ)
 3940        );
 3941    "});
 3942}
 3943
 3944#[gpui::test]
 3945async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3946    init_test(cx, |settings| {
 3947        settings.defaults.tab_size = NonZeroU32::new(3)
 3948    });
 3949
 3950    let mut cx = EditorTestContext::new(cx).await;
 3951    cx.set_state(indoc! {"
 3952         ˇ
 3953        \t ˇ
 3954        \t  ˇ
 3955        \t   ˇ
 3956         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3957    "});
 3958
 3959    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3960    cx.assert_editor_state(indoc! {"
 3961           ˇ
 3962        \t   ˇ
 3963        \t   ˇ
 3964        \t      ˇ
 3965         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3966    "});
 3967}
 3968
 3969#[gpui::test]
 3970async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3971    init_test(cx, |settings| {
 3972        settings.defaults.tab_size = NonZeroU32::new(4)
 3973    });
 3974
 3975    let language = Arc::new(
 3976        Language::new(
 3977            LanguageConfig::default(),
 3978            Some(tree_sitter_rust::LANGUAGE.into()),
 3979        )
 3980        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3981        .unwrap(),
 3982    );
 3983
 3984    let mut cx = EditorTestContext::new(cx).await;
 3985    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3986    cx.set_state(indoc! {"
 3987        fn a() {
 3988            if b {
 3989        \t ˇc
 3990            }
 3991        }
 3992    "});
 3993
 3994    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3995    cx.assert_editor_state(indoc! {"
 3996        fn a() {
 3997            if b {
 3998                ˇc
 3999            }
 4000        }
 4001    "});
 4002}
 4003
 4004#[gpui::test]
 4005async fn test_indent_outdent(cx: &mut TestAppContext) {
 4006    init_test(cx, |settings| {
 4007        settings.defaults.tab_size = NonZeroU32::new(4);
 4008    });
 4009
 4010    let mut cx = EditorTestContext::new(cx).await;
 4011
 4012    cx.set_state(indoc! {"
 4013          «oneˇ» «twoˇ»
 4014        three
 4015         four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019            «oneˇ» «twoˇ»
 4020        three
 4021         four
 4022    "});
 4023
 4024    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4025    cx.assert_editor_state(indoc! {"
 4026        «oneˇ» «twoˇ»
 4027        three
 4028         four
 4029    "});
 4030
 4031    // select across line ending
 4032    cx.set_state(indoc! {"
 4033        one two
 4034        t«hree
 4035        ˇ» four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        one two
 4040            t«hree
 4041        ˇ» four
 4042    "});
 4043
 4044    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4045    cx.assert_editor_state(indoc! {"
 4046        one two
 4047        t«hree
 4048        ˇ» four
 4049    "});
 4050
 4051    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4052    cx.set_state(indoc! {"
 4053        one two
 4054        ˇthree
 4055            four
 4056    "});
 4057    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4058    cx.assert_editor_state(indoc! {"
 4059        one two
 4060            ˇthree
 4061            four
 4062    "});
 4063
 4064    cx.set_state(indoc! {"
 4065        one two
 4066        ˇ    three
 4067            four
 4068    "});
 4069    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4070    cx.assert_editor_state(indoc! {"
 4071        one two
 4072        ˇthree
 4073            four
 4074    "});
 4075}
 4076
 4077#[gpui::test]
 4078async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4079    // This is a regression test for issue #33761
 4080    init_test(cx, |_| {});
 4081
 4082    let mut cx = EditorTestContext::new(cx).await;
 4083    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4085
 4086    cx.set_state(
 4087        r#"ˇ#     ingress:
 4088ˇ#         api:
 4089ˇ#             enabled: false
 4090ˇ#             pathType: Prefix
 4091ˇ#           console:
 4092ˇ#               enabled: false
 4093ˇ#               pathType: Prefix
 4094"#,
 4095    );
 4096
 4097    // Press tab to indent all lines
 4098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4099
 4100    cx.assert_editor_state(
 4101        r#"    ˇ#     ingress:
 4102    ˇ#         api:
 4103    ˇ#             enabled: false
 4104    ˇ#             pathType: Prefix
 4105    ˇ#           console:
 4106    ˇ#               enabled: false
 4107    ˇ#               pathType: Prefix
 4108"#,
 4109    );
 4110}
 4111
 4112#[gpui::test]
 4113async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4114    // This is a test to make sure our fix for issue #33761 didn't break anything
 4115    init_test(cx, |_| {});
 4116
 4117    let mut cx = EditorTestContext::new(cx).await;
 4118    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4120
 4121    cx.set_state(
 4122        r#"ˇingress:
 4123ˇ  api:
 4124ˇ    enabled: false
 4125ˇ    pathType: Prefix
 4126"#,
 4127    );
 4128
 4129    // Press tab to indent all lines
 4130    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4131
 4132    cx.assert_editor_state(
 4133        r#"ˇingress:
 4134    ˇapi:
 4135        ˇenabled: false
 4136        ˇpathType: Prefix
 4137"#,
 4138    );
 4139}
 4140
 4141#[gpui::test]
 4142async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4143    init_test(cx, |settings| {
 4144        settings.defaults.hard_tabs = Some(true);
 4145    });
 4146
 4147    let mut cx = EditorTestContext::new(cx).await;
 4148
 4149    // select two ranges on one line
 4150    cx.set_state(indoc! {"
 4151        «oneˇ» «twoˇ»
 4152        three
 4153        four
 4154    "});
 4155    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4156    cx.assert_editor_state(indoc! {"
 4157        \t«oneˇ» «twoˇ»
 4158        three
 4159        four
 4160    "});
 4161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4162    cx.assert_editor_state(indoc! {"
 4163        \t\t«oneˇ» «twoˇ»
 4164        three
 4165        four
 4166    "});
 4167    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4168    cx.assert_editor_state(indoc! {"
 4169        \t«oneˇ» «twoˇ»
 4170        three
 4171        four
 4172    "});
 4173    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4174    cx.assert_editor_state(indoc! {"
 4175        «oneˇ» «twoˇ»
 4176        three
 4177        four
 4178    "});
 4179
 4180    // select across a line ending
 4181    cx.set_state(indoc! {"
 4182        one two
 4183        t«hree
 4184        ˇ»four
 4185    "});
 4186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4187    cx.assert_editor_state(indoc! {"
 4188        one two
 4189        \tt«hree
 4190        ˇ»four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        one two
 4195        \t\tt«hree
 4196        ˇ»four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201        \tt«hree
 4202        ˇ»four
 4203    "});
 4204    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4205    cx.assert_editor_state(indoc! {"
 4206        one two
 4207        t«hree
 4208        ˇ»four
 4209    "});
 4210
 4211    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4212    cx.set_state(indoc! {"
 4213        one two
 4214        ˇthree
 4215        four
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        one two
 4220        ˇthree
 4221        four
 4222    "});
 4223    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4224    cx.assert_editor_state(indoc! {"
 4225        one two
 4226        \tˇthree
 4227        four
 4228    "});
 4229    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4230    cx.assert_editor_state(indoc! {"
 4231        one two
 4232        ˇthree
 4233        four
 4234    "});
 4235}
 4236
 4237#[gpui::test]
 4238fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4239    init_test(cx, |settings| {
 4240        settings.languages.0.extend([
 4241            (
 4242                "TOML".into(),
 4243                LanguageSettingsContent {
 4244                    tab_size: NonZeroU32::new(2),
 4245                    ..Default::default()
 4246                },
 4247            ),
 4248            (
 4249                "Rust".into(),
 4250                LanguageSettingsContent {
 4251                    tab_size: NonZeroU32::new(4),
 4252                    ..Default::default()
 4253                },
 4254            ),
 4255        ]);
 4256    });
 4257
 4258    let toml_language = Arc::new(Language::new(
 4259        LanguageConfig {
 4260            name: "TOML".into(),
 4261            ..Default::default()
 4262        },
 4263        None,
 4264    ));
 4265    let rust_language = Arc::new(Language::new(
 4266        LanguageConfig {
 4267            name: "Rust".into(),
 4268            ..Default::default()
 4269        },
 4270        None,
 4271    ));
 4272
 4273    let toml_buffer =
 4274        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4275    let rust_buffer =
 4276        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4277    let multibuffer = cx.new(|cx| {
 4278        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4279        multibuffer.push_excerpts(
 4280            toml_buffer.clone(),
 4281            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4282            cx,
 4283        );
 4284        multibuffer.push_excerpts(
 4285            rust_buffer.clone(),
 4286            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4287            cx,
 4288        );
 4289        multibuffer
 4290    });
 4291
 4292    cx.add_window(|window, cx| {
 4293        let mut editor = build_editor(multibuffer, window, cx);
 4294
 4295        assert_eq!(
 4296            editor.text(cx),
 4297            indoc! {"
 4298                a = 1
 4299                b = 2
 4300
 4301                const c: usize = 3;
 4302            "}
 4303        );
 4304
 4305        select_ranges(
 4306            &mut editor,
 4307            indoc! {"
 4308                «aˇ» = 1
 4309                b = 2
 4310
 4311                «const c:ˇ» usize = 3;
 4312            "},
 4313            window,
 4314            cx,
 4315        );
 4316
 4317        editor.tab(&Tab, window, cx);
 4318        assert_text_with_selections(
 4319            &mut editor,
 4320            indoc! {"
 4321                  «aˇ» = 1
 4322                b = 2
 4323
 4324                    «const c:ˇ» usize = 3;
 4325            "},
 4326            cx,
 4327        );
 4328        editor.backtab(&Backtab, window, cx);
 4329        assert_text_with_selections(
 4330            &mut editor,
 4331            indoc! {"
 4332                «aˇ» = 1
 4333                b = 2
 4334
 4335                «const c:ˇ» usize = 3;
 4336            "},
 4337            cx,
 4338        );
 4339
 4340        editor
 4341    });
 4342}
 4343
 4344#[gpui::test]
 4345async fn test_backspace(cx: &mut TestAppContext) {
 4346    init_test(cx, |_| {});
 4347
 4348    let mut cx = EditorTestContext::new(cx).await;
 4349
 4350    // Basic backspace
 4351    cx.set_state(indoc! {"
 4352        onˇe two three
 4353        fou«rˇ» five six
 4354        seven «ˇeight nine
 4355        »ten
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        oˇe two three
 4360        fouˇ five six
 4361        seven ˇten
 4362    "});
 4363
 4364    // Test backspace inside and around indents
 4365    cx.set_state(indoc! {"
 4366        zero
 4367            ˇone
 4368                ˇtwo
 4369            ˇ ˇ ˇ  three
 4370        ˇ  ˇ  four
 4371    "});
 4372    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4373    cx.assert_editor_state(indoc! {"
 4374        zero
 4375        ˇone
 4376            ˇtwo
 4377        ˇ  threeˇ  four
 4378    "});
 4379}
 4380
 4381#[gpui::test]
 4382async fn test_delete(cx: &mut TestAppContext) {
 4383    init_test(cx, |_| {});
 4384
 4385    let mut cx = EditorTestContext::new(cx).await;
 4386    cx.set_state(indoc! {"
 4387        onˇe two three
 4388        fou«rˇ» five six
 4389        seven «ˇeight nine
 4390        »ten
 4391    "});
 4392    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4393    cx.assert_editor_state(indoc! {"
 4394        onˇ two three
 4395        fouˇ five six
 4396        seven ˇten
 4397    "});
 4398}
 4399
 4400#[gpui::test]
 4401fn test_delete_line(cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let editor = cx.add_window(|window, cx| {
 4405        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4406        build_editor(buffer, window, cx)
 4407    });
 4408    _ = editor.update(cx, |editor, window, cx| {
 4409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4410            s.select_display_ranges([
 4411                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4412                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4413                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4414            ])
 4415        });
 4416        editor.delete_line(&DeleteLine, window, cx);
 4417        assert_eq!(editor.display_text(cx), "ghi");
 4418        assert_eq!(
 4419            editor.selections.display_ranges(cx),
 4420            vec![
 4421                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4422                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4423            ]
 4424        );
 4425    });
 4426
 4427    let editor = cx.add_window(|window, cx| {
 4428        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4429        build_editor(buffer, window, cx)
 4430    });
 4431    _ = editor.update(cx, |editor, window, cx| {
 4432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4433            s.select_display_ranges([
 4434                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4435            ])
 4436        });
 4437        editor.delete_line(&DeleteLine, window, cx);
 4438        assert_eq!(editor.display_text(cx), "ghi\n");
 4439        assert_eq!(
 4440            editor.selections.display_ranges(cx),
 4441            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4442        );
 4443    });
 4444
 4445    let editor = cx.add_window(|window, cx| {
 4446        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4447        build_editor(buffer, window, cx)
 4448    });
 4449    _ = editor.update(cx, |editor, window, cx| {
 4450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4451            s.select_display_ranges([
 4452                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4453            ])
 4454        });
 4455        editor.delete_line(&DeleteLine, window, cx);
 4456        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4457        assert_eq!(
 4458            editor.selections.display_ranges(cx),
 4459            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4460        );
 4461    });
 4462}
 4463
 4464#[gpui::test]
 4465fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4466    init_test(cx, |_| {});
 4467
 4468    cx.add_window(|window, cx| {
 4469        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4470        let mut editor = build_editor(buffer.clone(), window, cx);
 4471        let buffer = buffer.read(cx).as_singleton().unwrap();
 4472
 4473        assert_eq!(
 4474            editor
 4475                .selections
 4476                .ranges::<Point>(&editor.display_snapshot(cx)),
 4477            &[Point::new(0, 0)..Point::new(0, 0)]
 4478        );
 4479
 4480        // When on single line, replace newline at end by space
 4481        editor.join_lines(&JoinLines, window, cx);
 4482        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4483        assert_eq!(
 4484            editor
 4485                .selections
 4486                .ranges::<Point>(&editor.display_snapshot(cx)),
 4487            &[Point::new(0, 3)..Point::new(0, 3)]
 4488        );
 4489
 4490        // When multiple lines are selected, remove newlines that are spanned by the selection
 4491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4492            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4493        });
 4494        editor.join_lines(&JoinLines, window, cx);
 4495        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4496        assert_eq!(
 4497            editor
 4498                .selections
 4499                .ranges::<Point>(&editor.display_snapshot(cx)),
 4500            &[Point::new(0, 11)..Point::new(0, 11)]
 4501        );
 4502
 4503        // Undo should be transactional
 4504        editor.undo(&Undo, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4506        assert_eq!(
 4507            editor
 4508                .selections
 4509                .ranges::<Point>(&editor.display_snapshot(cx)),
 4510            &[Point::new(0, 5)..Point::new(2, 2)]
 4511        );
 4512
 4513        // When joining an empty line don't insert a space
 4514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4515            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4516        });
 4517        editor.join_lines(&JoinLines, window, cx);
 4518        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4519        assert_eq!(
 4520            editor
 4521                .selections
 4522                .ranges::<Point>(&editor.display_snapshot(cx)),
 4523            [Point::new(2, 3)..Point::new(2, 3)]
 4524        );
 4525
 4526        // We can remove trailing newlines
 4527        editor.join_lines(&JoinLines, window, cx);
 4528        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            [Point::new(2, 3)..Point::new(2, 3)]
 4534        );
 4535
 4536        // We don't blow up on the last line
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            [Point::new(2, 3)..Point::new(2, 3)]
 4544        );
 4545
 4546        // reset to test indentation
 4547        editor.buffer.update(cx, |buffer, cx| {
 4548            buffer.edit(
 4549                [
 4550                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4551                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4552                ],
 4553                None,
 4554                cx,
 4555            )
 4556        });
 4557
 4558        // We remove any leading spaces
 4559        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4560        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4561            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4562        });
 4563        editor.join_lines(&JoinLines, window, cx);
 4564        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4565
 4566        // We don't insert a space for a line containing only spaces
 4567        editor.join_lines(&JoinLines, window, cx);
 4568        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4569
 4570        // We ignore any leading tabs
 4571        editor.join_lines(&JoinLines, window, cx);
 4572        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4573
 4574        editor
 4575    });
 4576}
 4577
 4578#[gpui::test]
 4579fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4580    init_test(cx, |_| {});
 4581
 4582    cx.add_window(|window, cx| {
 4583        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4584        let mut editor = build_editor(buffer.clone(), window, cx);
 4585        let buffer = buffer.read(cx).as_singleton().unwrap();
 4586
 4587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4588            s.select_ranges([
 4589                Point::new(0, 2)..Point::new(1, 1),
 4590                Point::new(1, 2)..Point::new(1, 2),
 4591                Point::new(3, 1)..Point::new(3, 2),
 4592            ])
 4593        });
 4594
 4595        editor.join_lines(&JoinLines, window, cx);
 4596        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4597
 4598        assert_eq!(
 4599            editor
 4600                .selections
 4601                .ranges::<Point>(&editor.display_snapshot(cx)),
 4602            [
 4603                Point::new(0, 7)..Point::new(0, 7),
 4604                Point::new(1, 3)..Point::new(1, 3)
 4605            ]
 4606        );
 4607        editor
 4608    });
 4609}
 4610
 4611#[gpui::test]
 4612async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4613    init_test(cx, |_| {});
 4614
 4615    let mut cx = EditorTestContext::new(cx).await;
 4616
 4617    let diff_base = r#"
 4618        Line 0
 4619        Line 1
 4620        Line 2
 4621        Line 3
 4622        "#
 4623    .unindent();
 4624
 4625    cx.set_state(
 4626        &r#"
 4627        ˇLine 0
 4628        Line 1
 4629        Line 2
 4630        Line 3
 4631        "#
 4632        .unindent(),
 4633    );
 4634
 4635    cx.set_head_text(&diff_base);
 4636    executor.run_until_parked();
 4637
 4638    // Join lines
 4639    cx.update_editor(|editor, window, cx| {
 4640        editor.join_lines(&JoinLines, window, cx);
 4641    });
 4642    executor.run_until_parked();
 4643
 4644    cx.assert_editor_state(
 4645        &r#"
 4646        Line 0ˇ Line 1
 4647        Line 2
 4648        Line 3
 4649        "#
 4650        .unindent(),
 4651    );
 4652    // Join again
 4653    cx.update_editor(|editor, window, cx| {
 4654        editor.join_lines(&JoinLines, window, cx);
 4655    });
 4656    executor.run_until_parked();
 4657
 4658    cx.assert_editor_state(
 4659        &r#"
 4660        Line 0 Line 1ˇ Line 2
 4661        Line 3
 4662        "#
 4663        .unindent(),
 4664    );
 4665}
 4666
 4667#[gpui::test]
 4668async fn test_custom_newlines_cause_no_false_positive_diffs(
 4669    executor: BackgroundExecutor,
 4670    cx: &mut TestAppContext,
 4671) {
 4672    init_test(cx, |_| {});
 4673    let mut cx = EditorTestContext::new(cx).await;
 4674    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4675    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4676    executor.run_until_parked();
 4677
 4678    cx.update_editor(|editor, window, cx| {
 4679        let snapshot = editor.snapshot(window, cx);
 4680        assert_eq!(
 4681            snapshot
 4682                .buffer_snapshot()
 4683                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4684                .collect::<Vec<_>>(),
 4685            Vec::new(),
 4686            "Should not have any diffs for files with custom newlines"
 4687        );
 4688    });
 4689}
 4690
 4691#[gpui::test]
 4692async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4693    init_test(cx, |_| {});
 4694
 4695    let mut cx = EditorTestContext::new(cx).await;
 4696
 4697    // Test sort_lines_case_insensitive()
 4698    cx.set_state(indoc! {"
 4699        «z
 4700        y
 4701        x
 4702        Z
 4703        Y
 4704        Xˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| {
 4707        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4708    });
 4709    cx.assert_editor_state(indoc! {"
 4710        «x
 4711        X
 4712        y
 4713        Y
 4714        z
 4715        Zˇ»
 4716    "});
 4717
 4718    // Test sort_lines_by_length()
 4719    //
 4720    // Demonstrates:
 4721    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4722    // - sort is stable
 4723    cx.set_state(indoc! {"
 4724        «123
 4725        æ
 4726        12
 4727 4728        1
 4729        æˇ»
 4730    "});
 4731    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4732    cx.assert_editor_state(indoc! {"
 4733        «æ
 4734 4735        1
 4736        æ
 4737        12
 4738        123ˇ»
 4739    "});
 4740
 4741    // Test reverse_lines()
 4742    cx.set_state(indoc! {"
 4743        «5
 4744        4
 4745        3
 4746        2
 4747        1ˇ»
 4748    "});
 4749    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4750    cx.assert_editor_state(indoc! {"
 4751        «1
 4752        2
 4753        3
 4754        4
 4755        5ˇ»
 4756    "});
 4757
 4758    // Skip testing shuffle_line()
 4759
 4760    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4761    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4762
 4763    // Don't manipulate when cursor is on single line, but expand the selection
 4764    cx.set_state(indoc! {"
 4765        ddˇdd
 4766        ccc
 4767        bb
 4768        a
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «ddddˇ»
 4775        ccc
 4776        bb
 4777        a
 4778    "});
 4779
 4780    // Basic manipulate case
 4781    // Start selection moves to column 0
 4782    // End of selection shrinks to fit shorter line
 4783    cx.set_state(indoc! {"
 4784        dd«d
 4785        ccc
 4786        bb
 4787        aaaaaˇ»
 4788    "});
 4789    cx.update_editor(|e, window, cx| {
 4790        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4791    });
 4792    cx.assert_editor_state(indoc! {"
 4793        «aaaaa
 4794        bb
 4795        ccc
 4796        dddˇ»
 4797    "});
 4798
 4799    // Manipulate case with newlines
 4800    cx.set_state(indoc! {"
 4801        dd«d
 4802        ccc
 4803
 4804        bb
 4805        aaaaa
 4806
 4807        ˇ»
 4808    "});
 4809    cx.update_editor(|e, window, cx| {
 4810        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4811    });
 4812    cx.assert_editor_state(indoc! {"
 4813        «
 4814
 4815        aaaaa
 4816        bb
 4817        ccc
 4818        dddˇ»
 4819
 4820    "});
 4821
 4822    // Adding new line
 4823    cx.set_state(indoc! {"
 4824        aa«a
 4825        bbˇ»b
 4826    "});
 4827    cx.update_editor(|e, window, cx| {
 4828        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4829    });
 4830    cx.assert_editor_state(indoc! {"
 4831        «aaa
 4832        bbb
 4833        added_lineˇ»
 4834    "});
 4835
 4836    // Removing line
 4837    cx.set_state(indoc! {"
 4838        aa«a
 4839        bbbˇ»
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.manipulate_immutable_lines(window, cx, |lines| {
 4843            lines.pop();
 4844        })
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «aaaˇ»
 4848    "});
 4849
 4850    // Removing all lines
 4851    cx.set_state(indoc! {"
 4852        aa«a
 4853        bbbˇ»
 4854    "});
 4855    cx.update_editor(|e, window, cx| {
 4856        e.manipulate_immutable_lines(window, cx, |lines| {
 4857            lines.drain(..);
 4858        })
 4859    });
 4860    cx.assert_editor_state(indoc! {"
 4861        ˇ
 4862    "});
 4863}
 4864
 4865#[gpui::test]
 4866async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4867    init_test(cx, |_| {});
 4868
 4869    let mut cx = EditorTestContext::new(cx).await;
 4870
 4871    // Consider continuous selection as single selection
 4872    cx.set_state(indoc! {"
 4873        Aaa«aa
 4874        cˇ»c«c
 4875        bb
 4876        aaaˇ»aa
 4877    "});
 4878    cx.update_editor(|e, window, cx| {
 4879        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4880    });
 4881    cx.assert_editor_state(indoc! {"
 4882        «Aaaaa
 4883        ccc
 4884        bb
 4885        aaaaaˇ»
 4886    "});
 4887
 4888    cx.set_state(indoc! {"
 4889        Aaa«aa
 4890        cˇ»c«c
 4891        bb
 4892        aaaˇ»aa
 4893    "});
 4894    cx.update_editor(|e, window, cx| {
 4895        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4896    });
 4897    cx.assert_editor_state(indoc! {"
 4898        «Aaaaa
 4899        ccc
 4900        bbˇ»
 4901    "});
 4902
 4903    // Consider non continuous selection as distinct dedup operations
 4904    cx.set_state(indoc! {"
 4905        «aaaaa
 4906        bb
 4907        aaaaa
 4908        aaaaaˇ»
 4909
 4910        aaa«aaˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4914    });
 4915    cx.assert_editor_state(indoc! {"
 4916        «aaaaa
 4917        bbˇ»
 4918
 4919        «aaaaaˇ»
 4920    "});
 4921}
 4922
 4923#[gpui::test]
 4924async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4925    init_test(cx, |_| {});
 4926
 4927    let mut cx = EditorTestContext::new(cx).await;
 4928
 4929    cx.set_state(indoc! {"
 4930        «Aaa
 4931        aAa
 4932        Aaaˇ»
 4933    "});
 4934    cx.update_editor(|e, window, cx| {
 4935        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4936    });
 4937    cx.assert_editor_state(indoc! {"
 4938        «Aaa
 4939        aAaˇ»
 4940    "});
 4941
 4942    cx.set_state(indoc! {"
 4943        «Aaa
 4944        aAa
 4945        aaAˇ»
 4946    "});
 4947    cx.update_editor(|e, window, cx| {
 4948        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4949    });
 4950    cx.assert_editor_state(indoc! {"
 4951        «Aaaˇ»
 4952    "});
 4953}
 4954
 4955#[gpui::test]
 4956async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4957    init_test(cx, |_| {});
 4958
 4959    let mut cx = EditorTestContext::new(cx).await;
 4960
 4961    let js_language = Arc::new(Language::new(
 4962        LanguageConfig {
 4963            name: "JavaScript".into(),
 4964            wrap_characters: Some(language::WrapCharactersConfig {
 4965                start_prefix: "<".into(),
 4966                start_suffix: ">".into(),
 4967                end_prefix: "</".into(),
 4968                end_suffix: ">".into(),
 4969            }),
 4970            ..LanguageConfig::default()
 4971        },
 4972        None,
 4973    ));
 4974
 4975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4976
 4977    cx.set_state(indoc! {"
 4978        «testˇ»
 4979    "});
 4980    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4981    cx.assert_editor_state(indoc! {"
 4982        <«ˇ»>test</«ˇ»>
 4983    "});
 4984
 4985    cx.set_state(indoc! {"
 4986        «test
 4987         testˇ»
 4988    "});
 4989    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4990    cx.assert_editor_state(indoc! {"
 4991        <«ˇ»>test
 4992         test</«ˇ»>
 4993    "});
 4994
 4995    cx.set_state(indoc! {"
 4996        teˇst
 4997    "});
 4998    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4999    cx.assert_editor_state(indoc! {"
 5000        te<«ˇ»></«ˇ»>st
 5001    "});
 5002}
 5003
 5004#[gpui::test]
 5005async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5006    init_test(cx, |_| {});
 5007
 5008    let mut cx = EditorTestContext::new(cx).await;
 5009
 5010    let js_language = Arc::new(Language::new(
 5011        LanguageConfig {
 5012            name: "JavaScript".into(),
 5013            wrap_characters: Some(language::WrapCharactersConfig {
 5014                start_prefix: "<".into(),
 5015                start_suffix: ">".into(),
 5016                end_prefix: "</".into(),
 5017                end_suffix: ">".into(),
 5018            }),
 5019            ..LanguageConfig::default()
 5020        },
 5021        None,
 5022    ));
 5023
 5024    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5025
 5026    cx.set_state(indoc! {"
 5027        «testˇ»
 5028        «testˇ» «testˇ»
 5029        «testˇ»
 5030    "});
 5031    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5032    cx.assert_editor_state(indoc! {"
 5033        <«ˇ»>test</«ˇ»>
 5034        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5035        <«ˇ»>test</«ˇ»>
 5036    "});
 5037
 5038    cx.set_state(indoc! {"
 5039        «test
 5040         testˇ»
 5041        «test
 5042         testˇ»
 5043    "});
 5044    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5045    cx.assert_editor_state(indoc! {"
 5046        <«ˇ»>test
 5047         test</«ˇ»>
 5048        <«ˇ»>test
 5049         test</«ˇ»>
 5050    "});
 5051}
 5052
 5053#[gpui::test]
 5054async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5055    init_test(cx, |_| {});
 5056
 5057    let mut cx = EditorTestContext::new(cx).await;
 5058
 5059    let plaintext_language = Arc::new(Language::new(
 5060        LanguageConfig {
 5061            name: "Plain Text".into(),
 5062            ..LanguageConfig::default()
 5063        },
 5064        None,
 5065    ));
 5066
 5067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5068
 5069    cx.set_state(indoc! {"
 5070        «testˇ»
 5071    "});
 5072    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5073    cx.assert_editor_state(indoc! {"
 5074      «testˇ»
 5075    "});
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5080    init_test(cx, |_| {});
 5081
 5082    let mut cx = EditorTestContext::new(cx).await;
 5083
 5084    // Manipulate with multiple selections on a single line
 5085    cx.set_state(indoc! {"
 5086        dd«dd
 5087        cˇ»c«c
 5088        bb
 5089        aaaˇ»aa
 5090    "});
 5091    cx.update_editor(|e, window, cx| {
 5092        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5093    });
 5094    cx.assert_editor_state(indoc! {"
 5095        «aaaaa
 5096        bb
 5097        ccc
 5098        ddddˇ»
 5099    "});
 5100
 5101    // Manipulate with multiple disjoin selections
 5102    cx.set_state(indoc! {"
 5103 5104        4
 5105        3
 5106        2
 5107        1ˇ»
 5108
 5109        dd«dd
 5110        ccc
 5111        bb
 5112        aaaˇ»aa
 5113    "});
 5114    cx.update_editor(|e, window, cx| {
 5115        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5116    });
 5117    cx.assert_editor_state(indoc! {"
 5118        «1
 5119        2
 5120        3
 5121        4
 5122        5ˇ»
 5123
 5124        «aaaaa
 5125        bb
 5126        ccc
 5127        ddddˇ»
 5128    "});
 5129
 5130    // Adding lines on each selection
 5131    cx.set_state(indoc! {"
 5132 5133        1ˇ»
 5134
 5135        bb«bb
 5136        aaaˇ»aa
 5137    "});
 5138    cx.update_editor(|e, window, cx| {
 5139        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5140    });
 5141    cx.assert_editor_state(indoc! {"
 5142        «2
 5143        1
 5144        added lineˇ»
 5145
 5146        «bbbb
 5147        aaaaa
 5148        added lineˇ»
 5149    "});
 5150
 5151    // Removing lines on each selection
 5152    cx.set_state(indoc! {"
 5153 5154        1ˇ»
 5155
 5156        bb«bb
 5157        aaaˇ»aa
 5158    "});
 5159    cx.update_editor(|e, window, cx| {
 5160        e.manipulate_immutable_lines(window, cx, |lines| {
 5161            lines.pop();
 5162        })
 5163    });
 5164    cx.assert_editor_state(indoc! {"
 5165        «2ˇ»
 5166
 5167        «bbbbˇ»
 5168    "});
 5169}
 5170
 5171#[gpui::test]
 5172async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5173    init_test(cx, |settings| {
 5174        settings.defaults.tab_size = NonZeroU32::new(3)
 5175    });
 5176
 5177    let mut cx = EditorTestContext::new(cx).await;
 5178
 5179    // MULTI SELECTION
 5180    // Ln.1 "«" tests empty lines
 5181    // Ln.9 tests just leading whitespace
 5182    cx.set_state(indoc! {"
 5183        «
 5184        abc                 // No indentationˇ»
 5185        «\tabc              // 1 tabˇ»
 5186        \t\tabc «      ˇ»   // 2 tabs
 5187        \t ab«c             // Tab followed by space
 5188         \tabc              // Space followed by tab (3 spaces should be the result)
 5189        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5190           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5191        \t
 5192        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5193    "});
 5194    cx.update_editor(|e, window, cx| {
 5195        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5196    });
 5197    cx.assert_editor_state(
 5198        indoc! {"
 5199            «
 5200            abc                 // No indentation
 5201               abc              // 1 tab
 5202                  abc          // 2 tabs
 5203                abc             // Tab followed by space
 5204               abc              // Space followed by tab (3 spaces should be the result)
 5205                           abc   // Mixed indentation (tab conversion depends on the column)
 5206               abc         // Already space indented
 5207               ·
 5208               abc\tdef          // Only the leading tab is manipulatedˇ»
 5209        "}
 5210        .replace("·", "")
 5211        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5212    );
 5213
 5214    // Test on just a few lines, the others should remain unchanged
 5215    // Only lines (3, 5, 10, 11) should change
 5216    cx.set_state(
 5217        indoc! {"
 5218            ·
 5219            abc                 // No indentation
 5220            \tabcˇ               // 1 tab
 5221            \t\tabc             // 2 tabs
 5222            \t abcˇ              // Tab followed by space
 5223             \tabc              // Space followed by tab (3 spaces should be the result)
 5224            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5225               abc              // Already space indented
 5226            «\t
 5227            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5228        "}
 5229        .replace("·", "")
 5230        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5231    );
 5232    cx.update_editor(|e, window, cx| {
 5233        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5234    });
 5235    cx.assert_editor_state(
 5236        indoc! {"
 5237            ·
 5238            abc                 // No indentation
 5239            «   abc               // 1 tabˇ»
 5240            \t\tabc             // 2 tabs
 5241            «    abc              // Tab followed by spaceˇ»
 5242             \tabc              // Space followed by tab (3 spaces should be the result)
 5243            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5244               abc              // Already space indented
 5245            «   ·
 5246               abc\tdef          // Only the leading tab is manipulatedˇ»
 5247        "}
 5248        .replace("·", "")
 5249        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5250    );
 5251
 5252    // SINGLE SELECTION
 5253    // Ln.1 "«" tests empty lines
 5254    // Ln.9 tests just leading whitespace
 5255    cx.set_state(indoc! {"
 5256        «
 5257        abc                 // No indentation
 5258        \tabc               // 1 tab
 5259        \t\tabc             // 2 tabs
 5260        \t abc              // Tab followed by space
 5261         \tabc              // Space followed by tab (3 spaces should be the result)
 5262        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5263           abc              // Already space indented
 5264        \t
 5265        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| {
 5268        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5269    });
 5270    cx.assert_editor_state(
 5271        indoc! {"
 5272            «
 5273            abc                 // No indentation
 5274               abc               // 1 tab
 5275                  abc             // 2 tabs
 5276                abc              // Tab followed by space
 5277               abc              // Space followed by tab (3 spaces should be the result)
 5278                           abc   // Mixed indentation (tab conversion depends on the column)
 5279               abc              // Already space indented
 5280               ·
 5281               abc\tdef          // Only the leading tab is manipulatedˇ»
 5282        "}
 5283        .replace("·", "")
 5284        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5285    );
 5286}
 5287
 5288#[gpui::test]
 5289async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5290    init_test(cx, |settings| {
 5291        settings.defaults.tab_size = NonZeroU32::new(3)
 5292    });
 5293
 5294    let mut cx = EditorTestContext::new(cx).await;
 5295
 5296    // MULTI SELECTION
 5297    // Ln.1 "«" tests empty lines
 5298    // Ln.11 tests just leading whitespace
 5299    cx.set_state(indoc! {"
 5300        «
 5301        abˇ»ˇc                 // No indentation
 5302         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5303          abc  «             // 2 spaces (< 3 so dont convert)
 5304           abc              // 3 spaces (convert)
 5305             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5306        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5307        «\t abc              // Tab followed by space
 5308         \tabc              // Space followed by tab (should be consumed due to tab)
 5309        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5310           \tˇ»  «\t
 5311           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5312    "});
 5313    cx.update_editor(|e, window, cx| {
 5314        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5315    });
 5316    cx.assert_editor_state(indoc! {"
 5317        «
 5318        abc                 // No indentation
 5319         abc                // 1 space (< 3 so dont convert)
 5320          abc               // 2 spaces (< 3 so dont convert)
 5321        \tabc              // 3 spaces (convert)
 5322        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5323        \t\t\tabc           // Already tab indented
 5324        \t abc              // Tab followed by space
 5325        \tabc              // Space followed by tab (should be consumed due to tab)
 5326        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5327        \t\t\t
 5328        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5329    "});
 5330
 5331    // Test on just a few lines, the other should remain unchanged
 5332    // Only lines (4, 8, 11, 12) should change
 5333    cx.set_state(
 5334        indoc! {"
 5335            ·
 5336            abc                 // No indentation
 5337             abc                // 1 space (< 3 so dont convert)
 5338              abc               // 2 spaces (< 3 so dont convert)
 5339            «   abc              // 3 spaces (convert)ˇ»
 5340                 abc            // 5 spaces (1 tab + 2 spaces)
 5341            \t\t\tabc           // Already tab indented
 5342            \t abc              // Tab followed by space
 5343             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5344               \t\t  \tabc      // Mixed indentation
 5345            \t \t  \t   \tabc   // Mixed indentation
 5346               \t  \tˇ
 5347            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5348        "}
 5349        .replace("·", "")
 5350        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5351    );
 5352    cx.update_editor(|e, window, cx| {
 5353        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5354    });
 5355    cx.assert_editor_state(
 5356        indoc! {"
 5357            ·
 5358            abc                 // No indentation
 5359             abc                // 1 space (< 3 so dont convert)
 5360              abc               // 2 spaces (< 3 so dont convert)
 5361            «\tabc              // 3 spaces (convert)ˇ»
 5362                 abc            // 5 spaces (1 tab + 2 spaces)
 5363            \t\t\tabc           // Already tab indented
 5364            \t abc              // Tab followed by space
 5365            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5366               \t\t  \tabc      // Mixed indentation
 5367            \t \t  \t   \tabc   // Mixed indentation
 5368            «\t\t\t
 5369            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5370        "}
 5371        .replace("·", "")
 5372        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5373    );
 5374
 5375    // SINGLE SELECTION
 5376    // Ln.1 "«" tests empty lines
 5377    // Ln.11 tests just leading whitespace
 5378    cx.set_state(indoc! {"
 5379        «
 5380        abc                 // No indentation
 5381         abc                // 1 space (< 3 so dont convert)
 5382          abc               // 2 spaces (< 3 so dont convert)
 5383           abc              // 3 spaces (convert)
 5384             abc            // 5 spaces (1 tab + 2 spaces)
 5385        \t\t\tabc           // Already tab indented
 5386        \t abc              // Tab followed by space
 5387         \tabc              // Space followed by tab (should be consumed due to tab)
 5388        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5389           \t  \t
 5390           abc   \t         // Only the leading spaces should be convertedˇ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| {
 5393        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5394    });
 5395    cx.assert_editor_state(indoc! {"
 5396        «
 5397        abc                 // No indentation
 5398         abc                // 1 space (< 3 so dont convert)
 5399          abc               // 2 spaces (< 3 so dont convert)
 5400        \tabc              // 3 spaces (convert)
 5401        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5402        \t\t\tabc           // Already tab indented
 5403        \t abc              // Tab followed by space
 5404        \tabc              // Space followed by tab (should be consumed due to tab)
 5405        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5406        \t\t\t
 5407        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5408    "});
 5409}
 5410
 5411#[gpui::test]
 5412async fn test_toggle_case(cx: &mut TestAppContext) {
 5413    init_test(cx, |_| {});
 5414
 5415    let mut cx = EditorTestContext::new(cx).await;
 5416
 5417    // If all lower case -> upper case
 5418    cx.set_state(indoc! {"
 5419        «hello worldˇ»
 5420    "});
 5421    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5422    cx.assert_editor_state(indoc! {"
 5423        «HELLO WORLDˇ»
 5424    "});
 5425
 5426    // If all upper case -> lower case
 5427    cx.set_state(indoc! {"
 5428        «HELLO WORLDˇ»
 5429    "});
 5430    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5431    cx.assert_editor_state(indoc! {"
 5432        «hello worldˇ»
 5433    "});
 5434
 5435    // If any upper case characters are identified -> lower case
 5436    // This matches JetBrains IDEs
 5437    cx.set_state(indoc! {"
 5438        «hEllo worldˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5441    cx.assert_editor_state(indoc! {"
 5442        «hello worldˇ»
 5443    "});
 5444}
 5445
 5446#[gpui::test]
 5447async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5448    init_test(cx, |_| {});
 5449
 5450    let mut cx = EditorTestContext::new(cx).await;
 5451
 5452    cx.set_state(indoc! {"
 5453        «implement-windows-supportˇ»
 5454    "});
 5455    cx.update_editor(|e, window, cx| {
 5456        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5457    });
 5458    cx.assert_editor_state(indoc! {"
 5459        «Implement windows supportˇ»
 5460    "});
 5461}
 5462
 5463#[gpui::test]
 5464async fn test_manipulate_text(cx: &mut TestAppContext) {
 5465    init_test(cx, |_| {});
 5466
 5467    let mut cx = EditorTestContext::new(cx).await;
 5468
 5469    // Test convert_to_upper_case()
 5470    cx.set_state(indoc! {"
 5471        «hello worldˇ»
 5472    "});
 5473    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5474    cx.assert_editor_state(indoc! {"
 5475        «HELLO WORLDˇ»
 5476    "});
 5477
 5478    // Test convert_to_lower_case()
 5479    cx.set_state(indoc! {"
 5480        «HELLO WORLDˇ»
 5481    "});
 5482    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5483    cx.assert_editor_state(indoc! {"
 5484        «hello worldˇ»
 5485    "});
 5486
 5487    // Test multiple line, single selection case
 5488    cx.set_state(indoc! {"
 5489        «The quick brown
 5490        fox jumps over
 5491        the lazy dogˇ»
 5492    "});
 5493    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5494    cx.assert_editor_state(indoc! {"
 5495        «The Quick Brown
 5496        Fox Jumps Over
 5497        The Lazy Dogˇ»
 5498    "});
 5499
 5500    // Test multiple line, single selection case
 5501    cx.set_state(indoc! {"
 5502        «The quick brown
 5503        fox jumps over
 5504        the lazy dogˇ»
 5505    "});
 5506    cx.update_editor(|e, window, cx| {
 5507        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5508    });
 5509    cx.assert_editor_state(indoc! {"
 5510        «TheQuickBrown
 5511        FoxJumpsOver
 5512        TheLazyDogˇ»
 5513    "});
 5514
 5515    // From here on out, test more complex cases of manipulate_text()
 5516
 5517    // Test no selection case - should affect words cursors are in
 5518    // Cursor at beginning, middle, and end of word
 5519    cx.set_state(indoc! {"
 5520        ˇhello big beauˇtiful worldˇ
 5521    "});
 5522    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5523    cx.assert_editor_state(indoc! {"
 5524        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5525    "});
 5526
 5527    // Test multiple selections on a single line and across multiple lines
 5528    cx.set_state(indoc! {"
 5529        «Theˇ» quick «brown
 5530        foxˇ» jumps «overˇ»
 5531        the «lazyˇ» dog
 5532    "});
 5533    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5534    cx.assert_editor_state(indoc! {"
 5535        «THEˇ» quick «BROWN
 5536        FOXˇ» jumps «OVERˇ»
 5537        the «LAZYˇ» dog
 5538    "});
 5539
 5540    // Test case where text length grows
 5541    cx.set_state(indoc! {"
 5542        «tschüߡ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «TSCHÜSSˇ»
 5547    "});
 5548
 5549    // Test to make sure we don't crash when text shrinks
 5550    cx.set_state(indoc! {"
 5551        aaa_bbbˇ
 5552    "});
 5553    cx.update_editor(|e, window, cx| {
 5554        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5555    });
 5556    cx.assert_editor_state(indoc! {"
 5557        «aaaBbbˇ»
 5558    "});
 5559
 5560    // Test to make sure we all aware of the fact that each word can grow and shrink
 5561    // Final selections should be aware of this fact
 5562    cx.set_state(indoc! {"
 5563        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5564    "});
 5565    cx.update_editor(|e, window, cx| {
 5566        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5567    });
 5568    cx.assert_editor_state(indoc! {"
 5569        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5570    "});
 5571
 5572    cx.set_state(indoc! {"
 5573        «hElLo, WoRld!ˇ»
 5574    "});
 5575    cx.update_editor(|e, window, cx| {
 5576        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5577    });
 5578    cx.assert_editor_state(indoc! {"
 5579        «HeLlO, wOrLD!ˇ»
 5580    "});
 5581
 5582    // Test selections with `line_mode() = true`.
 5583    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5584    cx.set_state(indoc! {"
 5585        «The quick brown
 5586        fox jumps over
 5587        tˇ»he lazy dog
 5588    "});
 5589    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        «THE QUICK BROWN
 5592        FOX JUMPS OVER
 5593        THE LAZY DOGˇ»
 5594    "});
 5595}
 5596
 5597#[gpui::test]
 5598fn test_duplicate_line(cx: &mut TestAppContext) {
 5599    init_test(cx, |_| {});
 5600
 5601    let editor = cx.add_window(|window, cx| {
 5602        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5603        build_editor(buffer, window, cx)
 5604    });
 5605    _ = editor.update(cx, |editor, window, cx| {
 5606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5607            s.select_display_ranges([
 5608                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5609                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5610                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5611                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5612            ])
 5613        });
 5614        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5615        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5616        assert_eq!(
 5617            editor.selections.display_ranges(cx),
 5618            vec![
 5619                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5621                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5622                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5623            ]
 5624        );
 5625    });
 5626
 5627    let editor = cx.add_window(|window, cx| {
 5628        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5629        build_editor(buffer, window, cx)
 5630    });
 5631    _ = editor.update(cx, |editor, window, cx| {
 5632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5633            s.select_display_ranges([
 5634                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5635                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5636            ])
 5637        });
 5638        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5639        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5640        assert_eq!(
 5641            editor.selections.display_ranges(cx),
 5642            vec![
 5643                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5644                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5645            ]
 5646        );
 5647    });
 5648
 5649    // With `move_upwards` the selections stay in place, except for
 5650    // the lines inserted above them
 5651    let editor = cx.add_window(|window, cx| {
 5652        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5653        build_editor(buffer, window, cx)
 5654    });
 5655    _ = editor.update(cx, |editor, window, cx| {
 5656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5657            s.select_display_ranges([
 5658                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5659                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5660                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5661                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5662            ])
 5663        });
 5664        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5665        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5666        assert_eq!(
 5667            editor.selections.display_ranges(cx),
 5668            vec![
 5669                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5670                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5671                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5672                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5673            ]
 5674        );
 5675    });
 5676
 5677    let editor = cx.add_window(|window, cx| {
 5678        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5679        build_editor(buffer, window, cx)
 5680    });
 5681    _ = editor.update(cx, |editor, window, cx| {
 5682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5683            s.select_display_ranges([
 5684                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5685                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5686            ])
 5687        });
 5688        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5689        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5690        assert_eq!(
 5691            editor.selections.display_ranges(cx),
 5692            vec![
 5693                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5694                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5695            ]
 5696        );
 5697    });
 5698
 5699    let editor = cx.add_window(|window, cx| {
 5700        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5701        build_editor(buffer, window, cx)
 5702    });
 5703    _ = editor.update(cx, |editor, window, cx| {
 5704        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5705            s.select_display_ranges([
 5706                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5708            ])
 5709        });
 5710        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5711        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5712        assert_eq!(
 5713            editor.selections.display_ranges(cx),
 5714            vec![
 5715                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5716                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5717            ]
 5718        );
 5719    });
 5720}
 5721
 5722#[gpui::test]
 5723fn test_move_line_up_down(cx: &mut TestAppContext) {
 5724    init_test(cx, |_| {});
 5725
 5726    let editor = cx.add_window(|window, cx| {
 5727        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5728        build_editor(buffer, window, cx)
 5729    });
 5730    _ = editor.update(cx, |editor, window, cx| {
 5731        editor.fold_creases(
 5732            vec![
 5733                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5734                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5735                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5736            ],
 5737            true,
 5738            window,
 5739            cx,
 5740        );
 5741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5742            s.select_display_ranges([
 5743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5744                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5745                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5746                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5747            ])
 5748        });
 5749        assert_eq!(
 5750            editor.display_text(cx),
 5751            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5752        );
 5753
 5754        editor.move_line_up(&MoveLineUp, window, cx);
 5755        assert_eq!(
 5756            editor.display_text(cx),
 5757            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5758        );
 5759        assert_eq!(
 5760            editor.selections.display_ranges(cx),
 5761            vec![
 5762                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5763                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5764                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5765                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5766            ]
 5767        );
 5768    });
 5769
 5770    _ = editor.update(cx, |editor, window, cx| {
 5771        editor.move_line_down(&MoveLineDown, window, cx);
 5772        assert_eq!(
 5773            editor.display_text(cx),
 5774            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5775        );
 5776        assert_eq!(
 5777            editor.selections.display_ranges(cx),
 5778            vec![
 5779                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5780                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5781                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5782                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5783            ]
 5784        );
 5785    });
 5786
 5787    _ = editor.update(cx, |editor, window, cx| {
 5788        editor.move_line_down(&MoveLineDown, window, cx);
 5789        assert_eq!(
 5790            editor.display_text(cx),
 5791            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5792        );
 5793        assert_eq!(
 5794            editor.selections.display_ranges(cx),
 5795            vec![
 5796                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5797                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5798                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5799                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5800            ]
 5801        );
 5802    });
 5803
 5804    _ = editor.update(cx, |editor, window, cx| {
 5805        editor.move_line_up(&MoveLineUp, window, cx);
 5806        assert_eq!(
 5807            editor.display_text(cx),
 5808            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5809        );
 5810        assert_eq!(
 5811            editor.selections.display_ranges(cx),
 5812            vec![
 5813                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5814                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5815                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5816                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5817            ]
 5818        );
 5819    });
 5820}
 5821
 5822#[gpui::test]
 5823fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5824    init_test(cx, |_| {});
 5825    let editor = cx.add_window(|window, cx| {
 5826        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5827        build_editor(buffer, window, cx)
 5828    });
 5829    _ = editor.update(cx, |editor, window, cx| {
 5830        editor.fold_creases(
 5831            vec![Crease::simple(
 5832                Point::new(6, 4)..Point::new(7, 4),
 5833                FoldPlaceholder::test(),
 5834            )],
 5835            true,
 5836            window,
 5837            cx,
 5838        );
 5839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5840            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5841        });
 5842        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5843        editor.move_line_up(&MoveLineUp, window, cx);
 5844        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5845        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5846    });
 5847}
 5848
 5849#[gpui::test]
 5850fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5851    init_test(cx, |_| {});
 5852
 5853    let editor = cx.add_window(|window, cx| {
 5854        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5855        build_editor(buffer, window, cx)
 5856    });
 5857    _ = editor.update(cx, |editor, window, cx| {
 5858        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5859        editor.insert_blocks(
 5860            [BlockProperties {
 5861                style: BlockStyle::Fixed,
 5862                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5863                height: Some(1),
 5864                render: Arc::new(|_| div().into_any()),
 5865                priority: 0,
 5866            }],
 5867            Some(Autoscroll::fit()),
 5868            cx,
 5869        );
 5870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5871            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5872        });
 5873        editor.move_line_down(&MoveLineDown, window, cx);
 5874    });
 5875}
 5876
 5877#[gpui::test]
 5878async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5879    init_test(cx, |_| {});
 5880
 5881    let mut cx = EditorTestContext::new(cx).await;
 5882    cx.set_state(
 5883        &"
 5884            ˇzero
 5885            one
 5886            two
 5887            three
 5888            four
 5889            five
 5890        "
 5891        .unindent(),
 5892    );
 5893
 5894    // Create a four-line block that replaces three lines of text.
 5895    cx.update_editor(|editor, window, cx| {
 5896        let snapshot = editor.snapshot(window, cx);
 5897        let snapshot = &snapshot.buffer_snapshot();
 5898        let placement = BlockPlacement::Replace(
 5899            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5900        );
 5901        editor.insert_blocks(
 5902            [BlockProperties {
 5903                placement,
 5904                height: Some(4),
 5905                style: BlockStyle::Sticky,
 5906                render: Arc::new(|_| gpui::div().into_any_element()),
 5907                priority: 0,
 5908            }],
 5909            None,
 5910            cx,
 5911        );
 5912    });
 5913
 5914    // Move down so that the cursor touches the block.
 5915    cx.update_editor(|editor, window, cx| {
 5916        editor.move_down(&Default::default(), window, cx);
 5917    });
 5918    cx.assert_editor_state(
 5919        &"
 5920            zero
 5921            «one
 5922            two
 5923            threeˇ»
 5924            four
 5925            five
 5926        "
 5927        .unindent(),
 5928    );
 5929
 5930    // Move down past the block.
 5931    cx.update_editor(|editor, window, cx| {
 5932        editor.move_down(&Default::default(), window, cx);
 5933    });
 5934    cx.assert_editor_state(
 5935        &"
 5936            zero
 5937            one
 5938            two
 5939            three
 5940            ˇfour
 5941            five
 5942        "
 5943        .unindent(),
 5944    );
 5945}
 5946
 5947#[gpui::test]
 5948fn test_transpose(cx: &mut TestAppContext) {
 5949    init_test(cx, |_| {});
 5950
 5951    _ = cx.add_window(|window, cx| {
 5952        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5953        editor.set_style(EditorStyle::default(), window, cx);
 5954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5955            s.select_ranges([1..1])
 5956        });
 5957        editor.transpose(&Default::default(), window, cx);
 5958        assert_eq!(editor.text(cx), "bac");
 5959        assert_eq!(
 5960            editor.selections.ranges(&editor.display_snapshot(cx)),
 5961            [2..2]
 5962        );
 5963
 5964        editor.transpose(&Default::default(), window, cx);
 5965        assert_eq!(editor.text(cx), "bca");
 5966        assert_eq!(
 5967            editor.selections.ranges(&editor.display_snapshot(cx)),
 5968            [3..3]
 5969        );
 5970
 5971        editor.transpose(&Default::default(), window, cx);
 5972        assert_eq!(editor.text(cx), "bac");
 5973        assert_eq!(
 5974            editor.selections.ranges(&editor.display_snapshot(cx)),
 5975            [3..3]
 5976        );
 5977
 5978        editor
 5979    });
 5980
 5981    _ = cx.add_window(|window, cx| {
 5982        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5983        editor.set_style(EditorStyle::default(), window, cx);
 5984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5985            s.select_ranges([3..3])
 5986        });
 5987        editor.transpose(&Default::default(), window, cx);
 5988        assert_eq!(editor.text(cx), "acb\nde");
 5989        assert_eq!(
 5990            editor.selections.ranges(&editor.display_snapshot(cx)),
 5991            [3..3]
 5992        );
 5993
 5994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5995            s.select_ranges([4..4])
 5996        });
 5997        editor.transpose(&Default::default(), window, cx);
 5998        assert_eq!(editor.text(cx), "acbd\ne");
 5999        assert_eq!(
 6000            editor.selections.ranges(&editor.display_snapshot(cx)),
 6001            [5..5]
 6002        );
 6003
 6004        editor.transpose(&Default::default(), window, cx);
 6005        assert_eq!(editor.text(cx), "acbde\n");
 6006        assert_eq!(
 6007            editor.selections.ranges(&editor.display_snapshot(cx)),
 6008            [6..6]
 6009        );
 6010
 6011        editor.transpose(&Default::default(), window, cx);
 6012        assert_eq!(editor.text(cx), "acbd\ne");
 6013        assert_eq!(
 6014            editor.selections.ranges(&editor.display_snapshot(cx)),
 6015            [6..6]
 6016        );
 6017
 6018        editor
 6019    });
 6020
 6021    _ = cx.add_window(|window, cx| {
 6022        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6023        editor.set_style(EditorStyle::default(), window, cx);
 6024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6025            s.select_ranges([1..1, 2..2, 4..4])
 6026        });
 6027        editor.transpose(&Default::default(), window, cx);
 6028        assert_eq!(editor.text(cx), "bacd\ne");
 6029        assert_eq!(
 6030            editor.selections.ranges(&editor.display_snapshot(cx)),
 6031            [2..2, 3..3, 5..5]
 6032        );
 6033
 6034        editor.transpose(&Default::default(), window, cx);
 6035        assert_eq!(editor.text(cx), "bcade\n");
 6036        assert_eq!(
 6037            editor.selections.ranges(&editor.display_snapshot(cx)),
 6038            [3..3, 4..4, 6..6]
 6039        );
 6040
 6041        editor.transpose(&Default::default(), window, cx);
 6042        assert_eq!(editor.text(cx), "bcda\ne");
 6043        assert_eq!(
 6044            editor.selections.ranges(&editor.display_snapshot(cx)),
 6045            [4..4, 6..6]
 6046        );
 6047
 6048        editor.transpose(&Default::default(), window, cx);
 6049        assert_eq!(editor.text(cx), "bcade\n");
 6050        assert_eq!(
 6051            editor.selections.ranges(&editor.display_snapshot(cx)),
 6052            [4..4, 6..6]
 6053        );
 6054
 6055        editor.transpose(&Default::default(), window, cx);
 6056        assert_eq!(editor.text(cx), "bcaed\n");
 6057        assert_eq!(
 6058            editor.selections.ranges(&editor.display_snapshot(cx)),
 6059            [5..5, 6..6]
 6060        );
 6061
 6062        editor
 6063    });
 6064
 6065    _ = cx.add_window(|window, cx| {
 6066        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6067        editor.set_style(EditorStyle::default(), window, cx);
 6068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6069            s.select_ranges([4..4])
 6070        });
 6071        editor.transpose(&Default::default(), window, cx);
 6072        assert_eq!(editor.text(cx), "🏀🍐✋");
 6073        assert_eq!(
 6074            editor.selections.ranges(&editor.display_snapshot(cx)),
 6075            [8..8]
 6076        );
 6077
 6078        editor.transpose(&Default::default(), window, cx);
 6079        assert_eq!(editor.text(cx), "🏀✋🍐");
 6080        assert_eq!(
 6081            editor.selections.ranges(&editor.display_snapshot(cx)),
 6082            [11..11]
 6083        );
 6084
 6085        editor.transpose(&Default::default(), window, cx);
 6086        assert_eq!(editor.text(cx), "🏀🍐✋");
 6087        assert_eq!(
 6088            editor.selections.ranges(&editor.display_snapshot(cx)),
 6089            [11..11]
 6090        );
 6091
 6092        editor
 6093    });
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_rewrap(cx: &mut TestAppContext) {
 6098    init_test(cx, |settings| {
 6099        settings.languages.0.extend([
 6100            (
 6101                "Markdown".into(),
 6102                LanguageSettingsContent {
 6103                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6104                    preferred_line_length: Some(40),
 6105                    ..Default::default()
 6106                },
 6107            ),
 6108            (
 6109                "Plain Text".into(),
 6110                LanguageSettingsContent {
 6111                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6112                    preferred_line_length: Some(40),
 6113                    ..Default::default()
 6114                },
 6115            ),
 6116            (
 6117                "C++".into(),
 6118                LanguageSettingsContent {
 6119                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6120                    preferred_line_length: Some(40),
 6121                    ..Default::default()
 6122                },
 6123            ),
 6124            (
 6125                "Python".into(),
 6126                LanguageSettingsContent {
 6127                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6128                    preferred_line_length: Some(40),
 6129                    ..Default::default()
 6130                },
 6131            ),
 6132            (
 6133                "Rust".into(),
 6134                LanguageSettingsContent {
 6135                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6136                    preferred_line_length: Some(40),
 6137                    ..Default::default()
 6138                },
 6139            ),
 6140        ])
 6141    });
 6142
 6143    let mut cx = EditorTestContext::new(cx).await;
 6144
 6145    let cpp_language = Arc::new(Language::new(
 6146        LanguageConfig {
 6147            name: "C++".into(),
 6148            line_comments: vec!["// ".into()],
 6149            ..LanguageConfig::default()
 6150        },
 6151        None,
 6152    ));
 6153    let python_language = Arc::new(Language::new(
 6154        LanguageConfig {
 6155            name: "Python".into(),
 6156            line_comments: vec!["# ".into()],
 6157            ..LanguageConfig::default()
 6158        },
 6159        None,
 6160    ));
 6161    let markdown_language = Arc::new(Language::new(
 6162        LanguageConfig {
 6163            name: "Markdown".into(),
 6164            rewrap_prefixes: vec![
 6165                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6166                regex::Regex::new("[-*+]\\s+").unwrap(),
 6167            ],
 6168            ..LanguageConfig::default()
 6169        },
 6170        None,
 6171    ));
 6172    let rust_language = Arc::new(
 6173        Language::new(
 6174            LanguageConfig {
 6175                name: "Rust".into(),
 6176                line_comments: vec!["// ".into(), "/// ".into()],
 6177                ..LanguageConfig::default()
 6178            },
 6179            Some(tree_sitter_rust::LANGUAGE.into()),
 6180        )
 6181        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6182        .unwrap(),
 6183    );
 6184
 6185    let plaintext_language = Arc::new(Language::new(
 6186        LanguageConfig {
 6187            name: "Plain Text".into(),
 6188            ..LanguageConfig::default()
 6189        },
 6190        None,
 6191    ));
 6192
 6193    // Test basic rewrapping of a long line with a cursor
 6194    assert_rewrap(
 6195        indoc! {"
 6196            // ˇThis is a long comment that needs to be wrapped.
 6197        "},
 6198        indoc! {"
 6199            // ˇThis is a long comment that needs to
 6200            // be wrapped.
 6201        "},
 6202        cpp_language.clone(),
 6203        &mut cx,
 6204    );
 6205
 6206    // Test rewrapping a full selection
 6207    assert_rewrap(
 6208        indoc! {"
 6209            «// This selected long comment needs to be wrapped.ˇ»"
 6210        },
 6211        indoc! {"
 6212            «// This selected long comment needs to
 6213            // be wrapped.ˇ»"
 6214        },
 6215        cpp_language.clone(),
 6216        &mut cx,
 6217    );
 6218
 6219    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6220    assert_rewrap(
 6221        indoc! {"
 6222            // ˇThis is the first line.
 6223            // Thisˇ is the second line.
 6224            // This is the thirdˇ line, all part of one paragraph.
 6225         "},
 6226        indoc! {"
 6227            // ˇThis is the first line. Thisˇ is the
 6228            // second line. This is the thirdˇ line,
 6229            // all part of one paragraph.
 6230         "},
 6231        cpp_language.clone(),
 6232        &mut cx,
 6233    );
 6234
 6235    // Test multiple cursors in different paragraphs trigger separate rewraps
 6236    assert_rewrap(
 6237        indoc! {"
 6238            // ˇThis is the first paragraph, first line.
 6239            // ˇThis is the first paragraph, second line.
 6240
 6241            // ˇThis is the second paragraph, first line.
 6242            // ˇThis is the second paragraph, second line.
 6243        "},
 6244        indoc! {"
 6245            // ˇThis is the first paragraph, first
 6246            // line. ˇThis is the first paragraph,
 6247            // second line.
 6248
 6249            // ˇThis is the second paragraph, first
 6250            // line. ˇThis is the second paragraph,
 6251            // second line.
 6252        "},
 6253        cpp_language.clone(),
 6254        &mut cx,
 6255    );
 6256
 6257    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6258    assert_rewrap(
 6259        indoc! {"
 6260            «// A regular long long comment to be wrapped.
 6261            /// A documentation long comment to be wrapped.ˇ»
 6262          "},
 6263        indoc! {"
 6264            «// A regular long long comment to be
 6265            // wrapped.
 6266            /// A documentation long comment to be
 6267            /// wrapped.ˇ»
 6268          "},
 6269        rust_language.clone(),
 6270        &mut cx,
 6271    );
 6272
 6273    // Test that change in indentation level trigger seperate rewraps
 6274    assert_rewrap(
 6275        indoc! {"
 6276            fn foo() {
 6277                «// This is a long comment at the base indent.
 6278                    // This is a long comment at the next indent.ˇ»
 6279            }
 6280        "},
 6281        indoc! {"
 6282            fn foo() {
 6283                «// This is a long comment at the
 6284                // base indent.
 6285                    // This is a long comment at the
 6286                    // next indent.ˇ»
 6287            }
 6288        "},
 6289        rust_language.clone(),
 6290        &mut cx,
 6291    );
 6292
 6293    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6294    assert_rewrap(
 6295        indoc! {"
 6296            # ˇThis is a long comment using a pound sign.
 6297        "},
 6298        indoc! {"
 6299            # ˇThis is a long comment using a pound
 6300            # sign.
 6301        "},
 6302        python_language,
 6303        &mut cx,
 6304    );
 6305
 6306    // Test rewrapping only affects comments, not code even when selected
 6307    assert_rewrap(
 6308        indoc! {"
 6309            «/// This doc comment is long and should be wrapped.
 6310            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6311        "},
 6312        indoc! {"
 6313            «/// This doc comment is long and should
 6314            /// be wrapped.
 6315            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6316        "},
 6317        rust_language.clone(),
 6318        &mut cx,
 6319    );
 6320
 6321    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6322    assert_rewrap(
 6323        indoc! {"
 6324            # Header
 6325
 6326            A long long long line of markdown text to wrap.ˇ
 6327         "},
 6328        indoc! {"
 6329            # Header
 6330
 6331            A long long long line of markdown text
 6332            to wrap.ˇ
 6333         "},
 6334        markdown_language.clone(),
 6335        &mut cx,
 6336    );
 6337
 6338    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6339    assert_rewrap(
 6340        indoc! {"
 6341            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6342            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6343            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6344        "},
 6345        indoc! {"
 6346            «1. This is a numbered list item that is
 6347               very long and needs to be wrapped
 6348               properly.
 6349            2. This is a numbered list item that is
 6350               very long and needs to be wrapped
 6351               properly.
 6352            - This is an unordered list item that is
 6353              also very long and should not merge
 6354              with the numbered item.ˇ»
 6355        "},
 6356        markdown_language.clone(),
 6357        &mut cx,
 6358    );
 6359
 6360    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6361    assert_rewrap(
 6362        indoc! {"
 6363            «1. This is a numbered list item that is
 6364            very long and needs to be wrapped
 6365            properly.
 6366            2. This is a numbered list item that is
 6367            very long and needs to be wrapped
 6368            properly.
 6369            - This is an unordered list item that is
 6370            also very long and should not merge with
 6371            the numbered item.ˇ»
 6372        "},
 6373        indoc! {"
 6374            «1. This is a numbered list item that is
 6375               very long and needs to be wrapped
 6376               properly.
 6377            2. This is a numbered list item that is
 6378               very long and needs to be wrapped
 6379               properly.
 6380            - This is an unordered list item that is
 6381              also very long and should not merge
 6382              with the numbered item.ˇ»
 6383        "},
 6384        markdown_language.clone(),
 6385        &mut cx,
 6386    );
 6387
 6388    // Test that rewrapping maintain indents even when they already exists.
 6389    assert_rewrap(
 6390        indoc! {"
 6391            «1. This is a numbered list
 6392               item that is very long and needs to be wrapped properly.
 6393            2. This is a numbered list
 6394               item that is very long and needs to be wrapped properly.
 6395            - This is an unordered list item that is also very long and
 6396              should not merge with the numbered item.ˇ»
 6397        "},
 6398        indoc! {"
 6399            «1. This is a numbered list item that is
 6400               very long and needs to be wrapped
 6401               properly.
 6402            2. This is a numbered list item that is
 6403               very long and needs to be wrapped
 6404               properly.
 6405            - This is an unordered list item that is
 6406              also very long and should not merge
 6407              with the numbered item.ˇ»
 6408        "},
 6409        markdown_language,
 6410        &mut cx,
 6411    );
 6412
 6413    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6414    assert_rewrap(
 6415        indoc! {"
 6416            ˇThis is a very long line of plain text that will be wrapped.
 6417        "},
 6418        indoc! {"
 6419            ˇThis is a very long line of plain text
 6420            that will be wrapped.
 6421        "},
 6422        plaintext_language.clone(),
 6423        &mut cx,
 6424    );
 6425
 6426    // Test that non-commented code acts as a paragraph boundary within a selection
 6427    assert_rewrap(
 6428        indoc! {"
 6429               «// This is the first long comment block to be wrapped.
 6430               fn my_func(a: u32);
 6431               // This is the second long comment block to be wrapped.ˇ»
 6432           "},
 6433        indoc! {"
 6434               «// This is the first long comment block
 6435               // to be wrapped.
 6436               fn my_func(a: u32);
 6437               // This is the second long comment block
 6438               // to be wrapped.ˇ»
 6439           "},
 6440        rust_language,
 6441        &mut cx,
 6442    );
 6443
 6444    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6445    assert_rewrap(
 6446        indoc! {"
 6447            «ˇThis is a very long line that will be wrapped.
 6448
 6449            This is another paragraph in the same selection.»
 6450
 6451            «\tThis is a very long indented line that will be wrapped.ˇ»
 6452         "},
 6453        indoc! {"
 6454            «ˇThis is a very long line that will be
 6455            wrapped.
 6456
 6457            This is another paragraph in the same
 6458            selection.»
 6459
 6460            «\tThis is a very long indented line
 6461            \tthat will be wrapped.ˇ»
 6462         "},
 6463        plaintext_language,
 6464        &mut cx,
 6465    );
 6466
 6467    // Test that an empty comment line acts as a paragraph boundary
 6468    assert_rewrap(
 6469        indoc! {"
 6470            // ˇThis is a long comment that will be wrapped.
 6471            //
 6472            // And this is another long comment that will also be wrapped.ˇ
 6473         "},
 6474        indoc! {"
 6475            // ˇThis is a long comment that will be
 6476            // wrapped.
 6477            //
 6478            // And this is another long comment that
 6479            // will also be wrapped.ˇ
 6480         "},
 6481        cpp_language,
 6482        &mut cx,
 6483    );
 6484
 6485    #[track_caller]
 6486    fn assert_rewrap(
 6487        unwrapped_text: &str,
 6488        wrapped_text: &str,
 6489        language: Arc<Language>,
 6490        cx: &mut EditorTestContext,
 6491    ) {
 6492        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6493        cx.set_state(unwrapped_text);
 6494        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6495        cx.assert_editor_state(wrapped_text);
 6496    }
 6497}
 6498
 6499#[gpui::test]
 6500async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6501    init_test(cx, |settings| {
 6502        settings.languages.0.extend([(
 6503            "Rust".into(),
 6504            LanguageSettingsContent {
 6505                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6506                preferred_line_length: Some(40),
 6507                ..Default::default()
 6508            },
 6509        )])
 6510    });
 6511
 6512    let mut cx = EditorTestContext::new(cx).await;
 6513
 6514    let rust_lang = Arc::new(
 6515        Language::new(
 6516            LanguageConfig {
 6517                name: "Rust".into(),
 6518                line_comments: vec!["// ".into()],
 6519                block_comment: Some(BlockCommentConfig {
 6520                    start: "/*".into(),
 6521                    end: "*/".into(),
 6522                    prefix: "* ".into(),
 6523                    tab_size: 1,
 6524                }),
 6525                documentation_comment: Some(BlockCommentConfig {
 6526                    start: "/**".into(),
 6527                    end: "*/".into(),
 6528                    prefix: "* ".into(),
 6529                    tab_size: 1,
 6530                }),
 6531
 6532                ..LanguageConfig::default()
 6533            },
 6534            Some(tree_sitter_rust::LANGUAGE.into()),
 6535        )
 6536        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6537        .unwrap(),
 6538    );
 6539
 6540    // regular block comment
 6541    assert_rewrap(
 6542        indoc! {"
 6543            /*
 6544             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6545             */
 6546            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6547        "},
 6548        indoc! {"
 6549            /*
 6550             *ˇ Lorem ipsum dolor sit amet,
 6551             * consectetur adipiscing elit.
 6552             */
 6553            /*
 6554             *ˇ Lorem ipsum dolor sit amet,
 6555             * consectetur adipiscing elit.
 6556             */
 6557        "},
 6558        rust_lang.clone(),
 6559        &mut cx,
 6560    );
 6561
 6562    // indent is respected
 6563    assert_rewrap(
 6564        indoc! {"
 6565            {}
 6566                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6567        "},
 6568        indoc! {"
 6569            {}
 6570                /*
 6571                 *ˇ Lorem ipsum dolor sit amet,
 6572                 * consectetur adipiscing elit.
 6573                 */
 6574        "},
 6575        rust_lang.clone(),
 6576        &mut cx,
 6577    );
 6578
 6579    // short block comments with inline delimiters
 6580    assert_rewrap(
 6581        indoc! {"
 6582            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6583            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6584             */
 6585            /*
 6586             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6587        "},
 6588        indoc! {"
 6589            /*
 6590             *ˇ Lorem ipsum dolor sit amet,
 6591             * consectetur adipiscing elit.
 6592             */
 6593            /*
 6594             *ˇ Lorem ipsum dolor sit amet,
 6595             * consectetur adipiscing elit.
 6596             */
 6597            /*
 6598             *ˇ Lorem ipsum dolor sit amet,
 6599             * consectetur adipiscing elit.
 6600             */
 6601        "},
 6602        rust_lang.clone(),
 6603        &mut cx,
 6604    );
 6605
 6606    // multiline block comment with inline start/end delimiters
 6607    assert_rewrap(
 6608        indoc! {"
 6609            /*ˇ Lorem ipsum dolor sit amet,
 6610             * consectetur adipiscing elit. */
 6611        "},
 6612        indoc! {"
 6613            /*
 6614             *ˇ Lorem ipsum dolor sit amet,
 6615             * consectetur adipiscing elit.
 6616             */
 6617        "},
 6618        rust_lang.clone(),
 6619        &mut cx,
 6620    );
 6621
 6622    // block comment rewrap still respects paragraph bounds
 6623    assert_rewrap(
 6624        indoc! {"
 6625            /*
 6626             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6627             *
 6628             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6629             */
 6630        "},
 6631        indoc! {"
 6632            /*
 6633             *ˇ Lorem ipsum dolor sit amet,
 6634             * consectetur adipiscing elit.
 6635             *
 6636             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6637             */
 6638        "},
 6639        rust_lang.clone(),
 6640        &mut cx,
 6641    );
 6642
 6643    // documentation comments
 6644    assert_rewrap(
 6645        indoc! {"
 6646            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6647            /**
 6648             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6649             */
 6650        "},
 6651        indoc! {"
 6652            /**
 6653             *ˇ Lorem ipsum dolor sit amet,
 6654             * consectetur adipiscing elit.
 6655             */
 6656            /**
 6657             *ˇ Lorem ipsum dolor sit amet,
 6658             * consectetur adipiscing elit.
 6659             */
 6660        "},
 6661        rust_lang.clone(),
 6662        &mut cx,
 6663    );
 6664
 6665    // different, adjacent comments
 6666    assert_rewrap(
 6667        indoc! {"
 6668            /**
 6669             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6670             */
 6671            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6672            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6673        "},
 6674        indoc! {"
 6675            /**
 6676             *ˇ Lorem ipsum dolor sit amet,
 6677             * consectetur adipiscing elit.
 6678             */
 6679            /*
 6680             *ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit.
 6682             */
 6683            //ˇ Lorem ipsum dolor sit amet,
 6684            // consectetur adipiscing elit.
 6685        "},
 6686        rust_lang.clone(),
 6687        &mut cx,
 6688    );
 6689
 6690    // selection w/ single short block comment
 6691    assert_rewrap(
 6692        indoc! {"
 6693            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6694        "},
 6695        indoc! {"
 6696            «/*
 6697             * Lorem ipsum dolor sit amet,
 6698             * consectetur adipiscing elit.
 6699             */ˇ»
 6700        "},
 6701        rust_lang.clone(),
 6702        &mut cx,
 6703    );
 6704
 6705    // rewrapping a single comment w/ abutting comments
 6706    assert_rewrap(
 6707        indoc! {"
 6708            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6709            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6710        "},
 6711        indoc! {"
 6712            /*
 6713             * ˇLorem ipsum dolor sit amet,
 6714             * consectetur adipiscing elit.
 6715             */
 6716            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6717        "},
 6718        rust_lang.clone(),
 6719        &mut cx,
 6720    );
 6721
 6722    // selection w/ non-abutting short block comments
 6723    assert_rewrap(
 6724        indoc! {"
 6725            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6726
 6727            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6728        "},
 6729        indoc! {"
 6730            «/*
 6731             * Lorem ipsum dolor sit amet,
 6732             * consectetur adipiscing elit.
 6733             */
 6734
 6735            /*
 6736             * Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */ˇ»
 6739        "},
 6740        rust_lang.clone(),
 6741        &mut cx,
 6742    );
 6743
 6744    // selection of multiline block comments
 6745    assert_rewrap(
 6746        indoc! {"
 6747            «/* Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit. */ˇ»
 6749        "},
 6750        indoc! {"
 6751            «/*
 6752             * Lorem ipsum dolor sit amet,
 6753             * consectetur adipiscing elit.
 6754             */ˇ»
 6755        "},
 6756        rust_lang.clone(),
 6757        &mut cx,
 6758    );
 6759
 6760    // partial selection of multiline block comments
 6761    assert_rewrap(
 6762        indoc! {"
 6763            «/* Lorem ipsum dolor sit amet,ˇ»
 6764             * consectetur adipiscing elit. */
 6765            /* Lorem ipsum dolor sit amet,
 6766             «* consectetur adipiscing elit. */ˇ»
 6767        "},
 6768        indoc! {"
 6769            «/*
 6770             * Lorem ipsum dolor sit amet,ˇ»
 6771             * consectetur adipiscing elit. */
 6772            /* Lorem ipsum dolor sit amet,
 6773             «* consectetur adipiscing elit.
 6774             */ˇ»
 6775        "},
 6776        rust_lang.clone(),
 6777        &mut cx,
 6778    );
 6779
 6780    // selection w/ abutting short block comments
 6781    // TODO: should not be combined; should rewrap as 2 comments
 6782    assert_rewrap(
 6783        indoc! {"
 6784            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6785            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6786        "},
 6787        // desired behavior:
 6788        // indoc! {"
 6789        //     «/*
 6790        //      * Lorem ipsum dolor sit amet,
 6791        //      * consectetur adipiscing elit.
 6792        //      */
 6793        //     /*
 6794        //      * Lorem ipsum dolor sit amet,
 6795        //      * consectetur adipiscing elit.
 6796        //      */ˇ»
 6797        // "},
 6798        // actual behaviour:
 6799        indoc! {"
 6800            «/*
 6801             * Lorem ipsum dolor sit amet,
 6802             * consectetur adipiscing elit. Lorem
 6803             * ipsum dolor sit amet, consectetur
 6804             * adipiscing elit.
 6805             */ˇ»
 6806        "},
 6807        rust_lang.clone(),
 6808        &mut cx,
 6809    );
 6810
 6811    // TODO: same as above, but with delimiters on separate line
 6812    // assert_rewrap(
 6813    //     indoc! {"
 6814    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6815    //          */
 6816    //         /*
 6817    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6818    //     "},
 6819    //     // desired:
 6820    //     // indoc! {"
 6821    //     //     «/*
 6822    //     //      * Lorem ipsum dolor sit amet,
 6823    //     //      * consectetur adipiscing elit.
 6824    //     //      */
 6825    //     //     /*
 6826    //     //      * Lorem ipsum dolor sit amet,
 6827    //     //      * consectetur adipiscing elit.
 6828    //     //      */ˇ»
 6829    //     // "},
 6830    //     // actual: (but with trailing w/s on the empty lines)
 6831    //     indoc! {"
 6832    //         «/*
 6833    //          * Lorem ipsum dolor sit amet,
 6834    //          * consectetur adipiscing elit.
 6835    //          *
 6836    //          */
 6837    //         /*
 6838    //          *
 6839    //          * Lorem ipsum dolor sit amet,
 6840    //          * consectetur adipiscing elit.
 6841    //          */ˇ»
 6842    //     "},
 6843    //     rust_lang.clone(),
 6844    //     &mut cx,
 6845    // );
 6846
 6847    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6848    assert_rewrap(
 6849        indoc! {"
 6850            /*
 6851             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6852             */
 6853            /*
 6854             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6856        "},
 6857        // desired:
 6858        // indoc! {"
 6859        //     /*
 6860        //      *ˇ Lorem ipsum dolor sit amet,
 6861        //      * consectetur adipiscing elit.
 6862        //      */
 6863        //     /*
 6864        //      *ˇ Lorem ipsum dolor sit amet,
 6865        //      * consectetur adipiscing elit.
 6866        //      */
 6867        //     /*
 6868        //      *ˇ Lorem ipsum dolor sit amet
 6869        //      */ /* consectetur adipiscing elit. */
 6870        // "},
 6871        // actual:
 6872        indoc! {"
 6873            /*
 6874             //ˇ Lorem ipsum dolor sit amet,
 6875             // consectetur adipiscing elit.
 6876             */
 6877            /*
 6878             * //ˇ Lorem ipsum dolor sit amet,
 6879             * consectetur adipiscing elit.
 6880             */
 6881            /*
 6882             *ˇ Lorem ipsum dolor sit amet */ /*
 6883             * consectetur adipiscing elit.
 6884             */
 6885        "},
 6886        rust_lang,
 6887        &mut cx,
 6888    );
 6889
 6890    #[track_caller]
 6891    fn assert_rewrap(
 6892        unwrapped_text: &str,
 6893        wrapped_text: &str,
 6894        language: Arc<Language>,
 6895        cx: &mut EditorTestContext,
 6896    ) {
 6897        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6898        cx.set_state(unwrapped_text);
 6899        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6900        cx.assert_editor_state(wrapped_text);
 6901    }
 6902}
 6903
 6904#[gpui::test]
 6905async fn test_hard_wrap(cx: &mut TestAppContext) {
 6906    init_test(cx, |_| {});
 6907    let mut cx = EditorTestContext::new(cx).await;
 6908
 6909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6910    cx.update_editor(|editor, _, cx| {
 6911        editor.set_hard_wrap(Some(14), cx);
 6912    });
 6913
 6914    cx.set_state(indoc!(
 6915        "
 6916        one two three ˇ
 6917        "
 6918    ));
 6919    cx.simulate_input("four");
 6920    cx.run_until_parked();
 6921
 6922    cx.assert_editor_state(indoc!(
 6923        "
 6924        one two three
 6925        fourˇ
 6926        "
 6927    ));
 6928
 6929    cx.update_editor(|editor, window, cx| {
 6930        editor.newline(&Default::default(), window, cx);
 6931    });
 6932    cx.run_until_parked();
 6933    cx.assert_editor_state(indoc!(
 6934        "
 6935        one two three
 6936        four
 6937        ˇ
 6938        "
 6939    ));
 6940
 6941    cx.simulate_input("five");
 6942    cx.run_until_parked();
 6943    cx.assert_editor_state(indoc!(
 6944        "
 6945        one two three
 6946        four
 6947        fiveˇ
 6948        "
 6949    ));
 6950
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.newline(&Default::default(), window, cx);
 6953    });
 6954    cx.run_until_parked();
 6955    cx.simulate_input("# ");
 6956    cx.run_until_parked();
 6957    cx.assert_editor_state(indoc!(
 6958        "
 6959        one two three
 6960        four
 6961        five
 6962        # ˇ
 6963        "
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.newline(&Default::default(), window, cx);
 6968    });
 6969    cx.run_until_parked();
 6970    cx.assert_editor_state(indoc!(
 6971        "
 6972        one two three
 6973        four
 6974        five
 6975        #\x20
 6976 6977        "
 6978    ));
 6979
 6980    cx.simulate_input(" 6");
 6981    cx.run_until_parked();
 6982    cx.assert_editor_state(indoc!(
 6983        "
 6984        one two three
 6985        four
 6986        five
 6987        #
 6988        # 6ˇ
 6989        "
 6990    ));
 6991}
 6992
 6993#[gpui::test]
 6994async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6995    init_test(cx, |_| {});
 6996
 6997    let mut cx = EditorTestContext::new(cx).await;
 6998
 6999    cx.set_state(indoc! {"The quick brownˇ"});
 7000    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7001    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7002
 7003    cx.set_state(indoc! {"The emacs foxˇ"});
 7004    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7005    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7006
 7007    cx.set_state(indoc! {"
 7008        The quick« brownˇ»
 7009        fox jumps overˇ
 7010        the lazy dog"});
 7011    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7012    cx.assert_editor_state(indoc! {"
 7013        The quickˇ
 7014        ˇthe lazy dog"});
 7015
 7016    cx.set_state(indoc! {"
 7017        The quick« brownˇ»
 7018        fox jumps overˇ
 7019        the lazy dog"});
 7020    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7021    cx.assert_editor_state(indoc! {"
 7022        The quickˇ
 7023        fox jumps overˇthe lazy dog"});
 7024
 7025    cx.set_state(indoc! {"
 7026        The quick« brownˇ»
 7027        fox jumps overˇ
 7028        the lazy dog"});
 7029    cx.update_editor(|e, window, cx| {
 7030        e.cut_to_end_of_line(
 7031            &CutToEndOfLine {
 7032                stop_at_newlines: true,
 7033            },
 7034            window,
 7035            cx,
 7036        )
 7037    });
 7038    cx.assert_editor_state(indoc! {"
 7039        The quickˇ
 7040        fox jumps overˇ
 7041        the lazy dog"});
 7042
 7043    cx.set_state(indoc! {"
 7044        The quick« brownˇ»
 7045        fox jumps overˇ
 7046        the lazy dog"});
 7047    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7048    cx.assert_editor_state(indoc! {"
 7049        The quickˇ
 7050        fox jumps overˇthe lazy dog"});
 7051}
 7052
 7053#[gpui::test]
 7054async fn test_clipboard(cx: &mut TestAppContext) {
 7055    init_test(cx, |_| {});
 7056
 7057    let mut cx = EditorTestContext::new(cx).await;
 7058
 7059    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7060    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7061    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7062
 7063    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7064    cx.set_state("two ˇfour ˇsix ˇ");
 7065    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7066    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7067
 7068    // Paste again but with only two cursors. Since the number of cursors doesn't
 7069    // match the number of slices in the clipboard, the entire clipboard text
 7070    // is pasted at each cursor.
 7071    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7072    cx.update_editor(|e, window, cx| {
 7073        e.handle_input("( ", window, cx);
 7074        e.paste(&Paste, window, cx);
 7075        e.handle_input(") ", window, cx);
 7076    });
 7077    cx.assert_editor_state(
 7078        &([
 7079            "( one✅ ",
 7080            "three ",
 7081            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7082            "three ",
 7083            "five ) ˇ",
 7084        ]
 7085        .join("\n")),
 7086    );
 7087
 7088    // Cut with three selections, one of which is full-line.
 7089    cx.set_state(indoc! {"
 7090        1«2ˇ»3
 7091        4ˇ567
 7092        «8ˇ»9"});
 7093    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7094    cx.assert_editor_state(indoc! {"
 7095        1ˇ3
 7096        ˇ9"});
 7097
 7098    // Paste with three selections, noticing how the copied selection that was full-line
 7099    // gets inserted before the second cursor.
 7100    cx.set_state(indoc! {"
 7101        1ˇ3
 7102 7103        «oˇ»ne"});
 7104    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7105    cx.assert_editor_state(indoc! {"
 7106        12ˇ3
 7107        4567
 7108 7109        8ˇne"});
 7110
 7111    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7112    cx.set_state(indoc! {"
 7113        The quick brown
 7114        fox juˇmps over
 7115        the lazy dog"});
 7116    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7117    assert_eq!(
 7118        cx.read_from_clipboard()
 7119            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7120        Some("fox jumps over\n".to_string())
 7121    );
 7122
 7123    // Paste with three selections, noticing how the copied full-line selection is inserted
 7124    // before the empty selections but replaces the selection that is non-empty.
 7125    cx.set_state(indoc! {"
 7126        Tˇhe quick brown
 7127        «foˇ»x jumps over
 7128        tˇhe lazy dog"});
 7129    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7130    cx.assert_editor_state(indoc! {"
 7131        fox jumps over
 7132        Tˇhe quick brown
 7133        fox jumps over
 7134        ˇx jumps over
 7135        fox jumps over
 7136        tˇhe lazy dog"});
 7137}
 7138
 7139#[gpui::test]
 7140async fn test_copy_trim(cx: &mut TestAppContext) {
 7141    init_test(cx, |_| {});
 7142
 7143    let mut cx = EditorTestContext::new(cx).await;
 7144    cx.set_state(
 7145        r#"            «for selection in selections.iter() {
 7146            let mut start = selection.start;
 7147            let mut end = selection.end;
 7148            let is_entire_line = selection.is_empty();
 7149            if is_entire_line {
 7150                start = Point::new(start.row, 0);ˇ»
 7151                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7152            }
 7153        "#,
 7154    );
 7155    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7156    assert_eq!(
 7157        cx.read_from_clipboard()
 7158            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7159        Some(
 7160            "for selection in selections.iter() {
 7161            let mut start = selection.start;
 7162            let mut end = selection.end;
 7163            let is_entire_line = selection.is_empty();
 7164            if is_entire_line {
 7165                start = Point::new(start.row, 0);"
 7166                .to_string()
 7167        ),
 7168        "Regular copying preserves all indentation selected",
 7169    );
 7170    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7171    assert_eq!(
 7172        cx.read_from_clipboard()
 7173            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7174        Some(
 7175            "for selection in selections.iter() {
 7176let mut start = selection.start;
 7177let mut end = selection.end;
 7178let is_entire_line = selection.is_empty();
 7179if is_entire_line {
 7180    start = Point::new(start.row, 0);"
 7181                .to_string()
 7182        ),
 7183        "Copying with stripping should strip all leading whitespaces"
 7184    );
 7185
 7186    cx.set_state(
 7187        r#"       «     for selection in selections.iter() {
 7188            let mut start = selection.start;
 7189            let mut end = selection.end;
 7190            let is_entire_line = selection.is_empty();
 7191            if is_entire_line {
 7192                start = Point::new(start.row, 0);ˇ»
 7193                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7194            }
 7195        "#,
 7196    );
 7197    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7198    assert_eq!(
 7199        cx.read_from_clipboard()
 7200            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7201        Some(
 7202            "     for selection in selections.iter() {
 7203            let mut start = selection.start;
 7204            let mut end = selection.end;
 7205            let is_entire_line = selection.is_empty();
 7206            if is_entire_line {
 7207                start = Point::new(start.row, 0);"
 7208                .to_string()
 7209        ),
 7210        "Regular copying preserves all indentation selected",
 7211    );
 7212    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7213    assert_eq!(
 7214        cx.read_from_clipboard()
 7215            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7216        Some(
 7217            "for selection in selections.iter() {
 7218let mut start = selection.start;
 7219let mut end = selection.end;
 7220let is_entire_line = selection.is_empty();
 7221if is_entire_line {
 7222    start = Point::new(start.row, 0);"
 7223                .to_string()
 7224        ),
 7225        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7226    );
 7227
 7228    cx.set_state(
 7229        r#"       «ˇ     for selection in selections.iter() {
 7230            let mut start = selection.start;
 7231            let mut end = selection.end;
 7232            let is_entire_line = selection.is_empty();
 7233            if is_entire_line {
 7234                start = Point::new(start.row, 0);»
 7235                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7236            }
 7237        "#,
 7238    );
 7239    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7240    assert_eq!(
 7241        cx.read_from_clipboard()
 7242            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7243        Some(
 7244            "     for selection in selections.iter() {
 7245            let mut start = selection.start;
 7246            let mut end = selection.end;
 7247            let is_entire_line = selection.is_empty();
 7248            if is_entire_line {
 7249                start = Point::new(start.row, 0);"
 7250                .to_string()
 7251        ),
 7252        "Regular copying for reverse selection works the same",
 7253    );
 7254    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7255    assert_eq!(
 7256        cx.read_from_clipboard()
 7257            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7258        Some(
 7259            "for selection in selections.iter() {
 7260let mut start = selection.start;
 7261let mut end = selection.end;
 7262let is_entire_line = selection.is_empty();
 7263if is_entire_line {
 7264    start = Point::new(start.row, 0);"
 7265                .to_string()
 7266        ),
 7267        "Copying with stripping for reverse selection works the same"
 7268    );
 7269
 7270    cx.set_state(
 7271        r#"            for selection «in selections.iter() {
 7272            let mut start = selection.start;
 7273            let mut end = selection.end;
 7274            let is_entire_line = selection.is_empty();
 7275            if is_entire_line {
 7276                start = Point::new(start.row, 0);ˇ»
 7277                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7278            }
 7279        "#,
 7280    );
 7281    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7282    assert_eq!(
 7283        cx.read_from_clipboard()
 7284            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7285        Some(
 7286            "in selections.iter() {
 7287            let mut start = selection.start;
 7288            let mut end = selection.end;
 7289            let is_entire_line = selection.is_empty();
 7290            if is_entire_line {
 7291                start = Point::new(start.row, 0);"
 7292                .to_string()
 7293        ),
 7294        "When selecting past the indent, the copying works as usual",
 7295    );
 7296    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7297    assert_eq!(
 7298        cx.read_from_clipboard()
 7299            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7300        Some(
 7301            "in selections.iter() {
 7302            let mut start = selection.start;
 7303            let mut end = selection.end;
 7304            let is_entire_line = selection.is_empty();
 7305            if is_entire_line {
 7306                start = Point::new(start.row, 0);"
 7307                .to_string()
 7308        ),
 7309        "When selecting past the indent, nothing is trimmed"
 7310    );
 7311
 7312    cx.set_state(
 7313        r#"            «for selection in selections.iter() {
 7314            let mut start = selection.start;
 7315
 7316            let mut end = selection.end;
 7317            let is_entire_line = selection.is_empty();
 7318            if is_entire_line {
 7319                start = Point::new(start.row, 0);
 7320ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7321            }
 7322        "#,
 7323    );
 7324    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7325    assert_eq!(
 7326        cx.read_from_clipboard()
 7327            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7328        Some(
 7329            "for selection in selections.iter() {
 7330let mut start = selection.start;
 7331
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);
 7336"
 7337            .to_string()
 7338        ),
 7339        "Copying with stripping should ignore empty lines"
 7340    );
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_paste_multiline(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346
 7347    let mut cx = EditorTestContext::new(cx).await;
 7348    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7349
 7350    // Cut an indented block, without the leading whitespace.
 7351    cx.set_state(indoc! {"
 7352        const a: B = (
 7353            c(),
 7354            «d(
 7355                e,
 7356                f
 7357            )ˇ»
 7358        );
 7359    "});
 7360    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7361    cx.assert_editor_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            ˇ
 7365        );
 7366    "});
 7367
 7368    // Paste it at the same position.
 7369    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7370    cx.assert_editor_state(indoc! {"
 7371        const a: B = (
 7372            c(),
 7373            d(
 7374                e,
 7375                f
 7376 7377        );
 7378    "});
 7379
 7380    // Paste it at a line with a lower indent level.
 7381    cx.set_state(indoc! {"
 7382        ˇ
 7383        const a: B = (
 7384            c(),
 7385        );
 7386    "});
 7387    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7388    cx.assert_editor_state(indoc! {"
 7389        d(
 7390            e,
 7391            f
 7392 7393        const a: B = (
 7394            c(),
 7395        );
 7396    "});
 7397
 7398    // Cut an indented block, with the leading whitespace.
 7399    cx.set_state(indoc! {"
 7400        const a: B = (
 7401            c(),
 7402        «    d(
 7403                e,
 7404                f
 7405            )
 7406        ˇ»);
 7407    "});
 7408    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7409    cx.assert_editor_state(indoc! {"
 7410        const a: B = (
 7411            c(),
 7412        ˇ);
 7413    "});
 7414
 7415    // Paste it at the same position.
 7416    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7417    cx.assert_editor_state(indoc! {"
 7418        const a: B = (
 7419            c(),
 7420            d(
 7421                e,
 7422                f
 7423            )
 7424        ˇ);
 7425    "});
 7426
 7427    // Paste it at a line with a higher indent level.
 7428    cx.set_state(indoc! {"
 7429        const a: B = (
 7430            c(),
 7431            d(
 7432                e,
 7433 7434            )
 7435        );
 7436    "});
 7437    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7438    cx.assert_editor_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            d(
 7442                e,
 7443                f    d(
 7444                    e,
 7445                    f
 7446                )
 7447        ˇ
 7448            )
 7449        );
 7450    "});
 7451
 7452    // Copy an indented block, starting mid-line
 7453    cx.set_state(indoc! {"
 7454        const a: B = (
 7455            c(),
 7456            somethin«g(
 7457                e,
 7458                f
 7459            )ˇ»
 7460        );
 7461    "});
 7462    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7463
 7464    // Paste it on a line with a lower indent level
 7465    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7466    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7467    cx.assert_editor_state(indoc! {"
 7468        const a: B = (
 7469            c(),
 7470            something(
 7471                e,
 7472                f
 7473            )
 7474        );
 7475        g(
 7476            e,
 7477            f
 7478"});
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    cx.write_to_clipboard(ClipboardItem::new_string(
 7486        "    d(\n        e\n    );\n".into(),
 7487    ));
 7488
 7489    let mut cx = EditorTestContext::new(cx).await;
 7490    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7491
 7492    cx.set_state(indoc! {"
 7493        fn a() {
 7494            b();
 7495            if c() {
 7496                ˇ
 7497            }
 7498        }
 7499    "});
 7500
 7501    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7502    cx.assert_editor_state(indoc! {"
 7503        fn a() {
 7504            b();
 7505            if c() {
 7506                d(
 7507                    e
 7508                );
 7509        ˇ
 7510            }
 7511        }
 7512    "});
 7513
 7514    cx.set_state(indoc! {"
 7515        fn a() {
 7516            b();
 7517            ˇ
 7518        }
 7519    "});
 7520
 7521    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7522    cx.assert_editor_state(indoc! {"
 7523        fn a() {
 7524            b();
 7525            d(
 7526                e
 7527            );
 7528        ˇ
 7529        }
 7530    "});
 7531}
 7532
 7533#[gpui::test]
 7534fn test_select_all(cx: &mut TestAppContext) {
 7535    init_test(cx, |_| {});
 7536
 7537    let editor = cx.add_window(|window, cx| {
 7538        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7539        build_editor(buffer, window, cx)
 7540    });
 7541    _ = editor.update(cx, |editor, window, cx| {
 7542        editor.select_all(&SelectAll, window, cx);
 7543        assert_eq!(
 7544            editor.selections.display_ranges(cx),
 7545            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7546        );
 7547    });
 7548}
 7549
 7550#[gpui::test]
 7551fn test_select_line(cx: &mut TestAppContext) {
 7552    init_test(cx, |_| {});
 7553
 7554    let editor = cx.add_window(|window, cx| {
 7555        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7556        build_editor(buffer, window, cx)
 7557    });
 7558    _ = editor.update(cx, |editor, window, cx| {
 7559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7560            s.select_display_ranges([
 7561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7563                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7564                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7565            ])
 7566        });
 7567        editor.select_line(&SelectLine, window, cx);
 7568        assert_eq!(
 7569            editor.selections.display_ranges(cx),
 7570            vec![
 7571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7572                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7573            ]
 7574        );
 7575    });
 7576
 7577    _ = editor.update(cx, |editor, window, cx| {
 7578        editor.select_line(&SelectLine, window, cx);
 7579        assert_eq!(
 7580            editor.selections.display_ranges(cx),
 7581            vec![
 7582                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7583                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7584            ]
 7585        );
 7586    });
 7587
 7588    _ = editor.update(cx, |editor, window, cx| {
 7589        editor.select_line(&SelectLine, window, cx);
 7590        assert_eq!(
 7591            editor.selections.display_ranges(cx),
 7592            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7593        );
 7594    });
 7595}
 7596
 7597#[gpui::test]
 7598async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7599    init_test(cx, |_| {});
 7600    let mut cx = EditorTestContext::new(cx).await;
 7601
 7602    #[track_caller]
 7603    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7604        cx.set_state(initial_state);
 7605        cx.update_editor(|e, window, cx| {
 7606            e.split_selection_into_lines(&Default::default(), window, cx)
 7607        });
 7608        cx.assert_editor_state(expected_state);
 7609    }
 7610
 7611    // Selection starts and ends at the middle of lines, left-to-right
 7612    test(
 7613        &mut cx,
 7614        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7615        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7616    );
 7617    // Same thing, right-to-left
 7618    test(
 7619        &mut cx,
 7620        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7621        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7622    );
 7623
 7624    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7625    test(
 7626        &mut cx,
 7627        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7628        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7629    );
 7630    // Same thing, right-to-left
 7631    test(
 7632        &mut cx,
 7633        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7634        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7635    );
 7636
 7637    // Whole buffer, left-to-right, last line ends with newline
 7638    test(
 7639        &mut cx,
 7640        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7641        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7642    );
 7643    // Same thing, right-to-left
 7644    test(
 7645        &mut cx,
 7646        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7647        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7648    );
 7649
 7650    // Starts at the end of a line, ends at the start of another
 7651    test(
 7652        &mut cx,
 7653        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7654        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7655    );
 7656}
 7657
 7658#[gpui::test]
 7659async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7660    init_test(cx, |_| {});
 7661
 7662    let editor = cx.add_window(|window, cx| {
 7663        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7664        build_editor(buffer, window, cx)
 7665    });
 7666
 7667    // setup
 7668    _ = editor.update(cx, |editor, window, cx| {
 7669        editor.fold_creases(
 7670            vec![
 7671                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7672                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7673                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7674            ],
 7675            true,
 7676            window,
 7677            cx,
 7678        );
 7679        assert_eq!(
 7680            editor.display_text(cx),
 7681            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7682        );
 7683    });
 7684
 7685    _ = editor.update(cx, |editor, window, cx| {
 7686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7687            s.select_display_ranges([
 7688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7689                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7691                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7692            ])
 7693        });
 7694        editor.split_selection_into_lines(&Default::default(), window, cx);
 7695        assert_eq!(
 7696            editor.display_text(cx),
 7697            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7698        );
 7699    });
 7700    EditorTestContext::for_editor(editor, cx)
 7701        .await
 7702        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7703
 7704    _ = editor.update(cx, |editor, window, cx| {
 7705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7706            s.select_display_ranges([
 7707                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7708            ])
 7709        });
 7710        editor.split_selection_into_lines(&Default::default(), window, cx);
 7711        assert_eq!(
 7712            editor.display_text(cx),
 7713            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7714        );
 7715        assert_eq!(
 7716            editor.selections.display_ranges(cx),
 7717            [
 7718                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7719                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7720                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7721                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7722                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7723                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7724                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7725            ]
 7726        );
 7727    });
 7728    EditorTestContext::for_editor(editor, cx)
 7729        .await
 7730        .assert_editor_state(
 7731            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7732        );
 7733}
 7734
 7735#[gpui::test]
 7736async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7737    init_test(cx, |_| {});
 7738
 7739    let mut cx = EditorTestContext::new(cx).await;
 7740
 7741    cx.set_state(indoc!(
 7742        r#"abc
 7743           defˇghi
 7744
 7745           jk
 7746           nlmo
 7747           "#
 7748    ));
 7749
 7750    cx.update_editor(|editor, window, cx| {
 7751        editor.add_selection_above(&Default::default(), window, cx);
 7752    });
 7753
 7754    cx.assert_editor_state(indoc!(
 7755        r#"abcˇ
 7756           defˇghi
 7757
 7758           jk
 7759           nlmo
 7760           "#
 7761    ));
 7762
 7763    cx.update_editor(|editor, window, cx| {
 7764        editor.add_selection_above(&Default::default(), window, cx);
 7765    });
 7766
 7767    cx.assert_editor_state(indoc!(
 7768        r#"abcˇ
 7769            defˇghi
 7770
 7771            jk
 7772            nlmo
 7773            "#
 7774    ));
 7775
 7776    cx.update_editor(|editor, window, cx| {
 7777        editor.add_selection_below(&Default::default(), window, cx);
 7778    });
 7779
 7780    cx.assert_editor_state(indoc!(
 7781        r#"abc
 7782           defˇghi
 7783
 7784           jk
 7785           nlmo
 7786           "#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.undo_selection(&Default::default(), window, cx);
 7791    });
 7792
 7793    cx.assert_editor_state(indoc!(
 7794        r#"abcˇ
 7795           defˇghi
 7796
 7797           jk
 7798           nlmo
 7799           "#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.redo_selection(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.assert_editor_state(indoc!(
 7807        r#"abc
 7808           defˇghi
 7809
 7810           jk
 7811           nlmo
 7812           "#
 7813    ));
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_below(&Default::default(), window, cx);
 7817    });
 7818
 7819    cx.assert_editor_state(indoc!(
 7820        r#"abc
 7821           defˇghi
 7822           ˇ
 7823           jk
 7824           nlmo
 7825           "#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    cx.assert_editor_state(indoc!(
 7833        r#"abc
 7834           defˇghi
 7835           ˇ
 7836           jkˇ
 7837           nlmo
 7838           "#
 7839    ));
 7840
 7841    cx.update_editor(|editor, window, cx| {
 7842        editor.add_selection_below(&Default::default(), window, cx);
 7843    });
 7844
 7845    cx.assert_editor_state(indoc!(
 7846        r#"abc
 7847           defˇghi
 7848           ˇ
 7849           jkˇ
 7850           nlmˇo
 7851           "#
 7852    ));
 7853
 7854    cx.update_editor(|editor, window, cx| {
 7855        editor.add_selection_below(&Default::default(), window, cx);
 7856    });
 7857
 7858    cx.assert_editor_state(indoc!(
 7859        r#"abc
 7860           defˇghi
 7861           ˇ
 7862           jkˇ
 7863           nlmˇo
 7864           ˇ"#
 7865    ));
 7866
 7867    // change selections
 7868    cx.set_state(indoc!(
 7869        r#"abc
 7870           def«ˇg»hi
 7871
 7872           jk
 7873           nlmo
 7874           "#
 7875    ));
 7876
 7877    cx.update_editor(|editor, window, cx| {
 7878        editor.add_selection_below(&Default::default(), window, cx);
 7879    });
 7880
 7881    cx.assert_editor_state(indoc!(
 7882        r#"abc
 7883           def«ˇg»hi
 7884
 7885           jk
 7886           nlm«ˇo»
 7887           "#
 7888    ));
 7889
 7890    cx.update_editor(|editor, window, cx| {
 7891        editor.add_selection_below(&Default::default(), window, cx);
 7892    });
 7893
 7894    cx.assert_editor_state(indoc!(
 7895        r#"abc
 7896           def«ˇg»hi
 7897
 7898           jk
 7899           nlm«ˇo»
 7900           "#
 7901    ));
 7902
 7903    cx.update_editor(|editor, window, cx| {
 7904        editor.add_selection_above(&Default::default(), window, cx);
 7905    });
 7906
 7907    cx.assert_editor_state(indoc!(
 7908        r#"abc
 7909           def«ˇg»hi
 7910
 7911           jk
 7912           nlmo
 7913           "#
 7914    ));
 7915
 7916    cx.update_editor(|editor, window, cx| {
 7917        editor.add_selection_above(&Default::default(), window, cx);
 7918    });
 7919
 7920    cx.assert_editor_state(indoc!(
 7921        r#"abc
 7922           def«ˇg»hi
 7923
 7924           jk
 7925           nlmo
 7926           "#
 7927    ));
 7928
 7929    // Change selections again
 7930    cx.set_state(indoc!(
 7931        r#"a«bc
 7932           defgˇ»hi
 7933
 7934           jk
 7935           nlmo
 7936           "#
 7937    ));
 7938
 7939    cx.update_editor(|editor, window, cx| {
 7940        editor.add_selection_below(&Default::default(), window, cx);
 7941    });
 7942
 7943    cx.assert_editor_state(indoc!(
 7944        r#"a«bcˇ»
 7945           d«efgˇ»hi
 7946
 7947           j«kˇ»
 7948           nlmo
 7949           "#
 7950    ));
 7951
 7952    cx.update_editor(|editor, window, cx| {
 7953        editor.add_selection_below(&Default::default(), window, cx);
 7954    });
 7955    cx.assert_editor_state(indoc!(
 7956        r#"a«bcˇ»
 7957           d«efgˇ»hi
 7958
 7959           j«kˇ»
 7960           n«lmoˇ»
 7961           "#
 7962    ));
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.add_selection_above(&Default::default(), window, cx);
 7965    });
 7966
 7967    cx.assert_editor_state(indoc!(
 7968        r#"a«bcˇ»
 7969           d«efgˇ»hi
 7970
 7971           j«kˇ»
 7972           nlmo
 7973           "#
 7974    ));
 7975
 7976    // Change selections again
 7977    cx.set_state(indoc!(
 7978        r#"abc
 7979           d«ˇefghi
 7980
 7981           jk
 7982           nlm»o
 7983           "#
 7984    ));
 7985
 7986    cx.update_editor(|editor, window, cx| {
 7987        editor.add_selection_above(&Default::default(), window, cx);
 7988    });
 7989
 7990    cx.assert_editor_state(indoc!(
 7991        r#"a«ˇbc»
 7992           d«ˇef»ghi
 7993
 7994           j«ˇk»
 7995           n«ˇlm»o
 7996           "#
 7997    ));
 7998
 7999    cx.update_editor(|editor, window, cx| {
 8000        editor.add_selection_below(&Default::default(), window, cx);
 8001    });
 8002
 8003    cx.assert_editor_state(indoc!(
 8004        r#"abc
 8005           d«ˇef»ghi
 8006
 8007           j«ˇk»
 8008           n«ˇlm»o
 8009           "#
 8010    ));
 8011}
 8012
 8013#[gpui::test]
 8014async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8015    init_test(cx, |_| {});
 8016    let mut cx = EditorTestContext::new(cx).await;
 8017
 8018    cx.set_state(indoc!(
 8019        r#"line onˇe
 8020           liˇne two
 8021           line three
 8022           line four"#
 8023    ));
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.add_selection_below(&Default::default(), window, cx);
 8027    });
 8028
 8029    // test multiple cursors expand in the same direction
 8030    cx.assert_editor_state(indoc!(
 8031        r#"line onˇe
 8032           liˇne twˇo
 8033           liˇne three
 8034           line four"#
 8035    ));
 8036
 8037    cx.update_editor(|editor, window, cx| {
 8038        editor.add_selection_below(&Default::default(), window, cx);
 8039    });
 8040
 8041    cx.update_editor(|editor, window, cx| {
 8042        editor.add_selection_below(&Default::default(), window, cx);
 8043    });
 8044
 8045    // test multiple cursors expand below overflow
 8046    cx.assert_editor_state(indoc!(
 8047        r#"line onˇe
 8048           liˇne twˇo
 8049           liˇne thˇree
 8050           liˇne foˇur"#
 8051    ));
 8052
 8053    cx.update_editor(|editor, window, cx| {
 8054        editor.add_selection_above(&Default::default(), window, cx);
 8055    });
 8056
 8057    // test multiple cursors retrieves back correctly
 8058    cx.assert_editor_state(indoc!(
 8059        r#"line onˇe
 8060           liˇne twˇo
 8061           liˇne thˇree
 8062           line four"#
 8063    ));
 8064
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.add_selection_above(&Default::default(), window, cx);
 8067    });
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.add_selection_above(&Default::default(), window, cx);
 8071    });
 8072
 8073    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8074    cx.assert_editor_state(indoc!(
 8075        r#"liˇne onˇe
 8076           liˇne two
 8077           line three
 8078           line four"#
 8079    ));
 8080
 8081    cx.update_editor(|editor, window, cx| {
 8082        editor.undo_selection(&Default::default(), window, cx);
 8083    });
 8084
 8085    // test undo
 8086    cx.assert_editor_state(indoc!(
 8087        r#"line onˇe
 8088           liˇne twˇo
 8089           line three
 8090           line four"#
 8091    ));
 8092
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.redo_selection(&Default::default(), window, cx);
 8095    });
 8096
 8097    // test redo
 8098    cx.assert_editor_state(indoc!(
 8099        r#"liˇne onˇe
 8100           liˇne two
 8101           line three
 8102           line four"#
 8103    ));
 8104
 8105    cx.set_state(indoc!(
 8106        r#"abcd
 8107           ef«ghˇ»
 8108           ijkl
 8109           «mˇ»nop"#
 8110    ));
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_above(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple selections expand in the same direction
 8117    cx.assert_editor_state(indoc!(
 8118        r#"ab«cdˇ»
 8119           ef«ghˇ»
 8120           «iˇ»jkl
 8121           «mˇ»nop"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple selection upward overflow
 8129    cx.assert_editor_state(indoc!(
 8130        r#"ab«cdˇ»
 8131           «eˇ»f«ghˇ»
 8132           «iˇ»jkl
 8133           «mˇ»nop"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_below(&Default::default(), window, cx);
 8138    });
 8139
 8140    // test multiple selection retrieves back correctly
 8141    cx.assert_editor_state(indoc!(
 8142        r#"abcd
 8143           ef«ghˇ»
 8144           «iˇ»jkl
 8145           «mˇ»nop"#
 8146    ));
 8147
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_below(&Default::default(), window, cx);
 8150    });
 8151
 8152    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8153    cx.assert_editor_state(indoc!(
 8154        r#"abcd
 8155           ef«ghˇ»
 8156           ij«klˇ»
 8157           «mˇ»nop"#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    // test undo
 8165    cx.assert_editor_state(indoc!(
 8166        r#"abcd
 8167           ef«ghˇ»
 8168           «iˇ»jkl
 8169           «mˇ»nop"#
 8170    ));
 8171
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.redo_selection(&Default::default(), window, cx);
 8174    });
 8175
 8176    // test redo
 8177    cx.assert_editor_state(indoc!(
 8178        r#"abcd
 8179           ef«ghˇ»
 8180           ij«klˇ»
 8181           «mˇ»nop"#
 8182    ));
 8183}
 8184
 8185#[gpui::test]
 8186async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8187    init_test(cx, |_| {});
 8188    let mut cx = EditorTestContext::new(cx).await;
 8189
 8190    cx.set_state(indoc!(
 8191        r#"line onˇe
 8192           liˇne two
 8193           line three
 8194           line four"#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199        editor.add_selection_below(&Default::default(), window, cx);
 8200        editor.add_selection_below(&Default::default(), window, cx);
 8201    });
 8202
 8203    // initial state with two multi cursor groups
 8204    cx.assert_editor_state(indoc!(
 8205        r#"line onˇe
 8206           liˇne twˇo
 8207           liˇne thˇree
 8208           liˇne foˇur"#
 8209    ));
 8210
 8211    // add single cursor in middle - simulate opt click
 8212    cx.update_editor(|editor, window, cx| {
 8213        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8214        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8215        editor.end_selection(window, cx);
 8216    });
 8217
 8218    cx.assert_editor_state(indoc!(
 8219        r#"line onˇe
 8220           liˇne twˇo
 8221           liˇneˇ thˇree
 8222           liˇne foˇur"#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_above(&Default::default(), window, cx);
 8227    });
 8228
 8229    // test new added selection expands above and existing selection shrinks
 8230    cx.assert_editor_state(indoc!(
 8231        r#"line onˇe
 8232           liˇneˇ twˇo
 8233           liˇneˇ thˇree
 8234           line four"#
 8235    ));
 8236
 8237    cx.update_editor(|editor, window, cx| {
 8238        editor.add_selection_above(&Default::default(), window, cx);
 8239    });
 8240
 8241    // test new added selection expands above and existing selection shrinks
 8242    cx.assert_editor_state(indoc!(
 8243        r#"lineˇ onˇe
 8244           liˇneˇ twˇo
 8245           lineˇ three
 8246           line four"#
 8247    ));
 8248
 8249    // intial state with two selection groups
 8250    cx.set_state(indoc!(
 8251        r#"abcd
 8252           ef«ghˇ»
 8253           ijkl
 8254           «mˇ»nop"#
 8255    ));
 8256
 8257    cx.update_editor(|editor, window, cx| {
 8258        editor.add_selection_above(&Default::default(), window, cx);
 8259        editor.add_selection_above(&Default::default(), window, cx);
 8260    });
 8261
 8262    cx.assert_editor_state(indoc!(
 8263        r#"ab«cdˇ»
 8264           «eˇ»f«ghˇ»
 8265           «iˇ»jkl
 8266           «mˇ»nop"#
 8267    ));
 8268
 8269    // add single selection in middle - simulate opt drag
 8270    cx.update_editor(|editor, window, cx| {
 8271        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8272        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8273        editor.update_selection(
 8274            DisplayPoint::new(DisplayRow(2), 4),
 8275            0,
 8276            gpui::Point::<f32>::default(),
 8277            window,
 8278            cx,
 8279        );
 8280        editor.end_selection(window, cx);
 8281    });
 8282
 8283    cx.assert_editor_state(indoc!(
 8284        r#"ab«cdˇ»
 8285           «eˇ»f«ghˇ»
 8286           «iˇ»jk«lˇ»
 8287           «mˇ»nop"#
 8288    ));
 8289
 8290    cx.update_editor(|editor, window, cx| {
 8291        editor.add_selection_below(&Default::default(), window, cx);
 8292    });
 8293
 8294    // test new added selection expands below, others shrinks from above
 8295    cx.assert_editor_state(indoc!(
 8296        r#"abcd
 8297           ef«ghˇ»
 8298           «iˇ»jk«lˇ»
 8299           «mˇ»no«pˇ»"#
 8300    ));
 8301}
 8302
 8303#[gpui::test]
 8304async fn test_select_next(cx: &mut TestAppContext) {
 8305    init_test(cx, |_| {});
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8309
 8310    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8311        .unwrap();
 8312    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8313
 8314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8315        .unwrap();
 8316    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8317
 8318    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8319    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8320
 8321    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8322    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8323
 8324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8325        .unwrap();
 8326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8327
 8328    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8329        .unwrap();
 8330    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8331
 8332    // Test selection direction should be preserved
 8333    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8334
 8335    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8336        .unwrap();
 8337    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8338}
 8339
 8340#[gpui::test]
 8341async fn test_select_all_matches(cx: &mut TestAppContext) {
 8342    init_test(cx, |_| {});
 8343
 8344    let mut cx = EditorTestContext::new(cx).await;
 8345
 8346    // Test caret-only selections
 8347    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8348    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8349        .unwrap();
 8350    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8351
 8352    // Test left-to-right selections
 8353    cx.set_state("abc\n«abcˇ»\nabc");
 8354    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8355        .unwrap();
 8356    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8357
 8358    // Test right-to-left selections
 8359    cx.set_state("abc\n«ˇabc»\nabc");
 8360    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8361        .unwrap();
 8362    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8363
 8364    // Test selecting whitespace with caret selection
 8365    cx.set_state("abc\nˇ   abc\nabc");
 8366    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8367        .unwrap();
 8368    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8369
 8370    // Test selecting whitespace with left-to-right selection
 8371    cx.set_state("abc\n«ˇ  »abc\nabc");
 8372    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8373        .unwrap();
 8374    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8375
 8376    // Test no matches with right-to-left selection
 8377    cx.set_state("abc\n«  ˇ»abc\nabc");
 8378    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8379        .unwrap();
 8380    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8381
 8382    // Test with a single word and clip_at_line_ends=true (#29823)
 8383    cx.set_state("aˇbc");
 8384    cx.update_editor(|e, window, cx| {
 8385        e.set_clip_at_line_ends(true, cx);
 8386        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8387        e.set_clip_at_line_ends(false, cx);
 8388    });
 8389    cx.assert_editor_state("«abcˇ»");
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx = EditorTestContext::new(cx).await;
 8397
 8398    let large_body_1 = "\nd".repeat(200);
 8399    let large_body_2 = "\ne".repeat(200);
 8400
 8401    cx.set_state(&format!(
 8402        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8403    ));
 8404    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8405        let scroll_position = editor.scroll_position(cx);
 8406        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8407        scroll_position
 8408    });
 8409
 8410    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8411        .unwrap();
 8412    cx.assert_editor_state(&format!(
 8413        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8414    ));
 8415    let scroll_position_after_selection =
 8416        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8417    assert_eq!(
 8418        initial_scroll_position, scroll_position_after_selection,
 8419        "Scroll position should not change after selecting all matches"
 8420    );
 8421}
 8422
 8423#[gpui::test]
 8424async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8425    init_test(cx, |_| {});
 8426
 8427    let mut cx = EditorLspTestContext::new_rust(
 8428        lsp::ServerCapabilities {
 8429            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8430            ..Default::default()
 8431        },
 8432        cx,
 8433    )
 8434    .await;
 8435
 8436    cx.set_state(indoc! {"
 8437        line 1
 8438        line 2
 8439        linˇe 3
 8440        line 4
 8441        line 5
 8442    "});
 8443
 8444    // Make an edit
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.handle_input("X", window, cx);
 8447    });
 8448
 8449    // Move cursor to a different position
 8450    cx.update_editor(|editor, window, cx| {
 8451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8452            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8453        });
 8454    });
 8455
 8456    cx.assert_editor_state(indoc! {"
 8457        line 1
 8458        line 2
 8459        linXe 3
 8460        line 4
 8461        liˇne 5
 8462    "});
 8463
 8464    cx.lsp
 8465        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8466            Ok(Some(vec![lsp::TextEdit::new(
 8467                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8468                "PREFIX ".to_string(),
 8469            )]))
 8470        });
 8471
 8472    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8473        .unwrap()
 8474        .await
 8475        .unwrap();
 8476
 8477    cx.assert_editor_state(indoc! {"
 8478        PREFIX line 1
 8479        line 2
 8480        linXe 3
 8481        line 4
 8482        liˇne 5
 8483    "});
 8484
 8485    // Undo formatting
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.undo(&Default::default(), window, cx);
 8488    });
 8489
 8490    // Verify cursor moved back to position after edit
 8491    cx.assert_editor_state(indoc! {"
 8492        line 1
 8493        line 2
 8494        linXˇe 3
 8495        line 4
 8496        line 5
 8497    "});
 8498}
 8499
 8500#[gpui::test]
 8501async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8502    init_test(cx, |_| {});
 8503
 8504    let mut cx = EditorTestContext::new(cx).await;
 8505
 8506    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8507    cx.update_editor(|editor, window, cx| {
 8508        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8509    });
 8510
 8511    cx.set_state(indoc! {"
 8512        line 1
 8513        line 2
 8514        linˇe 3
 8515        line 4
 8516        line 5
 8517        line 6
 8518        line 7
 8519        line 8
 8520        line 9
 8521        line 10
 8522    "});
 8523
 8524    let snapshot = cx.buffer_snapshot();
 8525    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8526
 8527    cx.update(|_, cx| {
 8528        provider.update(cx, |provider, _| {
 8529            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8530                id: None,
 8531                edits: vec![(edit_position..edit_position, "X".into())],
 8532                edit_preview: None,
 8533            }))
 8534        })
 8535    });
 8536
 8537    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8538    cx.update_editor(|editor, window, cx| {
 8539        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8540    });
 8541
 8542    cx.assert_editor_state(indoc! {"
 8543        line 1
 8544        line 2
 8545        lineXˇ 3
 8546        line 4
 8547        line 5
 8548        line 6
 8549        line 7
 8550        line 8
 8551        line 9
 8552        line 10
 8553    "});
 8554
 8555    cx.update_editor(|editor, window, cx| {
 8556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8557            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8558        });
 8559    });
 8560
 8561    cx.assert_editor_state(indoc! {"
 8562        line 1
 8563        line 2
 8564        lineX 3
 8565        line 4
 8566        line 5
 8567        line 6
 8568        line 7
 8569        line 8
 8570        line 9
 8571        liˇne 10
 8572    "});
 8573
 8574    cx.update_editor(|editor, window, cx| {
 8575        editor.undo(&Default::default(), window, cx);
 8576    });
 8577
 8578    cx.assert_editor_state(indoc! {"
 8579        line 1
 8580        line 2
 8581        lineˇ 3
 8582        line 4
 8583        line 5
 8584        line 6
 8585        line 7
 8586        line 8
 8587        line 9
 8588        line 10
 8589    "});
 8590}
 8591
 8592#[gpui::test]
 8593async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let mut cx = EditorTestContext::new(cx).await;
 8597    cx.set_state(
 8598        r#"let foo = 2;
 8599lˇet foo = 2;
 8600let fooˇ = 2;
 8601let foo = 2;
 8602let foo = ˇ2;"#,
 8603    );
 8604
 8605    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8606        .unwrap();
 8607    cx.assert_editor_state(
 8608        r#"let foo = 2;
 8609«letˇ» foo = 2;
 8610let «fooˇ» = 2;
 8611let foo = 2;
 8612let foo = «2ˇ»;"#,
 8613    );
 8614
 8615    // noop for multiple selections with different contents
 8616    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8617        .unwrap();
 8618    cx.assert_editor_state(
 8619        r#"let foo = 2;
 8620«letˇ» foo = 2;
 8621let «fooˇ» = 2;
 8622let foo = 2;
 8623let foo = «2ˇ»;"#,
 8624    );
 8625
 8626    // Test last selection direction should be preserved
 8627    cx.set_state(
 8628        r#"let foo = 2;
 8629let foo = 2;
 8630let «fooˇ» = 2;
 8631let «ˇfoo» = 2;
 8632let foo = 2;"#,
 8633    );
 8634
 8635    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8636        .unwrap();
 8637    cx.assert_editor_state(
 8638        r#"let foo = 2;
 8639let foo = 2;
 8640let «fooˇ» = 2;
 8641let «ˇfoo» = 2;
 8642let «ˇfoo» = 2;"#,
 8643    );
 8644}
 8645
 8646#[gpui::test]
 8647async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8648    init_test(cx, |_| {});
 8649
 8650    let mut cx =
 8651        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8652
 8653    cx.assert_editor_state(indoc! {"
 8654        ˇbbb
 8655        ccc
 8656
 8657        bbb
 8658        ccc
 8659        "});
 8660    cx.dispatch_action(SelectPrevious::default());
 8661    cx.assert_editor_state(indoc! {"
 8662                «bbbˇ»
 8663                ccc
 8664
 8665                bbb
 8666                ccc
 8667                "});
 8668    cx.dispatch_action(SelectPrevious::default());
 8669    cx.assert_editor_state(indoc! {"
 8670                «bbbˇ»
 8671                ccc
 8672
 8673                «bbbˇ»
 8674                ccc
 8675                "});
 8676}
 8677
 8678#[gpui::test]
 8679async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8680    init_test(cx, |_| {});
 8681
 8682    let mut cx = EditorTestContext::new(cx).await;
 8683    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8684
 8685    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8686        .unwrap();
 8687    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8688
 8689    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8690        .unwrap();
 8691    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8692
 8693    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8694    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8695
 8696    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8697    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8698
 8699    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8700        .unwrap();
 8701    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8702
 8703    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8704        .unwrap();
 8705    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8706}
 8707
 8708#[gpui::test]
 8709async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8710    init_test(cx, |_| {});
 8711
 8712    let mut cx = EditorTestContext::new(cx).await;
 8713    cx.set_state("");
 8714
 8715    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8716        .unwrap();
 8717    cx.assert_editor_state("«aˇ»");
 8718    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8719        .unwrap();
 8720    cx.assert_editor_state("«aˇ»");
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728    cx.set_state(
 8729        r#"let foo = 2;
 8730lˇet foo = 2;
 8731let fooˇ = 2;
 8732let foo = 2;
 8733let foo = ˇ2;"#,
 8734    );
 8735
 8736    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8737        .unwrap();
 8738    cx.assert_editor_state(
 8739        r#"let foo = 2;
 8740«letˇ» foo = 2;
 8741let «fooˇ» = 2;
 8742let foo = 2;
 8743let foo = «2ˇ»;"#,
 8744    );
 8745
 8746    // noop for multiple selections with different contents
 8747    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8748        .unwrap();
 8749    cx.assert_editor_state(
 8750        r#"let foo = 2;
 8751«letˇ» foo = 2;
 8752let «fooˇ» = 2;
 8753let foo = 2;
 8754let foo = «2ˇ»;"#,
 8755    );
 8756}
 8757
 8758#[gpui::test]
 8759async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8760    init_test(cx, |_| {});
 8761
 8762    let mut cx = EditorTestContext::new(cx).await;
 8763    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8764
 8765    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8766        .unwrap();
 8767    // selection direction is preserved
 8768    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8773
 8774    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8775    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8776
 8777    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8778    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8783
 8784    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8785        .unwrap();
 8786    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8787}
 8788
 8789#[gpui::test]
 8790async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8791    init_test(cx, |_| {});
 8792
 8793    let language = Arc::new(Language::new(
 8794        LanguageConfig::default(),
 8795        Some(tree_sitter_rust::LANGUAGE.into()),
 8796    ));
 8797
 8798    let text = r#"
 8799        use mod1::mod2::{mod3, mod4};
 8800
 8801        fn fn_1(param1: bool, param2: &str) {
 8802            let var1 = "text";
 8803        }
 8804    "#
 8805    .unindent();
 8806
 8807    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8809    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8810
 8811    editor
 8812        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8813        .await;
 8814
 8815    editor.update_in(cx, |editor, window, cx| {
 8816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8817            s.select_display_ranges([
 8818                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8819                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8820                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8821            ]);
 8822        });
 8823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8824    });
 8825    editor.update(cx, |editor, cx| {
 8826        assert_text_with_selections(
 8827            editor,
 8828            indoc! {r#"
 8829                use mod1::mod2::{mod3, «mod4ˇ»};
 8830
 8831                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8832                    let var1 = "«ˇtext»";
 8833                }
 8834            "#},
 8835            cx,
 8836        );
 8837    });
 8838
 8839    editor.update_in(cx, |editor, window, cx| {
 8840        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8841    });
 8842    editor.update(cx, |editor, cx| {
 8843        assert_text_with_selections(
 8844            editor,
 8845            indoc! {r#"
 8846                use mod1::mod2::«{mod3, mod4}ˇ»;
 8847
 8848                «ˇfn fn_1(param1: bool, param2: &str) {
 8849                    let var1 = "text";
 8850 8851            "#},
 8852            cx,
 8853        );
 8854    });
 8855
 8856    editor.update_in(cx, |editor, window, cx| {
 8857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8858    });
 8859    assert_eq!(
 8860        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8861        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8862    );
 8863
 8864    // Trying to expand the selected syntax node one more time has no effect.
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    assert_eq!(
 8869        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8870        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8871    );
 8872
 8873    editor.update_in(cx, |editor, window, cx| {
 8874        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8875    });
 8876    editor.update(cx, |editor, cx| {
 8877        assert_text_with_selections(
 8878            editor,
 8879            indoc! {r#"
 8880                use mod1::mod2::«{mod3, mod4}ˇ»;
 8881
 8882                «ˇfn fn_1(param1: bool, param2: &str) {
 8883                    let var1 = "text";
 8884 8885            "#},
 8886            cx,
 8887        );
 8888    });
 8889
 8890    editor.update_in(cx, |editor, window, cx| {
 8891        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8892    });
 8893    editor.update(cx, |editor, cx| {
 8894        assert_text_with_selections(
 8895            editor,
 8896            indoc! {r#"
 8897                use mod1::mod2::{mod3, «mod4ˇ»};
 8898
 8899                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8900                    let var1 = "«ˇtext»";
 8901                }
 8902            "#},
 8903            cx,
 8904        );
 8905    });
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8909    });
 8910    editor.update(cx, |editor, cx| {
 8911        assert_text_with_selections(
 8912            editor,
 8913            indoc! {r#"
 8914                use mod1::mod2::{mod3, moˇd4};
 8915
 8916                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8917                    let var1 = "teˇxt";
 8918                }
 8919            "#},
 8920            cx,
 8921        );
 8922    });
 8923
 8924    // Trying to shrink the selected syntax node one more time has no effect.
 8925    editor.update_in(cx, |editor, window, cx| {
 8926        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8927    });
 8928    editor.update_in(cx, |editor, _, cx| {
 8929        assert_text_with_selections(
 8930            editor,
 8931            indoc! {r#"
 8932                use mod1::mod2::{mod3, moˇd4};
 8933
 8934                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8935                    let var1 = "teˇxt";
 8936                }
 8937            "#},
 8938            cx,
 8939        );
 8940    });
 8941
 8942    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8943    // a fold.
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.fold_creases(
 8946            vec![
 8947                Crease::simple(
 8948                    Point::new(0, 21)..Point::new(0, 24),
 8949                    FoldPlaceholder::test(),
 8950                ),
 8951                Crease::simple(
 8952                    Point::new(3, 20)..Point::new(3, 22),
 8953                    FoldPlaceholder::test(),
 8954                ),
 8955            ],
 8956            true,
 8957            window,
 8958            cx,
 8959        );
 8960        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8961    });
 8962    editor.update(cx, |editor, cx| {
 8963        assert_text_with_selections(
 8964            editor,
 8965            indoc! {r#"
 8966                use mod1::mod2::«{mod3, mod4}ˇ»;
 8967
 8968                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8969                    let var1 = "«ˇtext»";
 8970                }
 8971            "#},
 8972            cx,
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig::default(),
 8983        Some(tree_sitter_rust::LANGUAGE.into()),
 8984    ));
 8985
 8986    let text = "let a = 2;";
 8987
 8988    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8991
 8992    editor
 8993        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8994        .await;
 8995
 8996    // Test case 1: Cursor at end of word
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8999            s.select_display_ranges([
 9000                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9001            ]);
 9002        });
 9003    });
 9004    editor.update(cx, |editor, cx| {
 9005        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9006    });
 9007    editor.update_in(cx, |editor, window, cx| {
 9008        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9009    });
 9010    editor.update(cx, |editor, cx| {
 9011        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9012    });
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9015    });
 9016    editor.update(cx, |editor, cx| {
 9017        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9018    });
 9019
 9020    // Test case 2: Cursor at end of statement
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9023            s.select_display_ranges([
 9024                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9025            ]);
 9026        });
 9027    });
 9028    editor.update(cx, |editor, cx| {
 9029        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9033    });
 9034    editor.update(cx, |editor, cx| {
 9035        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9036    });
 9037}
 9038
 9039#[gpui::test]
 9040async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9041    init_test(cx, |_| {});
 9042
 9043    let language = Arc::new(Language::new(
 9044        LanguageConfig {
 9045            name: "JavaScript".into(),
 9046            ..Default::default()
 9047        },
 9048        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9049    ));
 9050
 9051    let text = r#"
 9052        let a = {
 9053            key: "value",
 9054        };
 9055    "#
 9056    .unindent();
 9057
 9058    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9059    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9061
 9062    editor
 9063        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9064        .await;
 9065
 9066    // Test case 1: Cursor after '{'
 9067    editor.update_in(cx, |editor, window, cx| {
 9068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9069            s.select_display_ranges([
 9070                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9071            ]);
 9072        });
 9073    });
 9074    editor.update(cx, |editor, cx| {
 9075        assert_text_with_selections(
 9076            editor,
 9077            indoc! {r#"
 9078                let a = {ˇ
 9079                    key: "value",
 9080                };
 9081            "#},
 9082            cx,
 9083        );
 9084    });
 9085    editor.update_in(cx, |editor, window, cx| {
 9086        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9087    });
 9088    editor.update(cx, |editor, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                let a = «ˇ{
 9093                    key: "value",
 9094                }»;
 9095            "#},
 9096            cx,
 9097        );
 9098    });
 9099
 9100    // Test case 2: Cursor after ':'
 9101    editor.update_in(cx, |editor, window, cx| {
 9102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9103            s.select_display_ranges([
 9104                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9105            ]);
 9106        });
 9107    });
 9108    editor.update(cx, |editor, cx| {
 9109        assert_text_with_selections(
 9110            editor,
 9111            indoc! {r#"
 9112                let a = {
 9113                    key:ˇ "value",
 9114                };
 9115            "#},
 9116            cx,
 9117        );
 9118    });
 9119    editor.update_in(cx, |editor, window, cx| {
 9120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                let a = {
 9127                    «ˇkey: "value"»,
 9128                };
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(
 9138            editor,
 9139            indoc! {r#"
 9140                let a = «ˇ{
 9141                    key: "value",
 9142                }»;
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147
 9148    // Test case 3: Cursor after ','
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9151            s.select_display_ranges([
 9152                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9153            ]);
 9154        });
 9155    });
 9156    editor.update(cx, |editor, cx| {
 9157        assert_text_with_selections(
 9158            editor,
 9159            indoc! {r#"
 9160                let a = {
 9161                    key: "value",ˇ
 9162                };
 9163            "#},
 9164            cx,
 9165        );
 9166    });
 9167    editor.update_in(cx, |editor, window, cx| {
 9168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9169    });
 9170    editor.update(cx, |editor, cx| {
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                let a = «ˇ{
 9175                    key: "value",
 9176                }»;
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181
 9182    // Test case 4: Cursor after ';'
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9185            s.select_display_ranges([
 9186                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9187            ]);
 9188        });
 9189    });
 9190    editor.update(cx, |editor, cx| {
 9191        assert_text_with_selections(
 9192            editor,
 9193            indoc! {r#"
 9194                let a = {
 9195                    key: "value",
 9196                };ˇ
 9197            "#},
 9198            cx,
 9199        );
 9200    });
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                «ˇlet a = {
 9209                    key: "value",
 9210                };
 9211                »"#},
 9212            cx,
 9213        );
 9214    });
 9215}
 9216
 9217#[gpui::test]
 9218async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9219    init_test(cx, |_| {});
 9220
 9221    let language = Arc::new(Language::new(
 9222        LanguageConfig::default(),
 9223        Some(tree_sitter_rust::LANGUAGE.into()),
 9224    ));
 9225
 9226    let text = r#"
 9227        use mod1::mod2::{mod3, mod4};
 9228
 9229        fn fn_1(param1: bool, param2: &str) {
 9230            let var1 = "hello world";
 9231        }
 9232    "#
 9233    .unindent();
 9234
 9235    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9236    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9237    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9238
 9239    editor
 9240        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9241        .await;
 9242
 9243    // Test 1: Cursor on a letter of a string word
 9244    editor.update_in(cx, |editor, window, cx| {
 9245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9246            s.select_display_ranges([
 9247                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9248            ]);
 9249        });
 9250    });
 9251    editor.update_in(cx, |editor, window, cx| {
 9252        assert_text_with_selections(
 9253            editor,
 9254            indoc! {r#"
 9255                use mod1::mod2::{mod3, mod4};
 9256
 9257                fn fn_1(param1: bool, param2: &str) {
 9258                    let var1 = "hˇello world";
 9259                }
 9260            "#},
 9261            cx,
 9262        );
 9263        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9264        assert_text_with_selections(
 9265            editor,
 9266            indoc! {r#"
 9267                use mod1::mod2::{mod3, mod4};
 9268
 9269                fn fn_1(param1: bool, param2: &str) {
 9270                    let var1 = "«ˇhello» world";
 9271                }
 9272            "#},
 9273            cx,
 9274        );
 9275    });
 9276
 9277    // Test 2: Partial selection within a word
 9278    editor.update_in(cx, |editor, window, cx| {
 9279        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9280            s.select_display_ranges([
 9281                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9282            ]);
 9283        });
 9284    });
 9285    editor.update_in(cx, |editor, window, cx| {
 9286        assert_text_with_selections(
 9287            editor,
 9288            indoc! {r#"
 9289                use mod1::mod2::{mod3, mod4};
 9290
 9291                fn fn_1(param1: bool, param2: &str) {
 9292                    let var1 = "h«elˇ»lo world";
 9293                }
 9294            "#},
 9295            cx,
 9296        );
 9297        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9298        assert_text_with_selections(
 9299            editor,
 9300            indoc! {r#"
 9301                use mod1::mod2::{mod3, mod4};
 9302
 9303                fn fn_1(param1: bool, param2: &str) {
 9304                    let var1 = "«ˇhello» world";
 9305                }
 9306            "#},
 9307            cx,
 9308        );
 9309    });
 9310
 9311    // Test 3: Complete word already selected
 9312    editor.update_in(cx, |editor, window, cx| {
 9313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9314            s.select_display_ranges([
 9315                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9316            ]);
 9317        });
 9318    });
 9319    editor.update_in(cx, |editor, window, cx| {
 9320        assert_text_with_selections(
 9321            editor,
 9322            indoc! {r#"
 9323                use mod1::mod2::{mod3, mod4};
 9324
 9325                fn fn_1(param1: bool, param2: &str) {
 9326                    let var1 = "«helloˇ» world";
 9327                }
 9328            "#},
 9329            cx,
 9330        );
 9331        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9332        assert_text_with_selections(
 9333            editor,
 9334            indoc! {r#"
 9335                use mod1::mod2::{mod3, mod4};
 9336
 9337                fn fn_1(param1: bool, param2: &str) {
 9338                    let var1 = "«hello worldˇ»";
 9339                }
 9340            "#},
 9341            cx,
 9342        );
 9343    });
 9344
 9345    // Test 4: Selection spanning across words
 9346    editor.update_in(cx, |editor, window, cx| {
 9347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9348            s.select_display_ranges([
 9349                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9350            ]);
 9351        });
 9352    });
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        assert_text_with_selections(
 9355            editor,
 9356            indoc! {r#"
 9357                use mod1::mod2::{mod3, mod4};
 9358
 9359                fn fn_1(param1: bool, param2: &str) {
 9360                    let var1 = "hel«lo woˇ»rld";
 9361                }
 9362            "#},
 9363            cx,
 9364        );
 9365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9366        assert_text_with_selections(
 9367            editor,
 9368            indoc! {r#"
 9369                use mod1::mod2::{mod3, mod4};
 9370
 9371                fn fn_1(param1: bool, param2: &str) {
 9372                    let var1 = "«ˇhello world»";
 9373                }
 9374            "#},
 9375            cx,
 9376        );
 9377    });
 9378
 9379    // Test 5: Expansion beyond string
 9380    editor.update_in(cx, |editor, window, cx| {
 9381        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9382        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9383        assert_text_with_selections(
 9384            editor,
 9385            indoc! {r#"
 9386                use mod1::mod2::{mod3, mod4};
 9387
 9388                fn fn_1(param1: bool, param2: &str) {
 9389                    «ˇlet var1 = "hello world";»
 9390                }
 9391            "#},
 9392            cx,
 9393        );
 9394    });
 9395}
 9396
 9397#[gpui::test]
 9398async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9399    init_test(cx, |_| {});
 9400
 9401    let mut cx = EditorTestContext::new(cx).await;
 9402
 9403    let language = Arc::new(Language::new(
 9404        LanguageConfig::default(),
 9405        Some(tree_sitter_rust::LANGUAGE.into()),
 9406    ));
 9407
 9408    cx.update_buffer(|buffer, cx| {
 9409        buffer.set_language(Some(language), cx);
 9410    });
 9411
 9412    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9413    cx.update_editor(|editor, window, cx| {
 9414        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9415    });
 9416
 9417    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9418
 9419    cx.set_state(indoc! { r#"fn a() {
 9420          // what
 9421          // a
 9422          // ˇlong
 9423          // method
 9424          // I
 9425          // sure
 9426          // hope
 9427          // it
 9428          // works
 9429    }"# });
 9430
 9431    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9432    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9433    cx.update(|_, cx| {
 9434        multi_buffer.update(cx, |multi_buffer, cx| {
 9435            multi_buffer.set_excerpts_for_path(
 9436                PathKey::for_buffer(&buffer, cx),
 9437                buffer,
 9438                [Point::new(1, 0)..Point::new(1, 0)],
 9439                3,
 9440                cx,
 9441            );
 9442        });
 9443    });
 9444
 9445    let editor2 = cx.new_window_entity(|window, cx| {
 9446        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9447    });
 9448
 9449    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9450    cx.update_editor(|editor, window, cx| {
 9451        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9452            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9453        })
 9454    });
 9455
 9456    cx.assert_editor_state(indoc! { "
 9457        fn a() {
 9458              // what
 9459              // a
 9460        ˇ      // long
 9461              // method"});
 9462
 9463    cx.update_editor(|editor, window, cx| {
 9464        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9465    });
 9466
 9467    // Although we could potentially make the action work when the syntax node
 9468    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9469    // did. Maybe we could also expand the excerpt to contain the range?
 9470    cx.assert_editor_state(indoc! { "
 9471        fn a() {
 9472              // what
 9473              // a
 9474        ˇ      // long
 9475              // method"});
 9476}
 9477
 9478#[gpui::test]
 9479async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9480    init_test(cx, |_| {});
 9481
 9482    let base_text = r#"
 9483        impl A {
 9484            // this is an uncommitted comment
 9485
 9486            fn b() {
 9487                c();
 9488            }
 9489
 9490            // this is another uncommitted comment
 9491
 9492            fn d() {
 9493                // e
 9494                // f
 9495            }
 9496        }
 9497
 9498        fn g() {
 9499            // h
 9500        }
 9501    "#
 9502    .unindent();
 9503
 9504    let text = r#"
 9505        ˇimpl A {
 9506
 9507            fn b() {
 9508                c();
 9509            }
 9510
 9511            fn d() {
 9512                // e
 9513                // f
 9514            }
 9515        }
 9516
 9517        fn g() {
 9518            // h
 9519        }
 9520    "#
 9521    .unindent();
 9522
 9523    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9524    cx.set_state(&text);
 9525    cx.set_head_text(&base_text);
 9526    cx.update_editor(|editor, window, cx| {
 9527        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9528    });
 9529
 9530    cx.assert_state_with_diff(
 9531        "
 9532        ˇimpl A {
 9533      -     // this is an uncommitted comment
 9534
 9535            fn b() {
 9536                c();
 9537            }
 9538
 9539      -     // this is another uncommitted comment
 9540      -
 9541            fn d() {
 9542                // e
 9543                // f
 9544            }
 9545        }
 9546
 9547        fn g() {
 9548            // h
 9549        }
 9550    "
 9551        .unindent(),
 9552    );
 9553
 9554    let expected_display_text = "
 9555        impl A {
 9556            // this is an uncommitted comment
 9557
 9558            fn b() {
 9559 9560            }
 9561
 9562            // this is another uncommitted comment
 9563
 9564            fn d() {
 9565 9566            }
 9567        }
 9568
 9569        fn g() {
 9570 9571        }
 9572        "
 9573    .unindent();
 9574
 9575    cx.update_editor(|editor, window, cx| {
 9576        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9577        assert_eq!(editor.display_text(cx), expected_display_text);
 9578    });
 9579}
 9580
 9581#[gpui::test]
 9582async fn test_autoindent(cx: &mut TestAppContext) {
 9583    init_test(cx, |_| {});
 9584
 9585    let language = Arc::new(
 9586        Language::new(
 9587            LanguageConfig {
 9588                brackets: BracketPairConfig {
 9589                    pairs: vec![
 9590                        BracketPair {
 9591                            start: "{".to_string(),
 9592                            end: "}".to_string(),
 9593                            close: false,
 9594                            surround: false,
 9595                            newline: true,
 9596                        },
 9597                        BracketPair {
 9598                            start: "(".to_string(),
 9599                            end: ")".to_string(),
 9600                            close: false,
 9601                            surround: false,
 9602                            newline: true,
 9603                        },
 9604                    ],
 9605                    ..Default::default()
 9606                },
 9607                ..Default::default()
 9608            },
 9609            Some(tree_sitter_rust::LANGUAGE.into()),
 9610        )
 9611        .with_indents_query(
 9612            r#"
 9613                (_ "(" ")" @end) @indent
 9614                (_ "{" "}" @end) @indent
 9615            "#,
 9616        )
 9617        .unwrap(),
 9618    );
 9619
 9620    let text = "fn a() {}";
 9621
 9622    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9623    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9624    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9625    editor
 9626        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9627        .await;
 9628
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9631            s.select_ranges([5..5, 8..8, 9..9])
 9632        });
 9633        editor.newline(&Newline, window, cx);
 9634        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9635        assert_eq!(
 9636            editor.selections.ranges(&editor.display_snapshot(cx)),
 9637            &[
 9638                Point::new(1, 4)..Point::new(1, 4),
 9639                Point::new(3, 4)..Point::new(3, 4),
 9640                Point::new(5, 0)..Point::new(5, 0)
 9641            ]
 9642        );
 9643    });
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9648    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9649
 9650    let language = Arc::new(
 9651        Language::new(
 9652            LanguageConfig {
 9653                brackets: BracketPairConfig {
 9654                    pairs: vec![
 9655                        BracketPair {
 9656                            start: "{".to_string(),
 9657                            end: "}".to_string(),
 9658                            close: false,
 9659                            surround: false,
 9660                            newline: true,
 9661                        },
 9662                        BracketPair {
 9663                            start: "(".to_string(),
 9664                            end: ")".to_string(),
 9665                            close: false,
 9666                            surround: false,
 9667                            newline: true,
 9668                        },
 9669                    ],
 9670                    ..Default::default()
 9671                },
 9672                ..Default::default()
 9673            },
 9674            Some(tree_sitter_rust::LANGUAGE.into()),
 9675        )
 9676        .with_indents_query(
 9677            r#"
 9678                (_ "(" ")" @end) @indent
 9679                (_ "{" "}" @end) @indent
 9680            "#,
 9681        )
 9682        .unwrap(),
 9683    );
 9684
 9685    let text = "fn a() {}";
 9686
 9687    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9688    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9689    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9690    editor
 9691        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9692        .await;
 9693
 9694    editor.update_in(cx, |editor, window, cx| {
 9695        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9696            s.select_ranges([5..5, 8..8, 9..9])
 9697        });
 9698        editor.newline(&Newline, window, cx);
 9699        assert_eq!(
 9700            editor.text(cx),
 9701            indoc!(
 9702                "
 9703                fn a(
 9704
 9705                ) {
 9706
 9707                }
 9708                "
 9709            )
 9710        );
 9711        assert_eq!(
 9712            editor.selections.ranges(&editor.display_snapshot(cx)),
 9713            &[
 9714                Point::new(1, 0)..Point::new(1, 0),
 9715                Point::new(3, 0)..Point::new(3, 0),
 9716                Point::new(5, 0)..Point::new(5, 0)
 9717            ]
 9718        );
 9719    });
 9720}
 9721
 9722#[gpui::test]
 9723async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9724    init_test(cx, |settings| {
 9725        settings.defaults.auto_indent = Some(true);
 9726        settings.languages.0.insert(
 9727            "python".into(),
 9728            LanguageSettingsContent {
 9729                auto_indent: Some(false),
 9730                ..Default::default()
 9731            },
 9732        );
 9733    });
 9734
 9735    let mut cx = EditorTestContext::new(cx).await;
 9736
 9737    let injected_language = Arc::new(
 9738        Language::new(
 9739            LanguageConfig {
 9740                brackets: BracketPairConfig {
 9741                    pairs: vec![
 9742                        BracketPair {
 9743                            start: "{".to_string(),
 9744                            end: "}".to_string(),
 9745                            close: false,
 9746                            surround: false,
 9747                            newline: true,
 9748                        },
 9749                        BracketPair {
 9750                            start: "(".to_string(),
 9751                            end: ")".to_string(),
 9752                            close: true,
 9753                            surround: false,
 9754                            newline: true,
 9755                        },
 9756                    ],
 9757                    ..Default::default()
 9758                },
 9759                name: "python".into(),
 9760                ..Default::default()
 9761            },
 9762            Some(tree_sitter_python::LANGUAGE.into()),
 9763        )
 9764        .with_indents_query(
 9765            r#"
 9766                (_ "(" ")" @end) @indent
 9767                (_ "{" "}" @end) @indent
 9768            "#,
 9769        )
 9770        .unwrap(),
 9771    );
 9772
 9773    let language = Arc::new(
 9774        Language::new(
 9775            LanguageConfig {
 9776                brackets: BracketPairConfig {
 9777                    pairs: vec![
 9778                        BracketPair {
 9779                            start: "{".to_string(),
 9780                            end: "}".to_string(),
 9781                            close: false,
 9782                            surround: false,
 9783                            newline: true,
 9784                        },
 9785                        BracketPair {
 9786                            start: "(".to_string(),
 9787                            end: ")".to_string(),
 9788                            close: true,
 9789                            surround: false,
 9790                            newline: true,
 9791                        },
 9792                    ],
 9793                    ..Default::default()
 9794                },
 9795                name: LanguageName::new("rust"),
 9796                ..Default::default()
 9797            },
 9798            Some(tree_sitter_rust::LANGUAGE.into()),
 9799        )
 9800        .with_indents_query(
 9801            r#"
 9802                (_ "(" ")" @end) @indent
 9803                (_ "{" "}" @end) @indent
 9804            "#,
 9805        )
 9806        .unwrap()
 9807        .with_injection_query(
 9808            r#"
 9809            (macro_invocation
 9810                macro: (identifier) @_macro_name
 9811                (token_tree) @injection.content
 9812                (#set! injection.language "python"))
 9813           "#,
 9814        )
 9815        .unwrap(),
 9816    );
 9817
 9818    cx.language_registry().add(injected_language);
 9819    cx.language_registry().add(language.clone());
 9820
 9821    cx.update_buffer(|buffer, cx| {
 9822        buffer.set_language(Some(language), cx);
 9823    });
 9824
 9825    cx.set_state(r#"struct A {ˇ}"#);
 9826
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.newline(&Default::default(), window, cx);
 9829    });
 9830
 9831    cx.assert_editor_state(indoc!(
 9832        "struct A {
 9833            ˇ
 9834        }"
 9835    ));
 9836
 9837    cx.set_state(r#"select_biased!(ˇ)"#);
 9838
 9839    cx.update_editor(|editor, window, cx| {
 9840        editor.newline(&Default::default(), window, cx);
 9841        editor.handle_input("def ", window, cx);
 9842        editor.handle_input("(", window, cx);
 9843        editor.newline(&Default::default(), window, cx);
 9844        editor.handle_input("a", window, cx);
 9845    });
 9846
 9847    cx.assert_editor_state(indoc!(
 9848        "select_biased!(
 9849        def (
 9850 9851        )
 9852        )"
 9853    ));
 9854}
 9855
 9856#[gpui::test]
 9857async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9858    init_test(cx, |_| {});
 9859
 9860    {
 9861        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9862        cx.set_state(indoc! {"
 9863            impl A {
 9864
 9865                fn b() {}
 9866
 9867            «fn c() {
 9868
 9869            }ˇ»
 9870            }
 9871        "});
 9872
 9873        cx.update_editor(|editor, window, cx| {
 9874            editor.autoindent(&Default::default(), window, cx);
 9875        });
 9876
 9877        cx.assert_editor_state(indoc! {"
 9878            impl A {
 9879
 9880                fn b() {}
 9881
 9882                «fn c() {
 9883
 9884                }ˇ»
 9885            }
 9886        "});
 9887    }
 9888
 9889    {
 9890        let mut cx = EditorTestContext::new_multibuffer(
 9891            cx,
 9892            [indoc! { "
 9893                impl A {
 9894                «
 9895                // a
 9896                fn b(){}
 9897                »
 9898                «
 9899                    }
 9900                    fn c(){}
 9901                »
 9902            "}],
 9903        );
 9904
 9905        let buffer = cx.update_editor(|editor, _, cx| {
 9906            let buffer = editor.buffer().update(cx, |buffer, _| {
 9907                buffer.all_buffers().iter().next().unwrap().clone()
 9908            });
 9909            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9910            buffer
 9911        });
 9912
 9913        cx.run_until_parked();
 9914        cx.update_editor(|editor, window, cx| {
 9915            editor.select_all(&Default::default(), window, cx);
 9916            editor.autoindent(&Default::default(), window, cx)
 9917        });
 9918        cx.run_until_parked();
 9919
 9920        cx.update(|_, cx| {
 9921            assert_eq!(
 9922                buffer.read(cx).text(),
 9923                indoc! { "
 9924                    impl A {
 9925
 9926                        // a
 9927                        fn b(){}
 9928
 9929
 9930                    }
 9931                    fn c(){}
 9932
 9933                " }
 9934            )
 9935        });
 9936    }
 9937}
 9938
 9939#[gpui::test]
 9940async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9941    init_test(cx, |_| {});
 9942
 9943    let mut cx = EditorTestContext::new(cx).await;
 9944
 9945    let language = Arc::new(Language::new(
 9946        LanguageConfig {
 9947            brackets: BracketPairConfig {
 9948                pairs: vec![
 9949                    BracketPair {
 9950                        start: "{".to_string(),
 9951                        end: "}".to_string(),
 9952                        close: true,
 9953                        surround: true,
 9954                        newline: true,
 9955                    },
 9956                    BracketPair {
 9957                        start: "(".to_string(),
 9958                        end: ")".to_string(),
 9959                        close: true,
 9960                        surround: true,
 9961                        newline: true,
 9962                    },
 9963                    BracketPair {
 9964                        start: "/*".to_string(),
 9965                        end: " */".to_string(),
 9966                        close: true,
 9967                        surround: true,
 9968                        newline: true,
 9969                    },
 9970                    BracketPair {
 9971                        start: "[".to_string(),
 9972                        end: "]".to_string(),
 9973                        close: false,
 9974                        surround: false,
 9975                        newline: true,
 9976                    },
 9977                    BracketPair {
 9978                        start: "\"".to_string(),
 9979                        end: "\"".to_string(),
 9980                        close: true,
 9981                        surround: true,
 9982                        newline: false,
 9983                    },
 9984                    BracketPair {
 9985                        start: "<".to_string(),
 9986                        end: ">".to_string(),
 9987                        close: false,
 9988                        surround: true,
 9989                        newline: true,
 9990                    },
 9991                ],
 9992                ..Default::default()
 9993            },
 9994            autoclose_before: "})]".to_string(),
 9995            ..Default::default()
 9996        },
 9997        Some(tree_sitter_rust::LANGUAGE.into()),
 9998    ));
 9999
10000    cx.language_registry().add(language.clone());
10001    cx.update_buffer(|buffer, cx| {
10002        buffer.set_language(Some(language), cx);
10003    });
10004
10005    cx.set_state(
10006        &r#"
10007            🏀ˇ
10008            εˇ
10009            ❤️ˇ
10010        "#
10011        .unindent(),
10012    );
10013
10014    // autoclose multiple nested brackets at multiple cursors
10015    cx.update_editor(|editor, window, cx| {
10016        editor.handle_input("{", window, cx);
10017        editor.handle_input("{", window, cx);
10018        editor.handle_input("{", window, cx);
10019    });
10020    cx.assert_editor_state(
10021        &"
10022            🏀{{{ˇ}}}
10023            ε{{{ˇ}}}
10024            ❤️{{{ˇ}}}
10025        "
10026        .unindent(),
10027    );
10028
10029    // insert a different closing bracket
10030    cx.update_editor(|editor, window, cx| {
10031        editor.handle_input(")", window, cx);
10032    });
10033    cx.assert_editor_state(
10034        &"
10035            🏀{{{)ˇ}}}
10036            ε{{{)ˇ}}}
10037            ❤️{{{)ˇ}}}
10038        "
10039        .unindent(),
10040    );
10041
10042    // skip over the auto-closed brackets when typing a closing bracket
10043    cx.update_editor(|editor, window, cx| {
10044        editor.move_right(&MoveRight, window, cx);
10045        editor.handle_input("}", window, cx);
10046        editor.handle_input("}", window, cx);
10047        editor.handle_input("}", window, cx);
10048    });
10049    cx.assert_editor_state(
10050        &"
10051            🏀{{{)}}}}ˇ
10052            ε{{{)}}}}ˇ
10053            ❤️{{{)}}}}ˇ
10054        "
10055        .unindent(),
10056    );
10057
10058    // autoclose multi-character pairs
10059    cx.set_state(
10060        &"
10061            ˇ
10062            ˇ
10063        "
10064        .unindent(),
10065    );
10066    cx.update_editor(|editor, window, cx| {
10067        editor.handle_input("/", window, cx);
10068        editor.handle_input("*", window, cx);
10069    });
10070    cx.assert_editor_state(
10071        &"
10072            /*ˇ */
10073            /*ˇ */
10074        "
10075        .unindent(),
10076    );
10077
10078    // one cursor autocloses a multi-character pair, one cursor
10079    // does not autoclose.
10080    cx.set_state(
10081        &"
1008210083            ˇ
10084        "
10085        .unindent(),
10086    );
10087    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10088    cx.assert_editor_state(
10089        &"
10090            /*ˇ */
1009110092        "
10093        .unindent(),
10094    );
10095
10096    // Don't autoclose if the next character isn't whitespace and isn't
10097    // listed in the language's "autoclose_before" section.
10098    cx.set_state("ˇa b");
10099    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10100    cx.assert_editor_state("{ˇa b");
10101
10102    // Don't autoclose if `close` is false for the bracket pair
10103    cx.set_state("ˇ");
10104    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10105    cx.assert_editor_state("");
10106
10107    // Surround with brackets if text is selected
10108    cx.set_state("«aˇ» b");
10109    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10110    cx.assert_editor_state("{«aˇ»} b");
10111
10112    // Autoclose when not immediately after a word character
10113    cx.set_state("a ˇ");
10114    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10115    cx.assert_editor_state("a \"ˇ\"");
10116
10117    // Autoclose pair where the start and end characters are the same
10118    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10119    cx.assert_editor_state("a \"\"ˇ");
10120
10121    // Don't autoclose when immediately after a word character
10122    cx.set_state("");
10123    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10124    cx.assert_editor_state("a\"ˇ");
10125
10126    // Do autoclose when after a non-word character
10127    cx.set_state("");
10128    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10129    cx.assert_editor_state("{\"ˇ\"");
10130
10131    // Non identical pairs autoclose regardless of preceding character
10132    cx.set_state("");
10133    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10134    cx.assert_editor_state("a{ˇ}");
10135
10136    // Don't autoclose pair if autoclose is disabled
10137    cx.set_state("ˇ");
10138    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10139    cx.assert_editor_state("");
10140
10141    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10142    cx.set_state("«aˇ» b");
10143    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10144    cx.assert_editor_state("<«aˇ»> b");
10145}
10146
10147#[gpui::test]
10148async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10149    init_test(cx, |settings| {
10150        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10151    });
10152
10153    let mut cx = EditorTestContext::new(cx).await;
10154
10155    let language = Arc::new(Language::new(
10156        LanguageConfig {
10157            brackets: BracketPairConfig {
10158                pairs: vec![
10159                    BracketPair {
10160                        start: "{".to_string(),
10161                        end: "}".to_string(),
10162                        close: true,
10163                        surround: true,
10164                        newline: true,
10165                    },
10166                    BracketPair {
10167                        start: "(".to_string(),
10168                        end: ")".to_string(),
10169                        close: true,
10170                        surround: true,
10171                        newline: true,
10172                    },
10173                    BracketPair {
10174                        start: "[".to_string(),
10175                        end: "]".to_string(),
10176                        close: false,
10177                        surround: false,
10178                        newline: true,
10179                    },
10180                ],
10181                ..Default::default()
10182            },
10183            autoclose_before: "})]".to_string(),
10184            ..Default::default()
10185        },
10186        Some(tree_sitter_rust::LANGUAGE.into()),
10187    ));
10188
10189    cx.language_registry().add(language.clone());
10190    cx.update_buffer(|buffer, cx| {
10191        buffer.set_language(Some(language), cx);
10192    });
10193
10194    cx.set_state(
10195        &"
10196            ˇ
10197            ˇ
10198            ˇ
10199        "
10200        .unindent(),
10201    );
10202
10203    // ensure only matching closing brackets are skipped over
10204    cx.update_editor(|editor, window, cx| {
10205        editor.handle_input("}", window, cx);
10206        editor.move_left(&MoveLeft, window, cx);
10207        editor.handle_input(")", window, cx);
10208        editor.move_left(&MoveLeft, window, cx);
10209    });
10210    cx.assert_editor_state(
10211        &"
10212            ˇ)}
10213            ˇ)}
10214            ˇ)}
10215        "
10216        .unindent(),
10217    );
10218
10219    // skip-over closing brackets at multiple cursors
10220    cx.update_editor(|editor, window, cx| {
10221        editor.handle_input(")", window, cx);
10222        editor.handle_input("}", window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &"
10226            )}ˇ
10227            )}ˇ
10228            )}ˇ
10229        "
10230        .unindent(),
10231    );
10232
10233    // ignore non-close brackets
10234    cx.update_editor(|editor, window, cx| {
10235        editor.handle_input("]", window, cx);
10236        editor.move_left(&MoveLeft, window, cx);
10237        editor.handle_input("]", window, cx);
10238    });
10239    cx.assert_editor_state(
10240        &"
10241            )}]ˇ]
10242            )}]ˇ]
10243            )}]ˇ]
10244        "
10245        .unindent(),
10246    );
10247}
10248
10249#[gpui::test]
10250async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10251    init_test(cx, |_| {});
10252
10253    let mut cx = EditorTestContext::new(cx).await;
10254
10255    let html_language = Arc::new(
10256        Language::new(
10257            LanguageConfig {
10258                name: "HTML".into(),
10259                brackets: BracketPairConfig {
10260                    pairs: vec![
10261                        BracketPair {
10262                            start: "<".into(),
10263                            end: ">".into(),
10264                            close: true,
10265                            ..Default::default()
10266                        },
10267                        BracketPair {
10268                            start: "{".into(),
10269                            end: "}".into(),
10270                            close: true,
10271                            ..Default::default()
10272                        },
10273                        BracketPair {
10274                            start: "(".into(),
10275                            end: ")".into(),
10276                            close: true,
10277                            ..Default::default()
10278                        },
10279                    ],
10280                    ..Default::default()
10281                },
10282                autoclose_before: "})]>".into(),
10283                ..Default::default()
10284            },
10285            Some(tree_sitter_html::LANGUAGE.into()),
10286        )
10287        .with_injection_query(
10288            r#"
10289            (script_element
10290                (raw_text) @injection.content
10291                (#set! injection.language "javascript"))
10292            "#,
10293        )
10294        .unwrap(),
10295    );
10296
10297    let javascript_language = Arc::new(Language::new(
10298        LanguageConfig {
10299            name: "JavaScript".into(),
10300            brackets: BracketPairConfig {
10301                pairs: vec![
10302                    BracketPair {
10303                        start: "/*".into(),
10304                        end: " */".into(),
10305                        close: true,
10306                        ..Default::default()
10307                    },
10308                    BracketPair {
10309                        start: "{".into(),
10310                        end: "}".into(),
10311                        close: true,
10312                        ..Default::default()
10313                    },
10314                    BracketPair {
10315                        start: "(".into(),
10316                        end: ")".into(),
10317                        close: true,
10318                        ..Default::default()
10319                    },
10320                ],
10321                ..Default::default()
10322            },
10323            autoclose_before: "})]>".into(),
10324            ..Default::default()
10325        },
10326        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10327    ));
10328
10329    cx.language_registry().add(html_language.clone());
10330    cx.language_registry().add(javascript_language);
10331    cx.executor().run_until_parked();
10332
10333    cx.update_buffer(|buffer, cx| {
10334        buffer.set_language(Some(html_language), cx);
10335    });
10336
10337    cx.set_state(
10338        &r#"
10339            <body>ˇ
10340                <script>
10341                    var x = 1;ˇ
10342                </script>
10343            </body>ˇ
10344        "#
10345        .unindent(),
10346    );
10347
10348    // Precondition: different languages are active at different locations.
10349    cx.update_editor(|editor, window, cx| {
10350        let snapshot = editor.snapshot(window, cx);
10351        let cursors = editor
10352            .selections
10353            .ranges::<usize>(&editor.display_snapshot(cx));
10354        let languages = cursors
10355            .iter()
10356            .map(|c| snapshot.language_at(c.start).unwrap().name())
10357            .collect::<Vec<_>>();
10358        assert_eq!(
10359            languages,
10360            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10361        );
10362    });
10363
10364    // Angle brackets autoclose in HTML, but not JavaScript.
10365    cx.update_editor(|editor, window, cx| {
10366        editor.handle_input("<", window, cx);
10367        editor.handle_input("a", window, cx);
10368    });
10369    cx.assert_editor_state(
10370        &r#"
10371            <body><aˇ>
10372                <script>
10373                    var x = 1;<aˇ
10374                </script>
10375            </body><aˇ>
10376        "#
10377        .unindent(),
10378    );
10379
10380    // Curly braces and parens autoclose in both HTML and JavaScript.
10381    cx.update_editor(|editor, window, cx| {
10382        editor.handle_input(" b=", window, cx);
10383        editor.handle_input("{", window, cx);
10384        editor.handle_input("c", window, cx);
10385        editor.handle_input("(", window, cx);
10386    });
10387    cx.assert_editor_state(
10388        &r#"
10389            <body><a b={c(ˇ)}>
10390                <script>
10391                    var x = 1;<a b={c(ˇ)}
10392                </script>
10393            </body><a b={c(ˇ)}>
10394        "#
10395        .unindent(),
10396    );
10397
10398    // Brackets that were already autoclosed are skipped.
10399    cx.update_editor(|editor, window, cx| {
10400        editor.handle_input(")", window, cx);
10401        editor.handle_input("d", window, cx);
10402        editor.handle_input("}", window, cx);
10403    });
10404    cx.assert_editor_state(
10405        &r#"
10406            <body><a b={c()d}ˇ>
10407                <script>
10408                    var x = 1;<a b={c()d}ˇ
10409                </script>
10410            </body><a b={c()d}ˇ>
10411        "#
10412        .unindent(),
10413    );
10414    cx.update_editor(|editor, window, cx| {
10415        editor.handle_input(">", window, cx);
10416    });
10417    cx.assert_editor_state(
10418        &r#"
10419            <body><a b={c()d}>ˇ
10420                <script>
10421                    var x = 1;<a b={c()d}>ˇ
10422                </script>
10423            </body><a b={c()d}>ˇ
10424        "#
10425        .unindent(),
10426    );
10427
10428    // Reset
10429    cx.set_state(
10430        &r#"
10431            <body>ˇ
10432                <script>
10433                    var x = 1;ˇ
10434                </script>
10435            </body>ˇ
10436        "#
10437        .unindent(),
10438    );
10439
10440    cx.update_editor(|editor, window, cx| {
10441        editor.handle_input("<", window, cx);
10442    });
10443    cx.assert_editor_state(
10444        &r#"
10445            <body><ˇ>
10446                <script>
10447                    var x = 1;<ˇ
10448                </script>
10449            </body><ˇ>
10450        "#
10451        .unindent(),
10452    );
10453
10454    // When backspacing, the closing angle brackets are removed.
10455    cx.update_editor(|editor, window, cx| {
10456        editor.backspace(&Backspace, window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body>ˇ
10461                <script>
10462                    var x = 1;ˇ
10463                </script>
10464            </body>ˇ
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Block comments autoclose in JavaScript, but not HTML.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input("/", window, cx);
10472        editor.handle_input("*", window, cx);
10473    });
10474    cx.assert_editor_state(
10475        &r#"
10476            <body>/*ˇ
10477                <script>
10478                    var x = 1;/*ˇ */
10479                </script>
10480            </body>/*ˇ
10481        "#
10482        .unindent(),
10483    );
10484}
10485
10486#[gpui::test]
10487async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10488    init_test(cx, |_| {});
10489
10490    let mut cx = EditorTestContext::new(cx).await;
10491
10492    let rust_language = Arc::new(
10493        Language::new(
10494            LanguageConfig {
10495                name: "Rust".into(),
10496                brackets: serde_json::from_value(json!([
10497                    { "start": "{", "end": "}", "close": true, "newline": true },
10498                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10499                ]))
10500                .unwrap(),
10501                autoclose_before: "})]>".into(),
10502                ..Default::default()
10503            },
10504            Some(tree_sitter_rust::LANGUAGE.into()),
10505        )
10506        .with_override_query("(string_literal) @string")
10507        .unwrap(),
10508    );
10509
10510    cx.language_registry().add(rust_language.clone());
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(rust_language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            let x = ˇ
10518        "#
10519        .unindent(),
10520    );
10521
10522    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10523    cx.update_editor(|editor, window, cx| {
10524        editor.handle_input("\"", window, cx);
10525    });
10526    cx.assert_editor_state(
10527        &r#"
10528            let x = "ˇ"
10529        "#
10530        .unindent(),
10531    );
10532
10533    // Inserting another quotation mark. The cursor moves across the existing
10534    // automatically-inserted quotation mark.
10535    cx.update_editor(|editor, window, cx| {
10536        editor.handle_input("\"", window, cx);
10537    });
10538    cx.assert_editor_state(
10539        &r#"
10540            let x = ""ˇ
10541        "#
10542        .unindent(),
10543    );
10544
10545    // Reset
10546    cx.set_state(
10547        &r#"
10548            let x = ˇ
10549        "#
10550        .unindent(),
10551    );
10552
10553    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10554    cx.update_editor(|editor, window, cx| {
10555        editor.handle_input("\"", window, cx);
10556        editor.handle_input(" ", window, cx);
10557        editor.move_left(&Default::default(), window, cx);
10558        editor.handle_input("\\", window, cx);
10559        editor.handle_input("\"", window, cx);
10560    });
10561    cx.assert_editor_state(
10562        &r#"
10563            let x = "\"ˇ "
10564        "#
10565        .unindent(),
10566    );
10567
10568    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10569    // mark. Nothing is inserted.
10570    cx.update_editor(|editor, window, cx| {
10571        editor.move_right(&Default::default(), window, cx);
10572        editor.handle_input("\"", window, cx);
10573    });
10574    cx.assert_editor_state(
10575        &r#"
10576            let x = "\" "ˇ
10577        "#
10578        .unindent(),
10579    );
10580}
10581
10582#[gpui::test]
10583async fn test_surround_with_pair(cx: &mut TestAppContext) {
10584    init_test(cx, |_| {});
10585
10586    let language = Arc::new(Language::new(
10587        LanguageConfig {
10588            brackets: BracketPairConfig {
10589                pairs: vec![
10590                    BracketPair {
10591                        start: "{".to_string(),
10592                        end: "}".to_string(),
10593                        close: true,
10594                        surround: true,
10595                        newline: true,
10596                    },
10597                    BracketPair {
10598                        start: "/* ".to_string(),
10599                        end: "*/".to_string(),
10600                        close: true,
10601                        surround: true,
10602                        ..Default::default()
10603                    },
10604                ],
10605                ..Default::default()
10606            },
10607            ..Default::default()
10608        },
10609        Some(tree_sitter_rust::LANGUAGE.into()),
10610    ));
10611
10612    let text = r#"
10613        a
10614        b
10615        c
10616    "#
10617    .unindent();
10618
10619    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10620    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10621    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10622    editor
10623        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10624        .await;
10625
10626    editor.update_in(cx, |editor, window, cx| {
10627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10628            s.select_display_ranges([
10629                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10630                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10631                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10632            ])
10633        });
10634
10635        editor.handle_input("{", window, cx);
10636        editor.handle_input("{", window, cx);
10637        editor.handle_input("{", window, cx);
10638        assert_eq!(
10639            editor.text(cx),
10640            "
10641                {{{a}}}
10642                {{{b}}}
10643                {{{c}}}
10644            "
10645            .unindent()
10646        );
10647        assert_eq!(
10648            editor.selections.display_ranges(cx),
10649            [
10650                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10651                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10652                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10653            ]
10654        );
10655
10656        editor.undo(&Undo, window, cx);
10657        editor.undo(&Undo, window, cx);
10658        editor.undo(&Undo, window, cx);
10659        assert_eq!(
10660            editor.text(cx),
10661            "
10662                a
10663                b
10664                c
10665            "
10666            .unindent()
10667        );
10668        assert_eq!(
10669            editor.selections.display_ranges(cx),
10670            [
10671                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10672                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10673                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10674            ]
10675        );
10676
10677        // Ensure inserting the first character of a multi-byte bracket pair
10678        // doesn't surround the selections with the bracket.
10679        editor.handle_input("/", window, cx);
10680        assert_eq!(
10681            editor.text(cx),
10682            "
10683                /
10684                /
10685                /
10686            "
10687            .unindent()
10688        );
10689        assert_eq!(
10690            editor.selections.display_ranges(cx),
10691            [
10692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10693                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10694                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10695            ]
10696        );
10697
10698        editor.undo(&Undo, window, cx);
10699        assert_eq!(
10700            editor.text(cx),
10701            "
10702                a
10703                b
10704                c
10705            "
10706            .unindent()
10707        );
10708        assert_eq!(
10709            editor.selections.display_ranges(cx),
10710            [
10711                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10712                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10713                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10714            ]
10715        );
10716
10717        // Ensure inserting the last character of a multi-byte bracket pair
10718        // doesn't surround the selections with the bracket.
10719        editor.handle_input("*", window, cx);
10720        assert_eq!(
10721            editor.text(cx),
10722            "
10723                *
10724                *
10725                *
10726            "
10727            .unindent()
10728        );
10729        assert_eq!(
10730            editor.selections.display_ranges(cx),
10731            [
10732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10733                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10734                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10735            ]
10736        );
10737    });
10738}
10739
10740#[gpui::test]
10741async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10742    init_test(cx, |_| {});
10743
10744    let language = Arc::new(Language::new(
10745        LanguageConfig {
10746            brackets: BracketPairConfig {
10747                pairs: vec![BracketPair {
10748                    start: "{".to_string(),
10749                    end: "}".to_string(),
10750                    close: true,
10751                    surround: true,
10752                    newline: true,
10753                }],
10754                ..Default::default()
10755            },
10756            autoclose_before: "}".to_string(),
10757            ..Default::default()
10758        },
10759        Some(tree_sitter_rust::LANGUAGE.into()),
10760    ));
10761
10762    let text = r#"
10763        a
10764        b
10765        c
10766    "#
10767    .unindent();
10768
10769    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10770    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10771    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10772    editor
10773        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10774        .await;
10775
10776    editor.update_in(cx, |editor, window, cx| {
10777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10778            s.select_ranges([
10779                Point::new(0, 1)..Point::new(0, 1),
10780                Point::new(1, 1)..Point::new(1, 1),
10781                Point::new(2, 1)..Point::new(2, 1),
10782            ])
10783        });
10784
10785        editor.handle_input("{", window, cx);
10786        editor.handle_input("{", window, cx);
10787        editor.handle_input("_", window, cx);
10788        assert_eq!(
10789            editor.text(cx),
10790            "
10791                a{{_}}
10792                b{{_}}
10793                c{{_}}
10794            "
10795            .unindent()
10796        );
10797        assert_eq!(
10798            editor
10799                .selections
10800                .ranges::<Point>(&editor.display_snapshot(cx)),
10801            [
10802                Point::new(0, 4)..Point::new(0, 4),
10803                Point::new(1, 4)..Point::new(1, 4),
10804                Point::new(2, 4)..Point::new(2, 4)
10805            ]
10806        );
10807
10808        editor.backspace(&Default::default(), window, cx);
10809        editor.backspace(&Default::default(), window, cx);
10810        assert_eq!(
10811            editor.text(cx),
10812            "
10813                a{}
10814                b{}
10815                c{}
10816            "
10817            .unindent()
10818        );
10819        assert_eq!(
10820            editor
10821                .selections
10822                .ranges::<Point>(&editor.display_snapshot(cx)),
10823            [
10824                Point::new(0, 2)..Point::new(0, 2),
10825                Point::new(1, 2)..Point::new(1, 2),
10826                Point::new(2, 2)..Point::new(2, 2)
10827            ]
10828        );
10829
10830        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10831        assert_eq!(
10832            editor.text(cx),
10833            "
10834                a
10835                b
10836                c
10837            "
10838            .unindent()
10839        );
10840        assert_eq!(
10841            editor
10842                .selections
10843                .ranges::<Point>(&editor.display_snapshot(cx)),
10844            [
10845                Point::new(0, 1)..Point::new(0, 1),
10846                Point::new(1, 1)..Point::new(1, 1),
10847                Point::new(2, 1)..Point::new(2, 1)
10848            ]
10849        );
10850    });
10851}
10852
10853#[gpui::test]
10854async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10855    init_test(cx, |settings| {
10856        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10857    });
10858
10859    let mut cx = EditorTestContext::new(cx).await;
10860
10861    let language = Arc::new(Language::new(
10862        LanguageConfig {
10863            brackets: BracketPairConfig {
10864                pairs: vec![
10865                    BracketPair {
10866                        start: "{".to_string(),
10867                        end: "}".to_string(),
10868                        close: true,
10869                        surround: true,
10870                        newline: true,
10871                    },
10872                    BracketPair {
10873                        start: "(".to_string(),
10874                        end: ")".to_string(),
10875                        close: true,
10876                        surround: true,
10877                        newline: true,
10878                    },
10879                    BracketPair {
10880                        start: "[".to_string(),
10881                        end: "]".to_string(),
10882                        close: false,
10883                        surround: true,
10884                        newline: true,
10885                    },
10886                ],
10887                ..Default::default()
10888            },
10889            autoclose_before: "})]".to_string(),
10890            ..Default::default()
10891        },
10892        Some(tree_sitter_rust::LANGUAGE.into()),
10893    ));
10894
10895    cx.language_registry().add(language.clone());
10896    cx.update_buffer(|buffer, cx| {
10897        buffer.set_language(Some(language), cx);
10898    });
10899
10900    cx.set_state(
10901        &"
10902            {(ˇ)}
10903            [[ˇ]]
10904            {(ˇ)}
10905        "
10906        .unindent(),
10907    );
10908
10909    cx.update_editor(|editor, window, cx| {
10910        editor.backspace(&Default::default(), window, cx);
10911        editor.backspace(&Default::default(), window, cx);
10912    });
10913
10914    cx.assert_editor_state(
10915        &"
10916            ˇ
10917            ˇ]]
10918            ˇ
10919        "
10920        .unindent(),
10921    );
10922
10923    cx.update_editor(|editor, window, cx| {
10924        editor.handle_input("{", window, cx);
10925        editor.handle_input("{", window, cx);
10926        editor.move_right(&MoveRight, window, cx);
10927        editor.move_right(&MoveRight, window, cx);
10928        editor.move_left(&MoveLeft, window, cx);
10929        editor.move_left(&MoveLeft, window, cx);
10930        editor.backspace(&Default::default(), window, cx);
10931    });
10932
10933    cx.assert_editor_state(
10934        &"
10935            {ˇ}
10936            {ˇ}]]
10937            {ˇ}
10938        "
10939        .unindent(),
10940    );
10941
10942    cx.update_editor(|editor, window, cx| {
10943        editor.backspace(&Default::default(), window, cx);
10944    });
10945
10946    cx.assert_editor_state(
10947        &"
10948            ˇ
10949            ˇ]]
10950            ˇ
10951        "
10952        .unindent(),
10953    );
10954}
10955
10956#[gpui::test]
10957async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10958    init_test(cx, |_| {});
10959
10960    let language = Arc::new(Language::new(
10961        LanguageConfig::default(),
10962        Some(tree_sitter_rust::LANGUAGE.into()),
10963    ));
10964
10965    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10968    editor
10969        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10970        .await;
10971
10972    editor.update_in(cx, |editor, window, cx| {
10973        editor.set_auto_replace_emoji_shortcode(true);
10974
10975        editor.handle_input("Hello ", window, cx);
10976        editor.handle_input(":wave", window, cx);
10977        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10978
10979        editor.handle_input(":", window, cx);
10980        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10981
10982        editor.handle_input(" :smile", window, cx);
10983        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10984
10985        editor.handle_input(":", window, cx);
10986        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10987
10988        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10989        editor.handle_input(":wave", window, cx);
10990        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10991
10992        editor.handle_input(":", window, cx);
10993        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10994
10995        editor.handle_input(":1", window, cx);
10996        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10997
10998        editor.handle_input(":", window, cx);
10999        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11000
11001        // Ensure shortcode does not get replaced when it is part of a word
11002        editor.handle_input(" Test:wave", window, cx);
11003        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11004
11005        editor.handle_input(":", window, cx);
11006        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11007
11008        editor.set_auto_replace_emoji_shortcode(false);
11009
11010        // Ensure shortcode does not get replaced when auto replace is off
11011        editor.handle_input(" :wave", window, cx);
11012        assert_eq!(
11013            editor.text(cx),
11014            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11015        );
11016
11017        editor.handle_input(":", window, cx);
11018        assert_eq!(
11019            editor.text(cx),
11020            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11021        );
11022    });
11023}
11024
11025#[gpui::test]
11026async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11027    init_test(cx, |_| {});
11028
11029    let (text, insertion_ranges) = marked_text_ranges(
11030        indoc! {"
11031            ˇ
11032        "},
11033        false,
11034    );
11035
11036    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11037    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11038
11039    _ = editor.update_in(cx, |editor, window, cx| {
11040        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11041
11042        editor
11043            .insert_snippet(&insertion_ranges, snippet, window, cx)
11044            .unwrap();
11045
11046        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11047            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11048            assert_eq!(editor.text(cx), expected_text);
11049            assert_eq!(
11050                editor
11051                    .selections
11052                    .ranges::<usize>(&editor.display_snapshot(cx)),
11053                selection_ranges
11054            );
11055        }
11056
11057        assert(
11058            editor,
11059            cx,
11060            indoc! {"
11061            type «» =•
11062            "},
11063        );
11064
11065        assert!(editor.context_menu_visible(), "There should be a matches");
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_snippets(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let mut cx = EditorTestContext::new(cx).await;
11074
11075    cx.set_state(indoc! {"
11076        a.ˇ b
11077        a.ˇ b
11078        a.ˇ b
11079    "});
11080
11081    cx.update_editor(|editor, window, cx| {
11082        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11083        let insertion_ranges = editor
11084            .selections
11085            .all(&editor.display_snapshot(cx))
11086            .iter()
11087            .map(|s| s.range())
11088            .collect::<Vec<_>>();
11089        editor
11090            .insert_snippet(&insertion_ranges, snippet, window, cx)
11091            .unwrap();
11092    });
11093
11094    cx.assert_editor_state(indoc! {"
11095        a.f(«oneˇ», two, «threeˇ») b
11096        a.f(«oneˇ», two, «threeˇ») b
11097        a.f(«oneˇ», two, «threeˇ») b
11098    "});
11099
11100    // Can't move earlier than the first tab stop
11101    cx.update_editor(|editor, window, cx| {
11102        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11103    });
11104    cx.assert_editor_state(indoc! {"
11105        a.f(«oneˇ», two, «threeˇ») b
11106        a.f(«oneˇ», two, «threeˇ») b
11107        a.f(«oneˇ», two, «threeˇ») b
11108    "});
11109
11110    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11111    cx.assert_editor_state(indoc! {"
11112        a.f(one, «twoˇ», three) b
11113        a.f(one, «twoˇ», three) b
11114        a.f(one, «twoˇ», three) b
11115    "});
11116
11117    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11118    cx.assert_editor_state(indoc! {"
11119        a.f(«oneˇ», two, «threeˇ») b
11120        a.f(«oneˇ», two, «threeˇ») b
11121        a.f(«oneˇ», two, «threeˇ») b
11122    "});
11123
11124    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11125    cx.assert_editor_state(indoc! {"
11126        a.f(one, «twoˇ», three) b
11127        a.f(one, «twoˇ», three) b
11128        a.f(one, «twoˇ», three) b
11129    "});
11130    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11131    cx.assert_editor_state(indoc! {"
11132        a.f(one, two, three)ˇ b
11133        a.f(one, two, three)ˇ b
11134        a.f(one, two, three)ˇ b
11135    "});
11136
11137    // As soon as the last tab stop is reached, snippet state is gone
11138    cx.update_editor(|editor, window, cx| {
11139        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11140    });
11141    cx.assert_editor_state(indoc! {"
11142        a.f(one, two, three)ˇ b
11143        a.f(one, two, three)ˇ b
11144        a.f(one, two, three)ˇ b
11145    "});
11146}
11147
11148#[gpui::test]
11149async fn test_snippet_indentation(cx: &mut TestAppContext) {
11150    init_test(cx, |_| {});
11151
11152    let mut cx = EditorTestContext::new(cx).await;
11153
11154    cx.update_editor(|editor, window, cx| {
11155        let snippet = Snippet::parse(indoc! {"
11156            /*
11157             * Multiline comment with leading indentation
11158             *
11159             * $1
11160             */
11161            $0"})
11162        .unwrap();
11163        let insertion_ranges = editor
11164            .selections
11165            .all(&editor.display_snapshot(cx))
11166            .iter()
11167            .map(|s| s.range())
11168            .collect::<Vec<_>>();
11169        editor
11170            .insert_snippet(&insertion_ranges, snippet, window, cx)
11171            .unwrap();
11172    });
11173
11174    cx.assert_editor_state(indoc! {"
11175        /*
11176         * Multiline comment with leading indentation
11177         *
11178         * ˇ
11179         */
11180    "});
11181
11182    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11183    cx.assert_editor_state(indoc! {"
11184        /*
11185         * Multiline comment with leading indentation
11186         *
11187         *•
11188         */
11189        ˇ"});
11190}
11191
11192#[gpui::test]
11193async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11194    init_test(cx, |_| {});
11195
11196    let mut cx = EditorTestContext::new(cx).await;
11197    cx.update_editor(|editor, _, cx| {
11198        editor.project().unwrap().update(cx, |project, cx| {
11199            project.snippets().update(cx, |snippets, cx| {
11200                let snippet = project::snippet_provider::Snippet {
11201                    prefix: vec!["multi word".to_string()],
11202                    body: "this is many words".to_string(),
11203                    description: Some("description".to_string()),
11204                    name: "multi-word snippet test".to_string(),
11205                };
11206                snippets.add_snippet_for_test(
11207                    None,
11208                    PathBuf::from("test_snippets.json"),
11209                    vec![Arc::new(snippet)],
11210                    cx,
11211                );
11212            });
11213        })
11214    });
11215
11216    cx.set_state("ˇ");
11217    // cx.simulate_input("m");
11218    // cx.simulate_input("m ");
11219    // cx.simulate_input("m w");
11220    // cx.simulate_input("aa m w");
11221    cx.simulate_input("aa m g"); // fails correctly
11222
11223    cx.update_editor(|editor, _, _| {
11224        let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11225        else {
11226            panic!("expected completion menu");
11227        };
11228        assert!(context_menu.visible());
11229        let completions = context_menu.completions.borrow();
11230
11231        assert!(
11232            completions
11233                .iter()
11234                .any(|c| c.new_text == "this is many words"),
11235            "Expected to find 'multi word' snippet in completions"
11236        );
11237    });
11238}
11239
11240#[gpui::test]
11241async fn test_document_format_during_save(cx: &mut TestAppContext) {
11242    init_test(cx, |_| {});
11243
11244    let fs = FakeFs::new(cx.executor());
11245    fs.insert_file(path!("/file.rs"), Default::default()).await;
11246
11247    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11248
11249    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11250    language_registry.add(rust_lang());
11251    let mut fake_servers = language_registry.register_fake_lsp(
11252        "Rust",
11253        FakeLspAdapter {
11254            capabilities: lsp::ServerCapabilities {
11255                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11256                ..Default::default()
11257            },
11258            ..Default::default()
11259        },
11260    );
11261
11262    let buffer = project
11263        .update(cx, |project, cx| {
11264            project.open_local_buffer(path!("/file.rs"), cx)
11265        })
11266        .await
11267        .unwrap();
11268
11269    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11270    let (editor, cx) = cx.add_window_view(|window, cx| {
11271        build_editor_with_project(project.clone(), buffer, window, cx)
11272    });
11273    editor.update_in(cx, |editor, window, cx| {
11274        editor.set_text("one\ntwo\nthree\n", window, cx)
11275    });
11276    assert!(cx.read(|cx| editor.is_dirty(cx)));
11277
11278    cx.executor().start_waiting();
11279    let fake_server = fake_servers.next().await.unwrap();
11280
11281    {
11282        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11283            move |params, _| async move {
11284                assert_eq!(
11285                    params.text_document.uri,
11286                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11287                );
11288                assert_eq!(params.options.tab_size, 4);
11289                Ok(Some(vec![lsp::TextEdit::new(
11290                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11291                    ", ".to_string(),
11292                )]))
11293            },
11294        );
11295        let save = editor
11296            .update_in(cx, |editor, window, cx| {
11297                editor.save(
11298                    SaveOptions {
11299                        format: true,
11300                        autosave: false,
11301                    },
11302                    project.clone(),
11303                    window,
11304                    cx,
11305                )
11306            })
11307            .unwrap();
11308        cx.executor().start_waiting();
11309        save.await;
11310
11311        assert_eq!(
11312            editor.update(cx, |editor, cx| editor.text(cx)),
11313            "one, two\nthree\n"
11314        );
11315        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11316    }
11317
11318    {
11319        editor.update_in(cx, |editor, window, cx| {
11320            editor.set_text("one\ntwo\nthree\n", window, cx)
11321        });
11322        assert!(cx.read(|cx| editor.is_dirty(cx)));
11323
11324        // Ensure we can still save even if formatting hangs.
11325        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11326            move |params, _| async move {
11327                assert_eq!(
11328                    params.text_document.uri,
11329                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11330                );
11331                futures::future::pending::<()>().await;
11332                unreachable!()
11333            },
11334        );
11335        let save = editor
11336            .update_in(cx, |editor, window, cx| {
11337                editor.save(
11338                    SaveOptions {
11339                        format: true,
11340                        autosave: false,
11341                    },
11342                    project.clone(),
11343                    window,
11344                    cx,
11345                )
11346            })
11347            .unwrap();
11348        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11349        cx.executor().start_waiting();
11350        save.await;
11351        assert_eq!(
11352            editor.update(cx, |editor, cx| editor.text(cx)),
11353            "one\ntwo\nthree\n"
11354        );
11355    }
11356
11357    // Set rust language override and assert overridden tabsize is sent to language server
11358    update_test_language_settings(cx, |settings| {
11359        settings.languages.0.insert(
11360            "Rust".into(),
11361            LanguageSettingsContent {
11362                tab_size: NonZeroU32::new(8),
11363                ..Default::default()
11364            },
11365        );
11366    });
11367
11368    {
11369        editor.update_in(cx, |editor, window, cx| {
11370            editor.set_text("somehting_new\n", window, cx)
11371        });
11372        assert!(cx.read(|cx| editor.is_dirty(cx)));
11373        let _formatting_request_signal = fake_server
11374            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11375                assert_eq!(
11376                    params.text_document.uri,
11377                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11378                );
11379                assert_eq!(params.options.tab_size, 8);
11380                Ok(Some(vec![]))
11381            });
11382        let save = editor
11383            .update_in(cx, |editor, window, cx| {
11384                editor.save(
11385                    SaveOptions {
11386                        format: true,
11387                        autosave: false,
11388                    },
11389                    project.clone(),
11390                    window,
11391                    cx,
11392                )
11393            })
11394            .unwrap();
11395        cx.executor().start_waiting();
11396        save.await;
11397    }
11398}
11399
11400#[gpui::test]
11401async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11402    init_test(cx, |settings| {
11403        settings.defaults.ensure_final_newline_on_save = Some(false);
11404    });
11405
11406    let fs = FakeFs::new(cx.executor());
11407    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11408
11409    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11410
11411    let buffer = project
11412        .update(cx, |project, cx| {
11413            project.open_local_buffer(path!("/file.txt"), cx)
11414        })
11415        .await
11416        .unwrap();
11417
11418    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11419    let (editor, cx) = cx.add_window_view(|window, cx| {
11420        build_editor_with_project(project.clone(), buffer, window, cx)
11421    });
11422    editor.update_in(cx, |editor, window, cx| {
11423        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11424            s.select_ranges([0..0])
11425        });
11426    });
11427    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11428
11429    editor.update_in(cx, |editor, window, cx| {
11430        editor.handle_input("\n", window, cx)
11431    });
11432    cx.run_until_parked();
11433    save(&editor, &project, cx).await;
11434    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11435
11436    editor.update_in(cx, |editor, window, cx| {
11437        editor.undo(&Default::default(), window, cx);
11438    });
11439    save(&editor, &project, cx).await;
11440    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11441
11442    editor.update_in(cx, |editor, window, cx| {
11443        editor.redo(&Default::default(), window, cx);
11444    });
11445    cx.run_until_parked();
11446    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11447
11448    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11449        let save = editor
11450            .update_in(cx, |editor, window, cx| {
11451                editor.save(
11452                    SaveOptions {
11453                        format: true,
11454                        autosave: false,
11455                    },
11456                    project.clone(),
11457                    window,
11458                    cx,
11459                )
11460            })
11461            .unwrap();
11462        cx.executor().start_waiting();
11463        save.await;
11464        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11465    }
11466}
11467
11468#[gpui::test]
11469async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11470    init_test(cx, |_| {});
11471
11472    let cols = 4;
11473    let rows = 10;
11474    let sample_text_1 = sample_text(rows, cols, 'a');
11475    assert_eq!(
11476        sample_text_1,
11477        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11478    );
11479    let sample_text_2 = sample_text(rows, cols, 'l');
11480    assert_eq!(
11481        sample_text_2,
11482        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11483    );
11484    let sample_text_3 = sample_text(rows, cols, 'v');
11485    assert_eq!(
11486        sample_text_3,
11487        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11488    );
11489
11490    let fs = FakeFs::new(cx.executor());
11491    fs.insert_tree(
11492        path!("/a"),
11493        json!({
11494            "main.rs": sample_text_1,
11495            "other.rs": sample_text_2,
11496            "lib.rs": sample_text_3,
11497        }),
11498    )
11499    .await;
11500
11501    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11502    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11503    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11504
11505    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11506    language_registry.add(rust_lang());
11507    let mut fake_servers = language_registry.register_fake_lsp(
11508        "Rust",
11509        FakeLspAdapter {
11510            capabilities: lsp::ServerCapabilities {
11511                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11512                ..Default::default()
11513            },
11514            ..Default::default()
11515        },
11516    );
11517
11518    let worktree = project.update(cx, |project, cx| {
11519        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11520        assert_eq!(worktrees.len(), 1);
11521        worktrees.pop().unwrap()
11522    });
11523    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11524
11525    let buffer_1 = project
11526        .update(cx, |project, cx| {
11527            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11528        })
11529        .await
11530        .unwrap();
11531    let buffer_2 = project
11532        .update(cx, |project, cx| {
11533            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11534        })
11535        .await
11536        .unwrap();
11537    let buffer_3 = project
11538        .update(cx, |project, cx| {
11539            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11540        })
11541        .await
11542        .unwrap();
11543
11544    let multi_buffer = cx.new(|cx| {
11545        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11546        multi_buffer.push_excerpts(
11547            buffer_1.clone(),
11548            [
11549                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11550                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11551                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11552            ],
11553            cx,
11554        );
11555        multi_buffer.push_excerpts(
11556            buffer_2.clone(),
11557            [
11558                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11559                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11560                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11561            ],
11562            cx,
11563        );
11564        multi_buffer.push_excerpts(
11565            buffer_3.clone(),
11566            [
11567                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11568                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11569                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11570            ],
11571            cx,
11572        );
11573        multi_buffer
11574    });
11575    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11576        Editor::new(
11577            EditorMode::full(),
11578            multi_buffer,
11579            Some(project.clone()),
11580            window,
11581            cx,
11582        )
11583    });
11584
11585    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11586        editor.change_selections(
11587            SelectionEffects::scroll(Autoscroll::Next),
11588            window,
11589            cx,
11590            |s| s.select_ranges(Some(1..2)),
11591        );
11592        editor.insert("|one|two|three|", window, cx);
11593    });
11594    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11595    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11596        editor.change_selections(
11597            SelectionEffects::scroll(Autoscroll::Next),
11598            window,
11599            cx,
11600            |s| s.select_ranges(Some(60..70)),
11601        );
11602        editor.insert("|four|five|six|", window, cx);
11603    });
11604    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11605
11606    // First two buffers should be edited, but not the third one.
11607    assert_eq!(
11608        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11609        "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}",
11610    );
11611    buffer_1.update(cx, |buffer, _| {
11612        assert!(buffer.is_dirty());
11613        assert_eq!(
11614            buffer.text(),
11615            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11616        )
11617    });
11618    buffer_2.update(cx, |buffer, _| {
11619        assert!(buffer.is_dirty());
11620        assert_eq!(
11621            buffer.text(),
11622            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11623        )
11624    });
11625    buffer_3.update(cx, |buffer, _| {
11626        assert!(!buffer.is_dirty());
11627        assert_eq!(buffer.text(), sample_text_3,)
11628    });
11629    cx.executor().run_until_parked();
11630
11631    cx.executor().start_waiting();
11632    let save = multi_buffer_editor
11633        .update_in(cx, |editor, window, cx| {
11634            editor.save(
11635                SaveOptions {
11636                    format: true,
11637                    autosave: false,
11638                },
11639                project.clone(),
11640                window,
11641                cx,
11642            )
11643        })
11644        .unwrap();
11645
11646    let fake_server = fake_servers.next().await.unwrap();
11647    fake_server
11648        .server
11649        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11650            Ok(Some(vec![lsp::TextEdit::new(
11651                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11652                format!("[{} formatted]", params.text_document.uri),
11653            )]))
11654        })
11655        .detach();
11656    save.await;
11657
11658    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11659    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11660    assert_eq!(
11661        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11662        uri!(
11663            "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}"
11664        ),
11665    );
11666    buffer_1.update(cx, |buffer, _| {
11667        assert!(!buffer.is_dirty());
11668        assert_eq!(
11669            buffer.text(),
11670            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11671        )
11672    });
11673    buffer_2.update(cx, |buffer, _| {
11674        assert!(!buffer.is_dirty());
11675        assert_eq!(
11676            buffer.text(),
11677            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11678        )
11679    });
11680    buffer_3.update(cx, |buffer, _| {
11681        assert!(!buffer.is_dirty());
11682        assert_eq!(buffer.text(), sample_text_3,)
11683    });
11684}
11685
11686#[gpui::test]
11687async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11688    init_test(cx, |_| {});
11689
11690    let fs = FakeFs::new(cx.executor());
11691    fs.insert_tree(
11692        path!("/dir"),
11693        json!({
11694            "file1.rs": "fn main() { println!(\"hello\"); }",
11695            "file2.rs": "fn test() { println!(\"test\"); }",
11696            "file3.rs": "fn other() { println!(\"other\"); }\n",
11697        }),
11698    )
11699    .await;
11700
11701    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11703    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11704
11705    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11706    language_registry.add(rust_lang());
11707
11708    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11709    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11710
11711    // Open three buffers
11712    let buffer_1 = project
11713        .update(cx, |project, cx| {
11714            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11715        })
11716        .await
11717        .unwrap();
11718    let buffer_2 = project
11719        .update(cx, |project, cx| {
11720            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11721        })
11722        .await
11723        .unwrap();
11724    let buffer_3 = project
11725        .update(cx, |project, cx| {
11726            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11727        })
11728        .await
11729        .unwrap();
11730
11731    // Create a multi-buffer with all three buffers
11732    let multi_buffer = cx.new(|cx| {
11733        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11734        multi_buffer.push_excerpts(
11735            buffer_1.clone(),
11736            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11737            cx,
11738        );
11739        multi_buffer.push_excerpts(
11740            buffer_2.clone(),
11741            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11742            cx,
11743        );
11744        multi_buffer.push_excerpts(
11745            buffer_3.clone(),
11746            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11747            cx,
11748        );
11749        multi_buffer
11750    });
11751
11752    let editor = cx.new_window_entity(|window, cx| {
11753        Editor::new(
11754            EditorMode::full(),
11755            multi_buffer,
11756            Some(project.clone()),
11757            window,
11758            cx,
11759        )
11760    });
11761
11762    // Edit only the first buffer
11763    editor.update_in(cx, |editor, window, cx| {
11764        editor.change_selections(
11765            SelectionEffects::scroll(Autoscroll::Next),
11766            window,
11767            cx,
11768            |s| s.select_ranges(Some(10..10)),
11769        );
11770        editor.insert("// edited", window, cx);
11771    });
11772
11773    // Verify that only buffer 1 is dirty
11774    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11775    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11776    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11777
11778    // Get write counts after file creation (files were created with initial content)
11779    // We expect each file to have been written once during creation
11780    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11781    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11782    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11783
11784    // Perform autosave
11785    let save_task = editor.update_in(cx, |editor, window, cx| {
11786        editor.save(
11787            SaveOptions {
11788                format: true,
11789                autosave: true,
11790            },
11791            project.clone(),
11792            window,
11793            cx,
11794        )
11795    });
11796    save_task.await.unwrap();
11797
11798    // Only the dirty buffer should have been saved
11799    assert_eq!(
11800        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11801        1,
11802        "Buffer 1 was dirty, so it should have been written once during autosave"
11803    );
11804    assert_eq!(
11805        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11806        0,
11807        "Buffer 2 was clean, so it should not have been written during autosave"
11808    );
11809    assert_eq!(
11810        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11811        0,
11812        "Buffer 3 was clean, so it should not have been written during autosave"
11813    );
11814
11815    // Verify buffer states after autosave
11816    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11817    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11818    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11819
11820    // Now perform a manual save (format = true)
11821    let save_task = editor.update_in(cx, |editor, window, cx| {
11822        editor.save(
11823            SaveOptions {
11824                format: true,
11825                autosave: false,
11826            },
11827            project.clone(),
11828            window,
11829            cx,
11830        )
11831    });
11832    save_task.await.unwrap();
11833
11834    // During manual save, clean buffers don't get written to disk
11835    // They just get did_save called for language server notifications
11836    assert_eq!(
11837        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11838        1,
11839        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11840    );
11841    assert_eq!(
11842        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11843        0,
11844        "Buffer 2 should not have been written at all"
11845    );
11846    assert_eq!(
11847        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11848        0,
11849        "Buffer 3 should not have been written at all"
11850    );
11851}
11852
11853async fn setup_range_format_test(
11854    cx: &mut TestAppContext,
11855) -> (
11856    Entity<Project>,
11857    Entity<Editor>,
11858    &mut gpui::VisualTestContext,
11859    lsp::FakeLanguageServer,
11860) {
11861    init_test(cx, |_| {});
11862
11863    let fs = FakeFs::new(cx.executor());
11864    fs.insert_file(path!("/file.rs"), Default::default()).await;
11865
11866    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11867
11868    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11869    language_registry.add(rust_lang());
11870    let mut fake_servers = language_registry.register_fake_lsp(
11871        "Rust",
11872        FakeLspAdapter {
11873            capabilities: lsp::ServerCapabilities {
11874                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11875                ..lsp::ServerCapabilities::default()
11876            },
11877            ..FakeLspAdapter::default()
11878        },
11879    );
11880
11881    let buffer = project
11882        .update(cx, |project, cx| {
11883            project.open_local_buffer(path!("/file.rs"), cx)
11884        })
11885        .await
11886        .unwrap();
11887
11888    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11889    let (editor, cx) = cx.add_window_view(|window, cx| {
11890        build_editor_with_project(project.clone(), buffer, window, cx)
11891    });
11892
11893    cx.executor().start_waiting();
11894    let fake_server = fake_servers.next().await.unwrap();
11895
11896    (project, editor, cx, fake_server)
11897}
11898
11899#[gpui::test]
11900async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11901    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11902
11903    editor.update_in(cx, |editor, window, cx| {
11904        editor.set_text("one\ntwo\nthree\n", window, cx)
11905    });
11906    assert!(cx.read(|cx| editor.is_dirty(cx)));
11907
11908    let save = editor
11909        .update_in(cx, |editor, window, cx| {
11910            editor.save(
11911                SaveOptions {
11912                    format: true,
11913                    autosave: false,
11914                },
11915                project.clone(),
11916                window,
11917                cx,
11918            )
11919        })
11920        .unwrap();
11921    fake_server
11922        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11923            assert_eq!(
11924                params.text_document.uri,
11925                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11926            );
11927            assert_eq!(params.options.tab_size, 4);
11928            Ok(Some(vec![lsp::TextEdit::new(
11929                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11930                ", ".to_string(),
11931            )]))
11932        })
11933        .next()
11934        .await;
11935    cx.executor().start_waiting();
11936    save.await;
11937    assert_eq!(
11938        editor.update(cx, |editor, cx| editor.text(cx)),
11939        "one, two\nthree\n"
11940    );
11941    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11942}
11943
11944#[gpui::test]
11945async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11946    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11947
11948    editor.update_in(cx, |editor, window, cx| {
11949        editor.set_text("one\ntwo\nthree\n", window, cx)
11950    });
11951    assert!(cx.read(|cx| editor.is_dirty(cx)));
11952
11953    // Test that save still works when formatting hangs
11954    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11955        move |params, _| async move {
11956            assert_eq!(
11957                params.text_document.uri,
11958                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11959            );
11960            futures::future::pending::<()>().await;
11961            unreachable!()
11962        },
11963    );
11964    let save = editor
11965        .update_in(cx, |editor, window, cx| {
11966            editor.save(
11967                SaveOptions {
11968                    format: true,
11969                    autosave: false,
11970                },
11971                project.clone(),
11972                window,
11973                cx,
11974            )
11975        })
11976        .unwrap();
11977    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11978    cx.executor().start_waiting();
11979    save.await;
11980    assert_eq!(
11981        editor.update(cx, |editor, cx| editor.text(cx)),
11982        "one\ntwo\nthree\n"
11983    );
11984    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11985}
11986
11987#[gpui::test]
11988async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11989    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11990
11991    // Buffer starts clean, no formatting should be requested
11992    let save = editor
11993        .update_in(cx, |editor, window, cx| {
11994            editor.save(
11995                SaveOptions {
11996                    format: false,
11997                    autosave: false,
11998                },
11999                project.clone(),
12000                window,
12001                cx,
12002            )
12003        })
12004        .unwrap();
12005    let _pending_format_request = fake_server
12006        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12007            panic!("Should not be invoked");
12008        })
12009        .next();
12010    cx.executor().start_waiting();
12011    save.await;
12012    cx.run_until_parked();
12013}
12014
12015#[gpui::test]
12016async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12017    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12018
12019    // Set Rust language override and assert overridden tabsize is sent to language server
12020    update_test_language_settings(cx, |settings| {
12021        settings.languages.0.insert(
12022            "Rust".into(),
12023            LanguageSettingsContent {
12024                tab_size: NonZeroU32::new(8),
12025                ..Default::default()
12026            },
12027        );
12028    });
12029
12030    editor.update_in(cx, |editor, window, cx| {
12031        editor.set_text("something_new\n", window, cx)
12032    });
12033    assert!(cx.read(|cx| editor.is_dirty(cx)));
12034    let save = editor
12035        .update_in(cx, |editor, window, cx| {
12036            editor.save(
12037                SaveOptions {
12038                    format: true,
12039                    autosave: false,
12040                },
12041                project.clone(),
12042                window,
12043                cx,
12044            )
12045        })
12046        .unwrap();
12047    fake_server
12048        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12049            assert_eq!(
12050                params.text_document.uri,
12051                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12052            );
12053            assert_eq!(params.options.tab_size, 8);
12054            Ok(Some(Vec::new()))
12055        })
12056        .next()
12057        .await;
12058    save.await;
12059}
12060
12061#[gpui::test]
12062async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12063    init_test(cx, |settings| {
12064        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12065            settings::LanguageServerFormatterSpecifier::Current,
12066        )))
12067    });
12068
12069    let fs = FakeFs::new(cx.executor());
12070    fs.insert_file(path!("/file.rs"), Default::default()).await;
12071
12072    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12073
12074    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12075    language_registry.add(Arc::new(Language::new(
12076        LanguageConfig {
12077            name: "Rust".into(),
12078            matcher: LanguageMatcher {
12079                path_suffixes: vec!["rs".to_string()],
12080                ..Default::default()
12081            },
12082            ..LanguageConfig::default()
12083        },
12084        Some(tree_sitter_rust::LANGUAGE.into()),
12085    )));
12086    update_test_language_settings(cx, |settings| {
12087        // Enable Prettier formatting for the same buffer, and ensure
12088        // LSP is called instead of Prettier.
12089        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12090    });
12091    let mut fake_servers = language_registry.register_fake_lsp(
12092        "Rust",
12093        FakeLspAdapter {
12094            capabilities: lsp::ServerCapabilities {
12095                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12096                ..Default::default()
12097            },
12098            ..Default::default()
12099        },
12100    );
12101
12102    let buffer = project
12103        .update(cx, |project, cx| {
12104            project.open_local_buffer(path!("/file.rs"), cx)
12105        })
12106        .await
12107        .unwrap();
12108
12109    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12110    let (editor, cx) = cx.add_window_view(|window, cx| {
12111        build_editor_with_project(project.clone(), buffer, window, cx)
12112    });
12113    editor.update_in(cx, |editor, window, cx| {
12114        editor.set_text("one\ntwo\nthree\n", window, cx)
12115    });
12116
12117    cx.executor().start_waiting();
12118    let fake_server = fake_servers.next().await.unwrap();
12119
12120    let format = editor
12121        .update_in(cx, |editor, window, cx| {
12122            editor.perform_format(
12123                project.clone(),
12124                FormatTrigger::Manual,
12125                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12126                window,
12127                cx,
12128            )
12129        })
12130        .unwrap();
12131    fake_server
12132        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12133            assert_eq!(
12134                params.text_document.uri,
12135                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12136            );
12137            assert_eq!(params.options.tab_size, 4);
12138            Ok(Some(vec![lsp::TextEdit::new(
12139                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12140                ", ".to_string(),
12141            )]))
12142        })
12143        .next()
12144        .await;
12145    cx.executor().start_waiting();
12146    format.await;
12147    assert_eq!(
12148        editor.update(cx, |editor, cx| editor.text(cx)),
12149        "one, two\nthree\n"
12150    );
12151
12152    editor.update_in(cx, |editor, window, cx| {
12153        editor.set_text("one\ntwo\nthree\n", window, cx)
12154    });
12155    // Ensure we don't lock if formatting hangs.
12156    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12157        move |params, _| async move {
12158            assert_eq!(
12159                params.text_document.uri,
12160                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12161            );
12162            futures::future::pending::<()>().await;
12163            unreachable!()
12164        },
12165    );
12166    let format = editor
12167        .update_in(cx, |editor, window, cx| {
12168            editor.perform_format(
12169                project,
12170                FormatTrigger::Manual,
12171                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12172                window,
12173                cx,
12174            )
12175        })
12176        .unwrap();
12177    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12178    cx.executor().start_waiting();
12179    format.await;
12180    assert_eq!(
12181        editor.update(cx, |editor, cx| editor.text(cx)),
12182        "one\ntwo\nthree\n"
12183    );
12184}
12185
12186#[gpui::test]
12187async fn test_multiple_formatters(cx: &mut TestAppContext) {
12188    init_test(cx, |settings| {
12189        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12190        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12191            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12192            Formatter::CodeAction("code-action-1".into()),
12193            Formatter::CodeAction("code-action-2".into()),
12194        ]))
12195    });
12196
12197    let fs = FakeFs::new(cx.executor());
12198    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12199        .await;
12200
12201    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12202    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12203    language_registry.add(rust_lang());
12204
12205    let mut fake_servers = language_registry.register_fake_lsp(
12206        "Rust",
12207        FakeLspAdapter {
12208            capabilities: lsp::ServerCapabilities {
12209                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12210                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12211                    commands: vec!["the-command-for-code-action-1".into()],
12212                    ..Default::default()
12213                }),
12214                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12215                ..Default::default()
12216            },
12217            ..Default::default()
12218        },
12219    );
12220
12221    let buffer = project
12222        .update(cx, |project, cx| {
12223            project.open_local_buffer(path!("/file.rs"), cx)
12224        })
12225        .await
12226        .unwrap();
12227
12228    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12229    let (editor, cx) = cx.add_window_view(|window, cx| {
12230        build_editor_with_project(project.clone(), buffer, window, cx)
12231    });
12232
12233    cx.executor().start_waiting();
12234
12235    let fake_server = fake_servers.next().await.unwrap();
12236    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12237        move |_params, _| async move {
12238            Ok(Some(vec![lsp::TextEdit::new(
12239                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12240                "applied-formatting\n".to_string(),
12241            )]))
12242        },
12243    );
12244    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12245        move |params, _| async move {
12246            let requested_code_actions = params.context.only.expect("Expected code action request");
12247            assert_eq!(requested_code_actions.len(), 1);
12248
12249            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12250            let code_action = match requested_code_actions[0].as_str() {
12251                "code-action-1" => lsp::CodeAction {
12252                    kind: Some("code-action-1".into()),
12253                    edit: Some(lsp::WorkspaceEdit::new(
12254                        [(
12255                            uri,
12256                            vec![lsp::TextEdit::new(
12257                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12258                                "applied-code-action-1-edit\n".to_string(),
12259                            )],
12260                        )]
12261                        .into_iter()
12262                        .collect(),
12263                    )),
12264                    command: Some(lsp::Command {
12265                        command: "the-command-for-code-action-1".into(),
12266                        ..Default::default()
12267                    }),
12268                    ..Default::default()
12269                },
12270                "code-action-2" => lsp::CodeAction {
12271                    kind: Some("code-action-2".into()),
12272                    edit: Some(lsp::WorkspaceEdit::new(
12273                        [(
12274                            uri,
12275                            vec![lsp::TextEdit::new(
12276                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12277                                "applied-code-action-2-edit\n".to_string(),
12278                            )],
12279                        )]
12280                        .into_iter()
12281                        .collect(),
12282                    )),
12283                    ..Default::default()
12284                },
12285                req => panic!("Unexpected code action request: {:?}", req),
12286            };
12287            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12288                code_action,
12289            )]))
12290        },
12291    );
12292
12293    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12294        move |params, _| async move { Ok(params) }
12295    });
12296
12297    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12298    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12299        let fake = fake_server.clone();
12300        let lock = command_lock.clone();
12301        move |params, _| {
12302            assert_eq!(params.command, "the-command-for-code-action-1");
12303            let fake = fake.clone();
12304            let lock = lock.clone();
12305            async move {
12306                lock.lock().await;
12307                fake.server
12308                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12309                        label: None,
12310                        edit: lsp::WorkspaceEdit {
12311                            changes: Some(
12312                                [(
12313                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12314                                    vec![lsp::TextEdit {
12315                                        range: lsp::Range::new(
12316                                            lsp::Position::new(0, 0),
12317                                            lsp::Position::new(0, 0),
12318                                        ),
12319                                        new_text: "applied-code-action-1-command\n".into(),
12320                                    }],
12321                                )]
12322                                .into_iter()
12323                                .collect(),
12324                            ),
12325                            ..Default::default()
12326                        },
12327                    })
12328                    .await
12329                    .into_response()
12330                    .unwrap();
12331                Ok(Some(json!(null)))
12332            }
12333        }
12334    });
12335
12336    cx.executor().start_waiting();
12337    editor
12338        .update_in(cx, |editor, window, cx| {
12339            editor.perform_format(
12340                project.clone(),
12341                FormatTrigger::Manual,
12342                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12343                window,
12344                cx,
12345            )
12346        })
12347        .unwrap()
12348        .await;
12349    editor.update(cx, |editor, cx| {
12350        assert_eq!(
12351            editor.text(cx),
12352            r#"
12353                applied-code-action-2-edit
12354                applied-code-action-1-command
12355                applied-code-action-1-edit
12356                applied-formatting
12357                one
12358                two
12359                three
12360            "#
12361            .unindent()
12362        );
12363    });
12364
12365    editor.update_in(cx, |editor, window, cx| {
12366        editor.undo(&Default::default(), window, cx);
12367        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12368    });
12369
12370    // Perform a manual edit while waiting for an LSP command
12371    // that's being run as part of a formatting code action.
12372    let lock_guard = command_lock.lock().await;
12373    let format = editor
12374        .update_in(cx, |editor, window, cx| {
12375            editor.perform_format(
12376                project.clone(),
12377                FormatTrigger::Manual,
12378                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12379                window,
12380                cx,
12381            )
12382        })
12383        .unwrap();
12384    cx.run_until_parked();
12385    editor.update(cx, |editor, cx| {
12386        assert_eq!(
12387            editor.text(cx),
12388            r#"
12389                applied-code-action-1-edit
12390                applied-formatting
12391                one
12392                two
12393                three
12394            "#
12395            .unindent()
12396        );
12397
12398        editor.buffer.update(cx, |buffer, cx| {
12399            let ix = buffer.len(cx);
12400            buffer.edit([(ix..ix, "edited\n")], None, cx);
12401        });
12402    });
12403
12404    // Allow the LSP command to proceed. Because the buffer was edited,
12405    // the second code action will not be run.
12406    drop(lock_guard);
12407    format.await;
12408    editor.update_in(cx, |editor, window, cx| {
12409        assert_eq!(
12410            editor.text(cx),
12411            r#"
12412                applied-code-action-1-command
12413                applied-code-action-1-edit
12414                applied-formatting
12415                one
12416                two
12417                three
12418                edited
12419            "#
12420            .unindent()
12421        );
12422
12423        // The manual edit is undone first, because it is the last thing the user did
12424        // (even though the command completed afterwards).
12425        editor.undo(&Default::default(), window, cx);
12426        assert_eq!(
12427            editor.text(cx),
12428            r#"
12429                applied-code-action-1-command
12430                applied-code-action-1-edit
12431                applied-formatting
12432                one
12433                two
12434                three
12435            "#
12436            .unindent()
12437        );
12438
12439        // All the formatting (including the command, which completed after the manual edit)
12440        // is undone together.
12441        editor.undo(&Default::default(), window, cx);
12442        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12443    });
12444}
12445
12446#[gpui::test]
12447async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12448    init_test(cx, |settings| {
12449        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12450            settings::LanguageServerFormatterSpecifier::Current,
12451        )]))
12452    });
12453
12454    let fs = FakeFs::new(cx.executor());
12455    fs.insert_file(path!("/file.ts"), Default::default()).await;
12456
12457    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12458
12459    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12460    language_registry.add(Arc::new(Language::new(
12461        LanguageConfig {
12462            name: "TypeScript".into(),
12463            matcher: LanguageMatcher {
12464                path_suffixes: vec!["ts".to_string()],
12465                ..Default::default()
12466            },
12467            ..LanguageConfig::default()
12468        },
12469        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12470    )));
12471    update_test_language_settings(cx, |settings| {
12472        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12473    });
12474    let mut fake_servers = language_registry.register_fake_lsp(
12475        "TypeScript",
12476        FakeLspAdapter {
12477            capabilities: lsp::ServerCapabilities {
12478                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12479                ..Default::default()
12480            },
12481            ..Default::default()
12482        },
12483    );
12484
12485    let buffer = project
12486        .update(cx, |project, cx| {
12487            project.open_local_buffer(path!("/file.ts"), cx)
12488        })
12489        .await
12490        .unwrap();
12491
12492    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12493    let (editor, cx) = cx.add_window_view(|window, cx| {
12494        build_editor_with_project(project.clone(), buffer, window, cx)
12495    });
12496    editor.update_in(cx, |editor, window, cx| {
12497        editor.set_text(
12498            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12499            window,
12500            cx,
12501        )
12502    });
12503
12504    cx.executor().start_waiting();
12505    let fake_server = fake_servers.next().await.unwrap();
12506
12507    let format = editor
12508        .update_in(cx, |editor, window, cx| {
12509            editor.perform_code_action_kind(
12510                project.clone(),
12511                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12512                window,
12513                cx,
12514            )
12515        })
12516        .unwrap();
12517    fake_server
12518        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12519            assert_eq!(
12520                params.text_document.uri,
12521                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12522            );
12523            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12524                lsp::CodeAction {
12525                    title: "Organize Imports".to_string(),
12526                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12527                    edit: Some(lsp::WorkspaceEdit {
12528                        changes: Some(
12529                            [(
12530                                params.text_document.uri.clone(),
12531                                vec![lsp::TextEdit::new(
12532                                    lsp::Range::new(
12533                                        lsp::Position::new(1, 0),
12534                                        lsp::Position::new(2, 0),
12535                                    ),
12536                                    "".to_string(),
12537                                )],
12538                            )]
12539                            .into_iter()
12540                            .collect(),
12541                        ),
12542                        ..Default::default()
12543                    }),
12544                    ..Default::default()
12545                },
12546            )]))
12547        })
12548        .next()
12549        .await;
12550    cx.executor().start_waiting();
12551    format.await;
12552    assert_eq!(
12553        editor.update(cx, |editor, cx| editor.text(cx)),
12554        "import { a } from 'module';\n\nconst x = a;\n"
12555    );
12556
12557    editor.update_in(cx, |editor, window, cx| {
12558        editor.set_text(
12559            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12560            window,
12561            cx,
12562        )
12563    });
12564    // Ensure we don't lock if code action hangs.
12565    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12566        move |params, _| async move {
12567            assert_eq!(
12568                params.text_document.uri,
12569                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12570            );
12571            futures::future::pending::<()>().await;
12572            unreachable!()
12573        },
12574    );
12575    let format = editor
12576        .update_in(cx, |editor, window, cx| {
12577            editor.perform_code_action_kind(
12578                project,
12579                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12580                window,
12581                cx,
12582            )
12583        })
12584        .unwrap();
12585    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12586    cx.executor().start_waiting();
12587    format.await;
12588    assert_eq!(
12589        editor.update(cx, |editor, cx| editor.text(cx)),
12590        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12591    );
12592}
12593
12594#[gpui::test]
12595async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12596    init_test(cx, |_| {});
12597
12598    let mut cx = EditorLspTestContext::new_rust(
12599        lsp::ServerCapabilities {
12600            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12601            ..Default::default()
12602        },
12603        cx,
12604    )
12605    .await;
12606
12607    cx.set_state(indoc! {"
12608        one.twoˇ
12609    "});
12610
12611    // The format request takes a long time. When it completes, it inserts
12612    // a newline and an indent before the `.`
12613    cx.lsp
12614        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12615            let executor = cx.background_executor().clone();
12616            async move {
12617                executor.timer(Duration::from_millis(100)).await;
12618                Ok(Some(vec![lsp::TextEdit {
12619                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12620                    new_text: "\n    ".into(),
12621                }]))
12622            }
12623        });
12624
12625    // Submit a format request.
12626    let format_1 = cx
12627        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12628        .unwrap();
12629    cx.executor().run_until_parked();
12630
12631    // Submit a second format request.
12632    let format_2 = cx
12633        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12634        .unwrap();
12635    cx.executor().run_until_parked();
12636
12637    // Wait for both format requests to complete
12638    cx.executor().advance_clock(Duration::from_millis(200));
12639    cx.executor().start_waiting();
12640    format_1.await.unwrap();
12641    cx.executor().start_waiting();
12642    format_2.await.unwrap();
12643
12644    // The formatting edits only happens once.
12645    cx.assert_editor_state(indoc! {"
12646        one
12647            .twoˇ
12648    "});
12649}
12650
12651#[gpui::test]
12652async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12653    init_test(cx, |settings| {
12654        settings.defaults.formatter = Some(FormatterList::default())
12655    });
12656
12657    let mut cx = EditorLspTestContext::new_rust(
12658        lsp::ServerCapabilities {
12659            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12660            ..Default::default()
12661        },
12662        cx,
12663    )
12664    .await;
12665
12666    // Record which buffer changes have been sent to the language server
12667    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12668    cx.lsp
12669        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12670            let buffer_changes = buffer_changes.clone();
12671            move |params, _| {
12672                buffer_changes.lock().extend(
12673                    params
12674                        .content_changes
12675                        .into_iter()
12676                        .map(|e| (e.range.unwrap(), e.text)),
12677                );
12678            }
12679        });
12680    // Handle formatting requests to the language server.
12681    cx.lsp
12682        .set_request_handler::<lsp::request::Formatting, _, _>({
12683            let buffer_changes = buffer_changes.clone();
12684            move |_, _| {
12685                let buffer_changes = buffer_changes.clone();
12686                // Insert blank lines between each line of the buffer.
12687                async move {
12688                    // When formatting is requested, trailing whitespace has already been stripped,
12689                    // and the trailing newline has already been added.
12690                    assert_eq!(
12691                        &buffer_changes.lock()[1..],
12692                        &[
12693                            (
12694                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12695                                "".into()
12696                            ),
12697                            (
12698                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12699                                "".into()
12700                            ),
12701                            (
12702                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12703                                "\n".into()
12704                            ),
12705                        ]
12706                    );
12707
12708                    Ok(Some(vec![
12709                        lsp::TextEdit {
12710                            range: lsp::Range::new(
12711                                lsp::Position::new(1, 0),
12712                                lsp::Position::new(1, 0),
12713                            ),
12714                            new_text: "\n".into(),
12715                        },
12716                        lsp::TextEdit {
12717                            range: lsp::Range::new(
12718                                lsp::Position::new(2, 0),
12719                                lsp::Position::new(2, 0),
12720                            ),
12721                            new_text: "\n".into(),
12722                        },
12723                    ]))
12724                }
12725            }
12726        });
12727
12728    // Set up a buffer white some trailing whitespace and no trailing newline.
12729    cx.set_state(
12730        &[
12731            "one ",   //
12732            "twoˇ",   //
12733            "three ", //
12734            "four",   //
12735        ]
12736        .join("\n"),
12737    );
12738    cx.run_until_parked();
12739
12740    // Submit a format request.
12741    let format = cx
12742        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12743        .unwrap();
12744
12745    cx.run_until_parked();
12746    // After formatting the buffer, the trailing whitespace is stripped,
12747    // a newline is appended, and the edits provided by the language server
12748    // have been applied.
12749    format.await.unwrap();
12750
12751    cx.assert_editor_state(
12752        &[
12753            "one",   //
12754            "",      //
12755            "twoˇ",  //
12756            "",      //
12757            "three", //
12758            "four",  //
12759            "",      //
12760        ]
12761        .join("\n"),
12762    );
12763
12764    // Undoing the formatting undoes the trailing whitespace removal, the
12765    // trailing newline, and the LSP edits.
12766    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12767    cx.assert_editor_state(
12768        &[
12769            "one ",   //
12770            "twoˇ",   //
12771            "three ", //
12772            "four",   //
12773        ]
12774        .join("\n"),
12775    );
12776}
12777
12778#[gpui::test]
12779async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12780    cx: &mut TestAppContext,
12781) {
12782    init_test(cx, |_| {});
12783
12784    cx.update(|cx| {
12785        cx.update_global::<SettingsStore, _>(|settings, cx| {
12786            settings.update_user_settings(cx, |settings| {
12787                settings.editor.auto_signature_help = Some(true);
12788            });
12789        });
12790    });
12791
12792    let mut cx = EditorLspTestContext::new_rust(
12793        lsp::ServerCapabilities {
12794            signature_help_provider: Some(lsp::SignatureHelpOptions {
12795                ..Default::default()
12796            }),
12797            ..Default::default()
12798        },
12799        cx,
12800    )
12801    .await;
12802
12803    let language = Language::new(
12804        LanguageConfig {
12805            name: "Rust".into(),
12806            brackets: BracketPairConfig {
12807                pairs: vec![
12808                    BracketPair {
12809                        start: "{".to_string(),
12810                        end: "}".to_string(),
12811                        close: true,
12812                        surround: true,
12813                        newline: true,
12814                    },
12815                    BracketPair {
12816                        start: "(".to_string(),
12817                        end: ")".to_string(),
12818                        close: true,
12819                        surround: true,
12820                        newline: true,
12821                    },
12822                    BracketPair {
12823                        start: "/*".to_string(),
12824                        end: " */".to_string(),
12825                        close: true,
12826                        surround: true,
12827                        newline: true,
12828                    },
12829                    BracketPair {
12830                        start: "[".to_string(),
12831                        end: "]".to_string(),
12832                        close: false,
12833                        surround: false,
12834                        newline: true,
12835                    },
12836                    BracketPair {
12837                        start: "\"".to_string(),
12838                        end: "\"".to_string(),
12839                        close: true,
12840                        surround: true,
12841                        newline: false,
12842                    },
12843                    BracketPair {
12844                        start: "<".to_string(),
12845                        end: ">".to_string(),
12846                        close: false,
12847                        surround: true,
12848                        newline: true,
12849                    },
12850                ],
12851                ..Default::default()
12852            },
12853            autoclose_before: "})]".to_string(),
12854            ..Default::default()
12855        },
12856        Some(tree_sitter_rust::LANGUAGE.into()),
12857    );
12858    let language = Arc::new(language);
12859
12860    cx.language_registry().add(language.clone());
12861    cx.update_buffer(|buffer, cx| {
12862        buffer.set_language(Some(language), cx);
12863    });
12864
12865    cx.set_state(
12866        &r#"
12867            fn main() {
12868                sampleˇ
12869            }
12870        "#
12871        .unindent(),
12872    );
12873
12874    cx.update_editor(|editor, window, cx| {
12875        editor.handle_input("(", window, cx);
12876    });
12877    cx.assert_editor_state(
12878        &"
12879            fn main() {
12880                sample(ˇ)
12881            }
12882        "
12883        .unindent(),
12884    );
12885
12886    let mocked_response = lsp::SignatureHelp {
12887        signatures: vec![lsp::SignatureInformation {
12888            label: "fn sample(param1: u8, param2: u8)".to_string(),
12889            documentation: None,
12890            parameters: Some(vec![
12891                lsp::ParameterInformation {
12892                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12893                    documentation: None,
12894                },
12895                lsp::ParameterInformation {
12896                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12897                    documentation: None,
12898                },
12899            ]),
12900            active_parameter: None,
12901        }],
12902        active_signature: Some(0),
12903        active_parameter: Some(0),
12904    };
12905    handle_signature_help_request(&mut cx, mocked_response).await;
12906
12907    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12908        .await;
12909
12910    cx.editor(|editor, _, _| {
12911        let signature_help_state = editor.signature_help_state.popover().cloned();
12912        let signature = signature_help_state.unwrap();
12913        assert_eq!(
12914            signature.signatures[signature.current_signature].label,
12915            "fn sample(param1: u8, param2: u8)"
12916        );
12917    });
12918}
12919
12920#[gpui::test]
12921async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12922    init_test(cx, |_| {});
12923
12924    cx.update(|cx| {
12925        cx.update_global::<SettingsStore, _>(|settings, cx| {
12926            settings.update_user_settings(cx, |settings| {
12927                settings.editor.auto_signature_help = Some(false);
12928                settings.editor.show_signature_help_after_edits = Some(false);
12929            });
12930        });
12931    });
12932
12933    let mut cx = EditorLspTestContext::new_rust(
12934        lsp::ServerCapabilities {
12935            signature_help_provider: Some(lsp::SignatureHelpOptions {
12936                ..Default::default()
12937            }),
12938            ..Default::default()
12939        },
12940        cx,
12941    )
12942    .await;
12943
12944    let language = Language::new(
12945        LanguageConfig {
12946            name: "Rust".into(),
12947            brackets: BracketPairConfig {
12948                pairs: vec![
12949                    BracketPair {
12950                        start: "{".to_string(),
12951                        end: "}".to_string(),
12952                        close: true,
12953                        surround: true,
12954                        newline: true,
12955                    },
12956                    BracketPair {
12957                        start: "(".to_string(),
12958                        end: ")".to_string(),
12959                        close: true,
12960                        surround: true,
12961                        newline: true,
12962                    },
12963                    BracketPair {
12964                        start: "/*".to_string(),
12965                        end: " */".to_string(),
12966                        close: true,
12967                        surround: true,
12968                        newline: true,
12969                    },
12970                    BracketPair {
12971                        start: "[".to_string(),
12972                        end: "]".to_string(),
12973                        close: false,
12974                        surround: false,
12975                        newline: true,
12976                    },
12977                    BracketPair {
12978                        start: "\"".to_string(),
12979                        end: "\"".to_string(),
12980                        close: true,
12981                        surround: true,
12982                        newline: false,
12983                    },
12984                    BracketPair {
12985                        start: "<".to_string(),
12986                        end: ">".to_string(),
12987                        close: false,
12988                        surround: true,
12989                        newline: true,
12990                    },
12991                ],
12992                ..Default::default()
12993            },
12994            autoclose_before: "})]".to_string(),
12995            ..Default::default()
12996        },
12997        Some(tree_sitter_rust::LANGUAGE.into()),
12998    );
12999    let language = Arc::new(language);
13000
13001    cx.language_registry().add(language.clone());
13002    cx.update_buffer(|buffer, cx| {
13003        buffer.set_language(Some(language), cx);
13004    });
13005
13006    // Ensure that signature_help is not called when no signature help is enabled.
13007    cx.set_state(
13008        &r#"
13009            fn main() {
13010                sampleˇ
13011            }
13012        "#
13013        .unindent(),
13014    );
13015    cx.update_editor(|editor, window, cx| {
13016        editor.handle_input("(", window, cx);
13017    });
13018    cx.assert_editor_state(
13019        &"
13020            fn main() {
13021                sample(ˇ)
13022            }
13023        "
13024        .unindent(),
13025    );
13026    cx.editor(|editor, _, _| {
13027        assert!(editor.signature_help_state.task().is_none());
13028    });
13029
13030    let mocked_response = lsp::SignatureHelp {
13031        signatures: vec![lsp::SignatureInformation {
13032            label: "fn sample(param1: u8, param2: u8)".to_string(),
13033            documentation: None,
13034            parameters: Some(vec![
13035                lsp::ParameterInformation {
13036                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13037                    documentation: None,
13038                },
13039                lsp::ParameterInformation {
13040                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13041                    documentation: None,
13042                },
13043            ]),
13044            active_parameter: None,
13045        }],
13046        active_signature: Some(0),
13047        active_parameter: Some(0),
13048    };
13049
13050    // Ensure that signature_help is called when enabled afte edits
13051    cx.update(|_, cx| {
13052        cx.update_global::<SettingsStore, _>(|settings, cx| {
13053            settings.update_user_settings(cx, |settings| {
13054                settings.editor.auto_signature_help = Some(false);
13055                settings.editor.show_signature_help_after_edits = Some(true);
13056            });
13057        });
13058    });
13059    cx.set_state(
13060        &r#"
13061            fn main() {
13062                sampleˇ
13063            }
13064        "#
13065        .unindent(),
13066    );
13067    cx.update_editor(|editor, window, cx| {
13068        editor.handle_input("(", window, cx);
13069    });
13070    cx.assert_editor_state(
13071        &"
13072            fn main() {
13073                sample(ˇ)
13074            }
13075        "
13076        .unindent(),
13077    );
13078    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13079    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13080        .await;
13081    cx.update_editor(|editor, _, _| {
13082        let signature_help_state = editor.signature_help_state.popover().cloned();
13083        assert!(signature_help_state.is_some());
13084        let signature = signature_help_state.unwrap();
13085        assert_eq!(
13086            signature.signatures[signature.current_signature].label,
13087            "fn sample(param1: u8, param2: u8)"
13088        );
13089        editor.signature_help_state = SignatureHelpState::default();
13090    });
13091
13092    // Ensure that signature_help is called when auto signature help override is enabled
13093    cx.update(|_, cx| {
13094        cx.update_global::<SettingsStore, _>(|settings, cx| {
13095            settings.update_user_settings(cx, |settings| {
13096                settings.editor.auto_signature_help = Some(true);
13097                settings.editor.show_signature_help_after_edits = Some(false);
13098            });
13099        });
13100    });
13101    cx.set_state(
13102        &r#"
13103            fn main() {
13104                sampleˇ
13105            }
13106        "#
13107        .unindent(),
13108    );
13109    cx.update_editor(|editor, window, cx| {
13110        editor.handle_input("(", window, cx);
13111    });
13112    cx.assert_editor_state(
13113        &"
13114            fn main() {
13115                sample(ˇ)
13116            }
13117        "
13118        .unindent(),
13119    );
13120    handle_signature_help_request(&mut cx, mocked_response).await;
13121    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13122        .await;
13123    cx.editor(|editor, _, _| {
13124        let signature_help_state = editor.signature_help_state.popover().cloned();
13125        assert!(signature_help_state.is_some());
13126        let signature = signature_help_state.unwrap();
13127        assert_eq!(
13128            signature.signatures[signature.current_signature].label,
13129            "fn sample(param1: u8, param2: u8)"
13130        );
13131    });
13132}
13133
13134#[gpui::test]
13135async fn test_signature_help(cx: &mut TestAppContext) {
13136    init_test(cx, |_| {});
13137    cx.update(|cx| {
13138        cx.update_global::<SettingsStore, _>(|settings, cx| {
13139            settings.update_user_settings(cx, |settings| {
13140                settings.editor.auto_signature_help = Some(true);
13141            });
13142        });
13143    });
13144
13145    let mut cx = EditorLspTestContext::new_rust(
13146        lsp::ServerCapabilities {
13147            signature_help_provider: Some(lsp::SignatureHelpOptions {
13148                ..Default::default()
13149            }),
13150            ..Default::default()
13151        },
13152        cx,
13153    )
13154    .await;
13155
13156    // A test that directly calls `show_signature_help`
13157    cx.update_editor(|editor, window, cx| {
13158        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13159    });
13160
13161    let mocked_response = lsp::SignatureHelp {
13162        signatures: vec![lsp::SignatureInformation {
13163            label: "fn sample(param1: u8, param2: u8)".to_string(),
13164            documentation: None,
13165            parameters: Some(vec![
13166                lsp::ParameterInformation {
13167                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13168                    documentation: None,
13169                },
13170                lsp::ParameterInformation {
13171                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13172                    documentation: None,
13173                },
13174            ]),
13175            active_parameter: None,
13176        }],
13177        active_signature: Some(0),
13178        active_parameter: Some(0),
13179    };
13180    handle_signature_help_request(&mut cx, mocked_response).await;
13181
13182    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13183        .await;
13184
13185    cx.editor(|editor, _, _| {
13186        let signature_help_state = editor.signature_help_state.popover().cloned();
13187        assert!(signature_help_state.is_some());
13188        let signature = signature_help_state.unwrap();
13189        assert_eq!(
13190            signature.signatures[signature.current_signature].label,
13191            "fn sample(param1: u8, param2: u8)"
13192        );
13193    });
13194
13195    // When exiting outside from inside the brackets, `signature_help` is closed.
13196    cx.set_state(indoc! {"
13197        fn main() {
13198            sample(ˇ);
13199        }
13200
13201        fn sample(param1: u8, param2: u8) {}
13202    "});
13203
13204    cx.update_editor(|editor, window, cx| {
13205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13206            s.select_ranges([0..0])
13207        });
13208    });
13209
13210    let mocked_response = lsp::SignatureHelp {
13211        signatures: Vec::new(),
13212        active_signature: None,
13213        active_parameter: None,
13214    };
13215    handle_signature_help_request(&mut cx, mocked_response).await;
13216
13217    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13218        .await;
13219
13220    cx.editor(|editor, _, _| {
13221        assert!(!editor.signature_help_state.is_shown());
13222    });
13223
13224    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13225    cx.set_state(indoc! {"
13226        fn main() {
13227            sample(ˇ);
13228        }
13229
13230        fn sample(param1: u8, param2: u8) {}
13231    "});
13232
13233    let mocked_response = lsp::SignatureHelp {
13234        signatures: vec![lsp::SignatureInformation {
13235            label: "fn sample(param1: u8, param2: u8)".to_string(),
13236            documentation: None,
13237            parameters: Some(vec![
13238                lsp::ParameterInformation {
13239                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13240                    documentation: None,
13241                },
13242                lsp::ParameterInformation {
13243                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13244                    documentation: None,
13245                },
13246            ]),
13247            active_parameter: None,
13248        }],
13249        active_signature: Some(0),
13250        active_parameter: Some(0),
13251    };
13252    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13253    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13254        .await;
13255    cx.editor(|editor, _, _| {
13256        assert!(editor.signature_help_state.is_shown());
13257    });
13258
13259    // Restore the popover with more parameter input
13260    cx.set_state(indoc! {"
13261        fn main() {
13262            sample(param1, param2ˇ);
13263        }
13264
13265        fn sample(param1: u8, param2: u8) {}
13266    "});
13267
13268    let mocked_response = lsp::SignatureHelp {
13269        signatures: vec![lsp::SignatureInformation {
13270            label: "fn sample(param1: u8, param2: u8)".to_string(),
13271            documentation: None,
13272            parameters: Some(vec![
13273                lsp::ParameterInformation {
13274                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13275                    documentation: None,
13276                },
13277                lsp::ParameterInformation {
13278                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13279                    documentation: None,
13280                },
13281            ]),
13282            active_parameter: None,
13283        }],
13284        active_signature: Some(0),
13285        active_parameter: Some(1),
13286    };
13287    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13288    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13289        .await;
13290
13291    // When selecting a range, the popover is gone.
13292    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13293    cx.update_editor(|editor, window, cx| {
13294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13295            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13296        })
13297    });
13298    cx.assert_editor_state(indoc! {"
13299        fn main() {
13300            sample(param1, «ˇparam2»);
13301        }
13302
13303        fn sample(param1: u8, param2: u8) {}
13304    "});
13305    cx.editor(|editor, _, _| {
13306        assert!(!editor.signature_help_state.is_shown());
13307    });
13308
13309    // When unselecting again, the popover is back if within the brackets.
13310    cx.update_editor(|editor, window, cx| {
13311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13312            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13313        })
13314    });
13315    cx.assert_editor_state(indoc! {"
13316        fn main() {
13317            sample(param1, ˇparam2);
13318        }
13319
13320        fn sample(param1: u8, param2: u8) {}
13321    "});
13322    handle_signature_help_request(&mut cx, mocked_response).await;
13323    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13324        .await;
13325    cx.editor(|editor, _, _| {
13326        assert!(editor.signature_help_state.is_shown());
13327    });
13328
13329    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13330    cx.update_editor(|editor, window, cx| {
13331        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13332            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13333            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13334        })
13335    });
13336    cx.assert_editor_state(indoc! {"
13337        fn main() {
13338            sample(param1, ˇparam2);
13339        }
13340
13341        fn sample(param1: u8, param2: u8) {}
13342    "});
13343
13344    let mocked_response = lsp::SignatureHelp {
13345        signatures: vec![lsp::SignatureInformation {
13346            label: "fn sample(param1: u8, param2: u8)".to_string(),
13347            documentation: None,
13348            parameters: Some(vec![
13349                lsp::ParameterInformation {
13350                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13351                    documentation: None,
13352                },
13353                lsp::ParameterInformation {
13354                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13355                    documentation: None,
13356                },
13357            ]),
13358            active_parameter: None,
13359        }],
13360        active_signature: Some(0),
13361        active_parameter: Some(1),
13362    };
13363    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13364    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13365        .await;
13366    cx.update_editor(|editor, _, cx| {
13367        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13368    });
13369    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13370        .await;
13371    cx.update_editor(|editor, window, cx| {
13372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13373            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13374        })
13375    });
13376    cx.assert_editor_state(indoc! {"
13377        fn main() {
13378            sample(param1, «ˇparam2»);
13379        }
13380
13381        fn sample(param1: u8, param2: u8) {}
13382    "});
13383    cx.update_editor(|editor, window, cx| {
13384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13385            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13386        })
13387    });
13388    cx.assert_editor_state(indoc! {"
13389        fn main() {
13390            sample(param1, ˇparam2);
13391        }
13392
13393        fn sample(param1: u8, param2: u8) {}
13394    "});
13395    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13396        .await;
13397}
13398
13399#[gpui::test]
13400async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13401    init_test(cx, |_| {});
13402
13403    let mut cx = EditorLspTestContext::new_rust(
13404        lsp::ServerCapabilities {
13405            signature_help_provider: Some(lsp::SignatureHelpOptions {
13406                ..Default::default()
13407            }),
13408            ..Default::default()
13409        },
13410        cx,
13411    )
13412    .await;
13413
13414    cx.set_state(indoc! {"
13415        fn main() {
13416            overloadedˇ
13417        }
13418    "});
13419
13420    cx.update_editor(|editor, window, cx| {
13421        editor.handle_input("(", window, cx);
13422        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13423    });
13424
13425    // Mock response with 3 signatures
13426    let mocked_response = lsp::SignatureHelp {
13427        signatures: vec![
13428            lsp::SignatureInformation {
13429                label: "fn overloaded(x: i32)".to_string(),
13430                documentation: None,
13431                parameters: Some(vec![lsp::ParameterInformation {
13432                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13433                    documentation: None,
13434                }]),
13435                active_parameter: None,
13436            },
13437            lsp::SignatureInformation {
13438                label: "fn overloaded(x: i32, y: i32)".to_string(),
13439                documentation: None,
13440                parameters: Some(vec![
13441                    lsp::ParameterInformation {
13442                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13443                        documentation: None,
13444                    },
13445                    lsp::ParameterInformation {
13446                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13447                        documentation: None,
13448                    },
13449                ]),
13450                active_parameter: None,
13451            },
13452            lsp::SignatureInformation {
13453                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13454                documentation: None,
13455                parameters: Some(vec![
13456                    lsp::ParameterInformation {
13457                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13458                        documentation: None,
13459                    },
13460                    lsp::ParameterInformation {
13461                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13462                        documentation: None,
13463                    },
13464                    lsp::ParameterInformation {
13465                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13466                        documentation: None,
13467                    },
13468                ]),
13469                active_parameter: None,
13470            },
13471        ],
13472        active_signature: Some(1),
13473        active_parameter: Some(0),
13474    };
13475    handle_signature_help_request(&mut cx, mocked_response).await;
13476
13477    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13478        .await;
13479
13480    // Verify we have multiple signatures and the right one is selected
13481    cx.editor(|editor, _, _| {
13482        let popover = editor.signature_help_state.popover().cloned().unwrap();
13483        assert_eq!(popover.signatures.len(), 3);
13484        // active_signature was 1, so that should be the current
13485        assert_eq!(popover.current_signature, 1);
13486        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13487        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13488        assert_eq!(
13489            popover.signatures[2].label,
13490            "fn overloaded(x: i32, y: i32, z: i32)"
13491        );
13492    });
13493
13494    // Test navigation functionality
13495    cx.update_editor(|editor, window, cx| {
13496        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13497    });
13498
13499    cx.editor(|editor, _, _| {
13500        let popover = editor.signature_help_state.popover().cloned().unwrap();
13501        assert_eq!(popover.current_signature, 2);
13502    });
13503
13504    // Test wrap around
13505    cx.update_editor(|editor, window, cx| {
13506        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13507    });
13508
13509    cx.editor(|editor, _, _| {
13510        let popover = editor.signature_help_state.popover().cloned().unwrap();
13511        assert_eq!(popover.current_signature, 0);
13512    });
13513
13514    // Test previous navigation
13515    cx.update_editor(|editor, window, cx| {
13516        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13517    });
13518
13519    cx.editor(|editor, _, _| {
13520        let popover = editor.signature_help_state.popover().cloned().unwrap();
13521        assert_eq!(popover.current_signature, 2);
13522    });
13523}
13524
13525#[gpui::test]
13526async fn test_completion_mode(cx: &mut TestAppContext) {
13527    init_test(cx, |_| {});
13528    let mut cx = EditorLspTestContext::new_rust(
13529        lsp::ServerCapabilities {
13530            completion_provider: Some(lsp::CompletionOptions {
13531                resolve_provider: Some(true),
13532                ..Default::default()
13533            }),
13534            ..Default::default()
13535        },
13536        cx,
13537    )
13538    .await;
13539
13540    struct Run {
13541        run_description: &'static str,
13542        initial_state: String,
13543        buffer_marked_text: String,
13544        completion_label: &'static str,
13545        completion_text: &'static str,
13546        expected_with_insert_mode: String,
13547        expected_with_replace_mode: String,
13548        expected_with_replace_subsequence_mode: String,
13549        expected_with_replace_suffix_mode: String,
13550    }
13551
13552    let runs = [
13553        Run {
13554            run_description: "Start of word matches completion text",
13555            initial_state: "before ediˇ after".into(),
13556            buffer_marked_text: "before <edi|> after".into(),
13557            completion_label: "editor",
13558            completion_text: "editor",
13559            expected_with_insert_mode: "before editorˇ after".into(),
13560            expected_with_replace_mode: "before editorˇ after".into(),
13561            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13562            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13563        },
13564        Run {
13565            run_description: "Accept same text at the middle of the word",
13566            initial_state: "before ediˇtor after".into(),
13567            buffer_marked_text: "before <edi|tor> after".into(),
13568            completion_label: "editor",
13569            completion_text: "editor",
13570            expected_with_insert_mode: "before editorˇtor after".into(),
13571            expected_with_replace_mode: "before editorˇ after".into(),
13572            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13573            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13574        },
13575        Run {
13576            run_description: "End of word matches completion text -- cursor at end",
13577            initial_state: "before torˇ after".into(),
13578            buffer_marked_text: "before <tor|> after".into(),
13579            completion_label: "editor",
13580            completion_text: "editor",
13581            expected_with_insert_mode: "before editorˇ after".into(),
13582            expected_with_replace_mode: "before editorˇ after".into(),
13583            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13584            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13585        },
13586        Run {
13587            run_description: "End of word matches completion text -- cursor at start",
13588            initial_state: "before ˇtor after".into(),
13589            buffer_marked_text: "before <|tor> after".into(),
13590            completion_label: "editor",
13591            completion_text: "editor",
13592            expected_with_insert_mode: "before editorˇtor after".into(),
13593            expected_with_replace_mode: "before editorˇ after".into(),
13594            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13595            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13596        },
13597        Run {
13598            run_description: "Prepend text containing whitespace",
13599            initial_state: "pˇfield: bool".into(),
13600            buffer_marked_text: "<p|field>: bool".into(),
13601            completion_label: "pub ",
13602            completion_text: "pub ",
13603            expected_with_insert_mode: "pub ˇfield: bool".into(),
13604            expected_with_replace_mode: "pub ˇ: bool".into(),
13605            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13606            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13607        },
13608        Run {
13609            run_description: "Add element to start of list",
13610            initial_state: "[element_ˇelement_2]".into(),
13611            buffer_marked_text: "[<element_|element_2>]".into(),
13612            completion_label: "element_1",
13613            completion_text: "element_1",
13614            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13615            expected_with_replace_mode: "[element_1ˇ]".into(),
13616            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13617            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13618        },
13619        Run {
13620            run_description: "Add element to start of list -- first and second elements are equal",
13621            initial_state: "[elˇelement]".into(),
13622            buffer_marked_text: "[<el|element>]".into(),
13623            completion_label: "element",
13624            completion_text: "element",
13625            expected_with_insert_mode: "[elementˇelement]".into(),
13626            expected_with_replace_mode: "[elementˇ]".into(),
13627            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13628            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13629        },
13630        Run {
13631            run_description: "Ends with matching suffix",
13632            initial_state: "SubˇError".into(),
13633            buffer_marked_text: "<Sub|Error>".into(),
13634            completion_label: "SubscriptionError",
13635            completion_text: "SubscriptionError",
13636            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13637            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13638            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13639            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13640        },
13641        Run {
13642            run_description: "Suffix is a subsequence -- contiguous",
13643            initial_state: "SubˇErr".into(),
13644            buffer_marked_text: "<Sub|Err>".into(),
13645            completion_label: "SubscriptionError",
13646            completion_text: "SubscriptionError",
13647            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13648            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13649            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13650            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13651        },
13652        Run {
13653            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13654            initial_state: "Suˇscrirr".into(),
13655            buffer_marked_text: "<Su|scrirr>".into(),
13656            completion_label: "SubscriptionError",
13657            completion_text: "SubscriptionError",
13658            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13659            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13660            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13661            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13662        },
13663        Run {
13664            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13665            initial_state: "foo(indˇix)".into(),
13666            buffer_marked_text: "foo(<ind|ix>)".into(),
13667            completion_label: "node_index",
13668            completion_text: "node_index",
13669            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13670            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13671            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13672            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13673        },
13674        Run {
13675            run_description: "Replace range ends before cursor - should extend to cursor",
13676            initial_state: "before editˇo after".into(),
13677            buffer_marked_text: "before <{ed}>it|o after".into(),
13678            completion_label: "editor",
13679            completion_text: "editor",
13680            expected_with_insert_mode: "before editorˇo after".into(),
13681            expected_with_replace_mode: "before editorˇo after".into(),
13682            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13683            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13684        },
13685        Run {
13686            run_description: "Uses label for suffix matching",
13687            initial_state: "before ediˇtor after".into(),
13688            buffer_marked_text: "before <edi|tor> after".into(),
13689            completion_label: "editor",
13690            completion_text: "editor()",
13691            expected_with_insert_mode: "before editor()ˇtor after".into(),
13692            expected_with_replace_mode: "before editor()ˇ after".into(),
13693            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13694            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13695        },
13696        Run {
13697            run_description: "Case insensitive subsequence and suffix matching",
13698            initial_state: "before EDiˇtoR after".into(),
13699            buffer_marked_text: "before <EDi|toR> after".into(),
13700            completion_label: "editor",
13701            completion_text: "editor",
13702            expected_with_insert_mode: "before editorˇtoR after".into(),
13703            expected_with_replace_mode: "before editorˇ after".into(),
13704            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13705            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13706        },
13707    ];
13708
13709    for run in runs {
13710        let run_variations = [
13711            (LspInsertMode::Insert, run.expected_with_insert_mode),
13712            (LspInsertMode::Replace, run.expected_with_replace_mode),
13713            (
13714                LspInsertMode::ReplaceSubsequence,
13715                run.expected_with_replace_subsequence_mode,
13716            ),
13717            (
13718                LspInsertMode::ReplaceSuffix,
13719                run.expected_with_replace_suffix_mode,
13720            ),
13721        ];
13722
13723        for (lsp_insert_mode, expected_text) in run_variations {
13724            eprintln!(
13725                "run = {:?}, mode = {lsp_insert_mode:.?}",
13726                run.run_description,
13727            );
13728
13729            update_test_language_settings(&mut cx, |settings| {
13730                settings.defaults.completions = Some(CompletionSettingsContent {
13731                    lsp_insert_mode: Some(lsp_insert_mode),
13732                    words: Some(WordsCompletionMode::Disabled),
13733                    words_min_length: Some(0),
13734                    ..Default::default()
13735                });
13736            });
13737
13738            cx.set_state(&run.initial_state);
13739            cx.update_editor(|editor, window, cx| {
13740                editor.show_completions(
13741                    &ShowCompletions {
13742                        trigger: None,
13743                        snippets_only: false,
13744                    },
13745                    window,
13746                    cx,
13747                );
13748            });
13749
13750            let counter = Arc::new(AtomicUsize::new(0));
13751            handle_completion_request_with_insert_and_replace(
13752                &mut cx,
13753                &run.buffer_marked_text,
13754                vec![(run.completion_label, run.completion_text)],
13755                counter.clone(),
13756            )
13757            .await;
13758            cx.condition(|editor, _| editor.context_menu_visible())
13759                .await;
13760            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13761
13762            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13763                editor
13764                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13765                    .unwrap()
13766            });
13767            cx.assert_editor_state(&expected_text);
13768            handle_resolve_completion_request(&mut cx, None).await;
13769            apply_additional_edits.await.unwrap();
13770        }
13771    }
13772}
13773
13774#[gpui::test]
13775async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13776    init_test(cx, |_| {});
13777    let mut cx = EditorLspTestContext::new_rust(
13778        lsp::ServerCapabilities {
13779            completion_provider: Some(lsp::CompletionOptions {
13780                resolve_provider: Some(true),
13781                ..Default::default()
13782            }),
13783            ..Default::default()
13784        },
13785        cx,
13786    )
13787    .await;
13788
13789    let initial_state = "SubˇError";
13790    let buffer_marked_text = "<Sub|Error>";
13791    let completion_text = "SubscriptionError";
13792    let expected_with_insert_mode = "SubscriptionErrorˇError";
13793    let expected_with_replace_mode = "SubscriptionErrorˇ";
13794
13795    update_test_language_settings(&mut cx, |settings| {
13796        settings.defaults.completions = Some(CompletionSettingsContent {
13797            words: Some(WordsCompletionMode::Disabled),
13798            words_min_length: Some(0),
13799            // set the opposite here to ensure that the action is overriding the default behavior
13800            lsp_insert_mode: Some(LspInsertMode::Insert),
13801            ..Default::default()
13802        });
13803    });
13804
13805    cx.set_state(initial_state);
13806    cx.update_editor(|editor, window, cx| {
13807        editor.show_completions(
13808            &ShowCompletions {
13809                trigger: None,
13810                snippets_only: false,
13811            },
13812            window,
13813            cx,
13814        );
13815    });
13816
13817    let counter = Arc::new(AtomicUsize::new(0));
13818    handle_completion_request_with_insert_and_replace(
13819        &mut cx,
13820        buffer_marked_text,
13821        vec![(completion_text, completion_text)],
13822        counter.clone(),
13823    )
13824    .await;
13825    cx.condition(|editor, _| editor.context_menu_visible())
13826        .await;
13827    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13828
13829    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13830        editor
13831            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13832            .unwrap()
13833    });
13834    cx.assert_editor_state(expected_with_replace_mode);
13835    handle_resolve_completion_request(&mut cx, None).await;
13836    apply_additional_edits.await.unwrap();
13837
13838    update_test_language_settings(&mut cx, |settings| {
13839        settings.defaults.completions = Some(CompletionSettingsContent {
13840            words: Some(WordsCompletionMode::Disabled),
13841            words_min_length: Some(0),
13842            // set the opposite here to ensure that the action is overriding the default behavior
13843            lsp_insert_mode: Some(LspInsertMode::Replace),
13844            ..Default::default()
13845        });
13846    });
13847
13848    cx.set_state(initial_state);
13849    cx.update_editor(|editor, window, cx| {
13850        editor.show_completions(
13851            &ShowCompletions {
13852                trigger: None,
13853                snippets_only: false,
13854            },
13855            window,
13856            cx,
13857        );
13858    });
13859    handle_completion_request_with_insert_and_replace(
13860        &mut cx,
13861        buffer_marked_text,
13862        vec![(completion_text, completion_text)],
13863        counter.clone(),
13864    )
13865    .await;
13866    cx.condition(|editor, _| editor.context_menu_visible())
13867        .await;
13868    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13869
13870    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13871        editor
13872            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13873            .unwrap()
13874    });
13875    cx.assert_editor_state(expected_with_insert_mode);
13876    handle_resolve_completion_request(&mut cx, None).await;
13877    apply_additional_edits.await.unwrap();
13878}
13879
13880#[gpui::test]
13881async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13882    init_test(cx, |_| {});
13883    let mut cx = EditorLspTestContext::new_rust(
13884        lsp::ServerCapabilities {
13885            completion_provider: Some(lsp::CompletionOptions {
13886                resolve_provider: Some(true),
13887                ..Default::default()
13888            }),
13889            ..Default::default()
13890        },
13891        cx,
13892    )
13893    .await;
13894
13895    // scenario: surrounding text matches completion text
13896    let completion_text = "to_offset";
13897    let initial_state = indoc! {"
13898        1. buf.to_offˇsuffix
13899        2. buf.to_offˇsuf
13900        3. buf.to_offˇfix
13901        4. buf.to_offˇ
13902        5. into_offˇensive
13903        6. ˇsuffix
13904        7. let ˇ //
13905        8. aaˇzz
13906        9. buf.to_off«zzzzzˇ»suffix
13907        10. buf.«ˇzzzzz»suffix
13908        11. to_off«ˇzzzzz»
13909
13910        buf.to_offˇsuffix  // newest cursor
13911    "};
13912    let completion_marked_buffer = indoc! {"
13913        1. buf.to_offsuffix
13914        2. buf.to_offsuf
13915        3. buf.to_offfix
13916        4. buf.to_off
13917        5. into_offensive
13918        6. suffix
13919        7. let  //
13920        8. aazz
13921        9. buf.to_offzzzzzsuffix
13922        10. buf.zzzzzsuffix
13923        11. to_offzzzzz
13924
13925        buf.<to_off|suffix>  // newest cursor
13926    "};
13927    let expected = indoc! {"
13928        1. buf.to_offsetˇ
13929        2. buf.to_offsetˇsuf
13930        3. buf.to_offsetˇfix
13931        4. buf.to_offsetˇ
13932        5. into_offsetˇensive
13933        6. to_offsetˇsuffix
13934        7. let to_offsetˇ //
13935        8. aato_offsetˇzz
13936        9. buf.to_offsetˇ
13937        10. buf.to_offsetˇsuffix
13938        11. to_offsetˇ
13939
13940        buf.to_offsetˇ  // newest cursor
13941    "};
13942    cx.set_state(initial_state);
13943    cx.update_editor(|editor, window, cx| {
13944        editor.show_completions(
13945            &ShowCompletions {
13946                trigger: None,
13947                snippets_only: false,
13948            },
13949            window,
13950            cx,
13951        );
13952    });
13953    handle_completion_request_with_insert_and_replace(
13954        &mut cx,
13955        completion_marked_buffer,
13956        vec![(completion_text, completion_text)],
13957        Arc::new(AtomicUsize::new(0)),
13958    )
13959    .await;
13960    cx.condition(|editor, _| editor.context_menu_visible())
13961        .await;
13962    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13963        editor
13964            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13965            .unwrap()
13966    });
13967    cx.assert_editor_state(expected);
13968    handle_resolve_completion_request(&mut cx, None).await;
13969    apply_additional_edits.await.unwrap();
13970
13971    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13972    let completion_text = "foo_and_bar";
13973    let initial_state = indoc! {"
13974        1. ooanbˇ
13975        2. zooanbˇ
13976        3. ooanbˇz
13977        4. zooanbˇz
13978        5. ooanˇ
13979        6. oanbˇ
13980
13981        ooanbˇ
13982    "};
13983    let completion_marked_buffer = indoc! {"
13984        1. ooanb
13985        2. zooanb
13986        3. ooanbz
13987        4. zooanbz
13988        5. ooan
13989        6. oanb
13990
13991        <ooanb|>
13992    "};
13993    let expected = indoc! {"
13994        1. foo_and_barˇ
13995        2. zfoo_and_barˇ
13996        3. foo_and_barˇz
13997        4. zfoo_and_barˇz
13998        5. ooanfoo_and_barˇ
13999        6. oanbfoo_and_barˇ
14000
14001        foo_and_barˇ
14002    "};
14003    cx.set_state(initial_state);
14004    cx.update_editor(|editor, window, cx| {
14005        editor.show_completions(
14006            &ShowCompletions {
14007                trigger: None,
14008                snippets_only: false,
14009            },
14010            window,
14011            cx,
14012        );
14013    });
14014    handle_completion_request_with_insert_and_replace(
14015        &mut cx,
14016        completion_marked_buffer,
14017        vec![(completion_text, completion_text)],
14018        Arc::new(AtomicUsize::new(0)),
14019    )
14020    .await;
14021    cx.condition(|editor, _| editor.context_menu_visible())
14022        .await;
14023    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14024        editor
14025            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14026            .unwrap()
14027    });
14028    cx.assert_editor_state(expected);
14029    handle_resolve_completion_request(&mut cx, None).await;
14030    apply_additional_edits.await.unwrap();
14031
14032    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14033    // (expects the same as if it was inserted at the end)
14034    let completion_text = "foo_and_bar";
14035    let initial_state = indoc! {"
14036        1. ooˇanb
14037        2. zooˇanb
14038        3. ooˇanbz
14039        4. zooˇanbz
14040
14041        ooˇanb
14042    "};
14043    let completion_marked_buffer = indoc! {"
14044        1. ooanb
14045        2. zooanb
14046        3. ooanbz
14047        4. zooanbz
14048
14049        <oo|anb>
14050    "};
14051    let expected = indoc! {"
14052        1. foo_and_barˇ
14053        2. zfoo_and_barˇ
14054        3. foo_and_barˇz
14055        4. zfoo_and_barˇz
14056
14057        foo_and_barˇ
14058    "};
14059    cx.set_state(initial_state);
14060    cx.update_editor(|editor, window, cx| {
14061        editor.show_completions(
14062            &ShowCompletions {
14063                trigger: None,
14064                snippets_only: false,
14065            },
14066            window,
14067            cx,
14068        );
14069    });
14070    handle_completion_request_with_insert_and_replace(
14071        &mut cx,
14072        completion_marked_buffer,
14073        vec![(completion_text, completion_text)],
14074        Arc::new(AtomicUsize::new(0)),
14075    )
14076    .await;
14077    cx.condition(|editor, _| editor.context_menu_visible())
14078        .await;
14079    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14080        editor
14081            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14082            .unwrap()
14083    });
14084    cx.assert_editor_state(expected);
14085    handle_resolve_completion_request(&mut cx, None).await;
14086    apply_additional_edits.await.unwrap();
14087}
14088
14089// This used to crash
14090#[gpui::test]
14091async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14092    init_test(cx, |_| {});
14093
14094    let buffer_text = indoc! {"
14095        fn main() {
14096            10.satu;
14097
14098            //
14099            // separate cursors so they open in different excerpts (manually reproducible)
14100            //
14101
14102            10.satu20;
14103        }
14104    "};
14105    let multibuffer_text_with_selections = indoc! {"
14106        fn main() {
14107            10.satuˇ;
14108
14109            //
14110
14111            //
14112
14113            10.satuˇ20;
14114        }
14115    "};
14116    let expected_multibuffer = indoc! {"
14117        fn main() {
14118            10.saturating_sub()ˇ;
14119
14120            //
14121
14122            //
14123
14124            10.saturating_sub()ˇ;
14125        }
14126    "};
14127
14128    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14129    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14130
14131    let fs = FakeFs::new(cx.executor());
14132    fs.insert_tree(
14133        path!("/a"),
14134        json!({
14135            "main.rs": buffer_text,
14136        }),
14137    )
14138    .await;
14139
14140    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14141    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14142    language_registry.add(rust_lang());
14143    let mut fake_servers = language_registry.register_fake_lsp(
14144        "Rust",
14145        FakeLspAdapter {
14146            capabilities: lsp::ServerCapabilities {
14147                completion_provider: Some(lsp::CompletionOptions {
14148                    resolve_provider: None,
14149                    ..lsp::CompletionOptions::default()
14150                }),
14151                ..lsp::ServerCapabilities::default()
14152            },
14153            ..FakeLspAdapter::default()
14154        },
14155    );
14156    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14157    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14158    let buffer = project
14159        .update(cx, |project, cx| {
14160            project.open_local_buffer(path!("/a/main.rs"), cx)
14161        })
14162        .await
14163        .unwrap();
14164
14165    let multi_buffer = cx.new(|cx| {
14166        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14167        multi_buffer.push_excerpts(
14168            buffer.clone(),
14169            [ExcerptRange::new(0..first_excerpt_end)],
14170            cx,
14171        );
14172        multi_buffer.push_excerpts(
14173            buffer.clone(),
14174            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14175            cx,
14176        );
14177        multi_buffer
14178    });
14179
14180    let editor = workspace
14181        .update(cx, |_, window, cx| {
14182            cx.new(|cx| {
14183                Editor::new(
14184                    EditorMode::Full {
14185                        scale_ui_elements_with_buffer_font_size: false,
14186                        show_active_line_background: false,
14187                        sized_by_content: false,
14188                    },
14189                    multi_buffer.clone(),
14190                    Some(project.clone()),
14191                    window,
14192                    cx,
14193                )
14194            })
14195        })
14196        .unwrap();
14197
14198    let pane = workspace
14199        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14200        .unwrap();
14201    pane.update_in(cx, |pane, window, cx| {
14202        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14203    });
14204
14205    let fake_server = fake_servers.next().await.unwrap();
14206
14207    editor.update_in(cx, |editor, window, cx| {
14208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14209            s.select_ranges([
14210                Point::new(1, 11)..Point::new(1, 11),
14211                Point::new(7, 11)..Point::new(7, 11),
14212            ])
14213        });
14214
14215        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14216    });
14217
14218    editor.update_in(cx, |editor, window, cx| {
14219        editor.show_completions(
14220            &ShowCompletions {
14221                trigger: None,
14222                snippets_only: false,
14223            },
14224            window,
14225            cx,
14226        );
14227    });
14228
14229    fake_server
14230        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14231            let completion_item = lsp::CompletionItem {
14232                label: "saturating_sub()".into(),
14233                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14234                    lsp::InsertReplaceEdit {
14235                        new_text: "saturating_sub()".to_owned(),
14236                        insert: lsp::Range::new(
14237                            lsp::Position::new(7, 7),
14238                            lsp::Position::new(7, 11),
14239                        ),
14240                        replace: lsp::Range::new(
14241                            lsp::Position::new(7, 7),
14242                            lsp::Position::new(7, 13),
14243                        ),
14244                    },
14245                )),
14246                ..lsp::CompletionItem::default()
14247            };
14248
14249            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14250        })
14251        .next()
14252        .await
14253        .unwrap();
14254
14255    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14256        .await;
14257
14258    editor
14259        .update_in(cx, |editor, window, cx| {
14260            editor
14261                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14262                .unwrap()
14263        })
14264        .await
14265        .unwrap();
14266
14267    editor.update(cx, |editor, cx| {
14268        assert_text_with_selections(editor, expected_multibuffer, cx);
14269    })
14270}
14271
14272#[gpui::test]
14273async fn test_completion(cx: &mut TestAppContext) {
14274    init_test(cx, |_| {});
14275
14276    let mut cx = EditorLspTestContext::new_rust(
14277        lsp::ServerCapabilities {
14278            completion_provider: Some(lsp::CompletionOptions {
14279                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14280                resolve_provider: Some(true),
14281                ..Default::default()
14282            }),
14283            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14284            ..Default::default()
14285        },
14286        cx,
14287    )
14288    .await;
14289    let counter = Arc::new(AtomicUsize::new(0));
14290
14291    cx.set_state(indoc! {"
14292        oneˇ
14293        two
14294        three
14295    "});
14296    cx.simulate_keystroke(".");
14297    handle_completion_request(
14298        indoc! {"
14299            one.|<>
14300            two
14301            three
14302        "},
14303        vec!["first_completion", "second_completion"],
14304        true,
14305        counter.clone(),
14306        &mut cx,
14307    )
14308    .await;
14309    cx.condition(|editor, _| editor.context_menu_visible())
14310        .await;
14311    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14312
14313    let _handler = handle_signature_help_request(
14314        &mut cx,
14315        lsp::SignatureHelp {
14316            signatures: vec![lsp::SignatureInformation {
14317                label: "test signature".to_string(),
14318                documentation: None,
14319                parameters: Some(vec![lsp::ParameterInformation {
14320                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14321                    documentation: None,
14322                }]),
14323                active_parameter: None,
14324            }],
14325            active_signature: None,
14326            active_parameter: None,
14327        },
14328    );
14329    cx.update_editor(|editor, window, cx| {
14330        assert!(
14331            !editor.signature_help_state.is_shown(),
14332            "No signature help was called for"
14333        );
14334        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14335    });
14336    cx.run_until_parked();
14337    cx.update_editor(|editor, _, _| {
14338        assert!(
14339            !editor.signature_help_state.is_shown(),
14340            "No signature help should be shown when completions menu is open"
14341        );
14342    });
14343
14344    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14345        editor.context_menu_next(&Default::default(), window, cx);
14346        editor
14347            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14348            .unwrap()
14349    });
14350    cx.assert_editor_state(indoc! {"
14351        one.second_completionˇ
14352        two
14353        three
14354    "});
14355
14356    handle_resolve_completion_request(
14357        &mut cx,
14358        Some(vec![
14359            (
14360                //This overlaps with the primary completion edit which is
14361                //misbehavior from the LSP spec, test that we filter it out
14362                indoc! {"
14363                    one.second_ˇcompletion
14364                    two
14365                    threeˇ
14366                "},
14367                "overlapping additional edit",
14368            ),
14369            (
14370                indoc! {"
14371                    one.second_completion
14372                    two
14373                    threeˇ
14374                "},
14375                "\nadditional edit",
14376            ),
14377        ]),
14378    )
14379    .await;
14380    apply_additional_edits.await.unwrap();
14381    cx.assert_editor_state(indoc! {"
14382        one.second_completionˇ
14383        two
14384        three
14385        additional edit
14386    "});
14387
14388    cx.set_state(indoc! {"
14389        one.second_completion
14390        twoˇ
14391        threeˇ
14392        additional edit
14393    "});
14394    cx.simulate_keystroke(" ");
14395    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14396    cx.simulate_keystroke("s");
14397    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14398
14399    cx.assert_editor_state(indoc! {"
14400        one.second_completion
14401        two sˇ
14402        three sˇ
14403        additional edit
14404    "});
14405    handle_completion_request(
14406        indoc! {"
14407            one.second_completion
14408            two s
14409            three <s|>
14410            additional edit
14411        "},
14412        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14413        true,
14414        counter.clone(),
14415        &mut cx,
14416    )
14417    .await;
14418    cx.condition(|editor, _| editor.context_menu_visible())
14419        .await;
14420    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14421
14422    cx.simulate_keystroke("i");
14423
14424    handle_completion_request(
14425        indoc! {"
14426            one.second_completion
14427            two si
14428            three <si|>
14429            additional edit
14430        "},
14431        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14432        true,
14433        counter.clone(),
14434        &mut cx,
14435    )
14436    .await;
14437    cx.condition(|editor, _| editor.context_menu_visible())
14438        .await;
14439    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14440
14441    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14442        editor
14443            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14444            .unwrap()
14445    });
14446    cx.assert_editor_state(indoc! {"
14447        one.second_completion
14448        two sixth_completionˇ
14449        three sixth_completionˇ
14450        additional edit
14451    "});
14452
14453    apply_additional_edits.await.unwrap();
14454
14455    update_test_language_settings(&mut cx, |settings| {
14456        settings.defaults.show_completions_on_input = Some(false);
14457    });
14458    cx.set_state("editorˇ");
14459    cx.simulate_keystroke(".");
14460    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14461    cx.simulate_keystrokes("c l o");
14462    cx.assert_editor_state("editor.cloˇ");
14463    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14464    cx.update_editor(|editor, window, cx| {
14465        editor.show_completions(
14466            &ShowCompletions {
14467                trigger: None,
14468                snippets_only: false,
14469            },
14470            window,
14471            cx,
14472        );
14473    });
14474    handle_completion_request(
14475        "editor.<clo|>",
14476        vec!["close", "clobber"],
14477        true,
14478        counter.clone(),
14479        &mut cx,
14480    )
14481    .await;
14482    cx.condition(|editor, _| editor.context_menu_visible())
14483        .await;
14484    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14485
14486    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14487        editor
14488            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14489            .unwrap()
14490    });
14491    cx.assert_editor_state("editor.clobberˇ");
14492    handle_resolve_completion_request(&mut cx, None).await;
14493    apply_additional_edits.await.unwrap();
14494}
14495
14496#[gpui::test]
14497async fn test_completion_reuse(cx: &mut TestAppContext) {
14498    init_test(cx, |_| {});
14499
14500    let mut cx = EditorLspTestContext::new_rust(
14501        lsp::ServerCapabilities {
14502            completion_provider: Some(lsp::CompletionOptions {
14503                trigger_characters: Some(vec![".".to_string()]),
14504                ..Default::default()
14505            }),
14506            ..Default::default()
14507        },
14508        cx,
14509    )
14510    .await;
14511
14512    let counter = Arc::new(AtomicUsize::new(0));
14513    cx.set_state("objˇ");
14514    cx.simulate_keystroke(".");
14515
14516    // Initial completion request returns complete results
14517    let is_incomplete = false;
14518    handle_completion_request(
14519        "obj.|<>",
14520        vec!["a", "ab", "abc"],
14521        is_incomplete,
14522        counter.clone(),
14523        &mut cx,
14524    )
14525    .await;
14526    cx.run_until_parked();
14527    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14528    cx.assert_editor_state("obj.ˇ");
14529    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14530
14531    // Type "a" - filters existing completions
14532    cx.simulate_keystroke("a");
14533    cx.run_until_parked();
14534    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14535    cx.assert_editor_state("obj.aˇ");
14536    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14537
14538    // Type "b" - filters existing completions
14539    cx.simulate_keystroke("b");
14540    cx.run_until_parked();
14541    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14542    cx.assert_editor_state("obj.abˇ");
14543    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14544
14545    // Type "c" - filters existing completions
14546    cx.simulate_keystroke("c");
14547    cx.run_until_parked();
14548    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14549    cx.assert_editor_state("obj.abcˇ");
14550    check_displayed_completions(vec!["abc"], &mut cx);
14551
14552    // Backspace to delete "c" - filters existing completions
14553    cx.update_editor(|editor, window, cx| {
14554        editor.backspace(&Backspace, window, cx);
14555    });
14556    cx.run_until_parked();
14557    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14558    cx.assert_editor_state("obj.abˇ");
14559    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14560
14561    // Moving cursor to the left dismisses menu.
14562    cx.update_editor(|editor, window, cx| {
14563        editor.move_left(&MoveLeft, window, cx);
14564    });
14565    cx.run_until_parked();
14566    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14567    cx.assert_editor_state("obj.aˇb");
14568    cx.update_editor(|editor, _, _| {
14569        assert_eq!(editor.context_menu_visible(), false);
14570    });
14571
14572    // Type "b" - new request
14573    cx.simulate_keystroke("b");
14574    let is_incomplete = false;
14575    handle_completion_request(
14576        "obj.<ab|>a",
14577        vec!["ab", "abc"],
14578        is_incomplete,
14579        counter.clone(),
14580        &mut cx,
14581    )
14582    .await;
14583    cx.run_until_parked();
14584    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14585    cx.assert_editor_state("obj.abˇb");
14586    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14587
14588    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14589    cx.update_editor(|editor, window, cx| {
14590        editor.backspace(&Backspace, window, cx);
14591    });
14592    let is_incomplete = false;
14593    handle_completion_request(
14594        "obj.<a|>b",
14595        vec!["a", "ab", "abc"],
14596        is_incomplete,
14597        counter.clone(),
14598        &mut cx,
14599    )
14600    .await;
14601    cx.run_until_parked();
14602    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14603    cx.assert_editor_state("obj.aˇb");
14604    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14605
14606    // Backspace to delete "a" - dismisses menu.
14607    cx.update_editor(|editor, window, cx| {
14608        editor.backspace(&Backspace, window, cx);
14609    });
14610    cx.run_until_parked();
14611    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14612    cx.assert_editor_state("obj.ˇb");
14613    cx.update_editor(|editor, _, _| {
14614        assert_eq!(editor.context_menu_visible(), false);
14615    });
14616}
14617
14618#[gpui::test]
14619async fn test_word_completion(cx: &mut TestAppContext) {
14620    let lsp_fetch_timeout_ms = 10;
14621    init_test(cx, |language_settings| {
14622        language_settings.defaults.completions = Some(CompletionSettingsContent {
14623            words_min_length: Some(0),
14624            lsp_fetch_timeout_ms: Some(10),
14625            lsp_insert_mode: Some(LspInsertMode::Insert),
14626            ..Default::default()
14627        });
14628    });
14629
14630    let mut cx = EditorLspTestContext::new_rust(
14631        lsp::ServerCapabilities {
14632            completion_provider: Some(lsp::CompletionOptions {
14633                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14634                ..lsp::CompletionOptions::default()
14635            }),
14636            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14637            ..lsp::ServerCapabilities::default()
14638        },
14639        cx,
14640    )
14641    .await;
14642
14643    let throttle_completions = Arc::new(AtomicBool::new(false));
14644
14645    let lsp_throttle_completions = throttle_completions.clone();
14646    let _completion_requests_handler =
14647        cx.lsp
14648            .server
14649            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14650                let lsp_throttle_completions = lsp_throttle_completions.clone();
14651                let cx = cx.clone();
14652                async move {
14653                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14654                        cx.background_executor()
14655                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14656                            .await;
14657                    }
14658                    Ok(Some(lsp::CompletionResponse::Array(vec![
14659                        lsp::CompletionItem {
14660                            label: "first".into(),
14661                            ..lsp::CompletionItem::default()
14662                        },
14663                        lsp::CompletionItem {
14664                            label: "last".into(),
14665                            ..lsp::CompletionItem::default()
14666                        },
14667                    ])))
14668                }
14669            });
14670
14671    cx.set_state(indoc! {"
14672        oneˇ
14673        two
14674        three
14675    "});
14676    cx.simulate_keystroke(".");
14677    cx.executor().run_until_parked();
14678    cx.condition(|editor, _| editor.context_menu_visible())
14679        .await;
14680    cx.update_editor(|editor, window, cx| {
14681        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14682        {
14683            assert_eq!(
14684                completion_menu_entries(menu),
14685                &["first", "last"],
14686                "When LSP server is fast to reply, no fallback word completions are used"
14687            );
14688        } else {
14689            panic!("expected completion menu to be open");
14690        }
14691        editor.cancel(&Cancel, window, cx);
14692    });
14693    cx.executor().run_until_parked();
14694    cx.condition(|editor, _| !editor.context_menu_visible())
14695        .await;
14696
14697    throttle_completions.store(true, atomic::Ordering::Release);
14698    cx.simulate_keystroke(".");
14699    cx.executor()
14700        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14701    cx.executor().run_until_parked();
14702    cx.condition(|editor, _| editor.context_menu_visible())
14703        .await;
14704    cx.update_editor(|editor, _, _| {
14705        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14706        {
14707            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14708                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14709        } else {
14710            panic!("expected completion menu to be open");
14711        }
14712    });
14713}
14714
14715#[gpui::test]
14716async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14717    init_test(cx, |language_settings| {
14718        language_settings.defaults.completions = Some(CompletionSettingsContent {
14719            words: Some(WordsCompletionMode::Enabled),
14720            words_min_length: Some(0),
14721            lsp_insert_mode: Some(LspInsertMode::Insert),
14722            ..Default::default()
14723        });
14724    });
14725
14726    let mut cx = EditorLspTestContext::new_rust(
14727        lsp::ServerCapabilities {
14728            completion_provider: Some(lsp::CompletionOptions {
14729                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14730                ..lsp::CompletionOptions::default()
14731            }),
14732            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14733            ..lsp::ServerCapabilities::default()
14734        },
14735        cx,
14736    )
14737    .await;
14738
14739    let _completion_requests_handler =
14740        cx.lsp
14741            .server
14742            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14743                Ok(Some(lsp::CompletionResponse::Array(vec![
14744                    lsp::CompletionItem {
14745                        label: "first".into(),
14746                        ..lsp::CompletionItem::default()
14747                    },
14748                    lsp::CompletionItem {
14749                        label: "last".into(),
14750                        ..lsp::CompletionItem::default()
14751                    },
14752                ])))
14753            });
14754
14755    cx.set_state(indoc! {"ˇ
14756        first
14757        last
14758        second
14759    "});
14760    cx.simulate_keystroke(".");
14761    cx.executor().run_until_parked();
14762    cx.condition(|editor, _| editor.context_menu_visible())
14763        .await;
14764    cx.update_editor(|editor, _, _| {
14765        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14766        {
14767            assert_eq!(
14768                completion_menu_entries(menu),
14769                &["first", "last", "second"],
14770                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14771            );
14772        } else {
14773            panic!("expected completion menu to be open");
14774        }
14775    });
14776}
14777
14778#[gpui::test]
14779async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14780    init_test(cx, |language_settings| {
14781        language_settings.defaults.completions = Some(CompletionSettingsContent {
14782            words: Some(WordsCompletionMode::Disabled),
14783            words_min_length: Some(0),
14784            lsp_insert_mode: Some(LspInsertMode::Insert),
14785            ..Default::default()
14786        });
14787    });
14788
14789    let mut cx = EditorLspTestContext::new_rust(
14790        lsp::ServerCapabilities {
14791            completion_provider: Some(lsp::CompletionOptions {
14792                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14793                ..lsp::CompletionOptions::default()
14794            }),
14795            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14796            ..lsp::ServerCapabilities::default()
14797        },
14798        cx,
14799    )
14800    .await;
14801
14802    let _completion_requests_handler =
14803        cx.lsp
14804            .server
14805            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14806                panic!("LSP completions should not be queried when dealing with word completions")
14807            });
14808
14809    cx.set_state(indoc! {"ˇ
14810        first
14811        last
14812        second
14813    "});
14814    cx.update_editor(|editor, window, cx| {
14815        editor.show_word_completions(&ShowWordCompletions, window, cx);
14816    });
14817    cx.executor().run_until_parked();
14818    cx.condition(|editor, _| editor.context_menu_visible())
14819        .await;
14820    cx.update_editor(|editor, _, _| {
14821        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14822        {
14823            assert_eq!(
14824                completion_menu_entries(menu),
14825                &["first", "last", "second"],
14826                "`ShowWordCompletions` action should show word completions"
14827            );
14828        } else {
14829            panic!("expected completion menu to be open");
14830        }
14831    });
14832
14833    cx.simulate_keystroke("l");
14834    cx.executor().run_until_parked();
14835    cx.condition(|editor, _| editor.context_menu_visible())
14836        .await;
14837    cx.update_editor(|editor, _, _| {
14838        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14839        {
14840            assert_eq!(
14841                completion_menu_entries(menu),
14842                &["last"],
14843                "After showing word completions, further editing should filter them and not query the LSP"
14844            );
14845        } else {
14846            panic!("expected completion menu to be open");
14847        }
14848    });
14849}
14850
14851#[gpui::test]
14852async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14853    init_test(cx, |language_settings| {
14854        language_settings.defaults.completions = Some(CompletionSettingsContent {
14855            words_min_length: Some(0),
14856            lsp: Some(false),
14857            lsp_insert_mode: Some(LspInsertMode::Insert),
14858            ..Default::default()
14859        });
14860    });
14861
14862    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14863
14864    cx.set_state(indoc! {"ˇ
14865        0_usize
14866        let
14867        33
14868        4.5f32
14869    "});
14870    cx.update_editor(|editor, window, cx| {
14871        editor.show_completions(&ShowCompletions::default(), window, cx);
14872    });
14873    cx.executor().run_until_parked();
14874    cx.condition(|editor, _| editor.context_menu_visible())
14875        .await;
14876    cx.update_editor(|editor, window, cx| {
14877        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14878        {
14879            assert_eq!(
14880                completion_menu_entries(menu),
14881                &["let"],
14882                "With no digits in the completion query, no digits should be in the word completions"
14883            );
14884        } else {
14885            panic!("expected completion menu to be open");
14886        }
14887        editor.cancel(&Cancel, window, cx);
14888    });
14889
14890    cx.set_state(indoc! {"14891        0_usize
14892        let
14893        3
14894        33.35f32
14895    "});
14896    cx.update_editor(|editor, window, cx| {
14897        editor.show_completions(&ShowCompletions::default(), window, cx);
14898    });
14899    cx.executor().run_until_parked();
14900    cx.condition(|editor, _| editor.context_menu_visible())
14901        .await;
14902    cx.update_editor(|editor, _, _| {
14903        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14904        {
14905            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14906                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14907        } else {
14908            panic!("expected completion menu to be open");
14909        }
14910    });
14911}
14912
14913#[gpui::test]
14914async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14915    init_test(cx, |language_settings| {
14916        language_settings.defaults.completions = Some(CompletionSettingsContent {
14917            words: Some(WordsCompletionMode::Enabled),
14918            words_min_length: Some(3),
14919            lsp_insert_mode: Some(LspInsertMode::Insert),
14920            ..Default::default()
14921        });
14922    });
14923
14924    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14925    cx.set_state(indoc! {"ˇ
14926        wow
14927        wowen
14928        wowser
14929    "});
14930    cx.simulate_keystroke("w");
14931    cx.executor().run_until_parked();
14932    cx.update_editor(|editor, _, _| {
14933        if editor.context_menu.borrow_mut().is_some() {
14934            panic!(
14935                "expected completion menu to be hidden, as words completion threshold is not met"
14936            );
14937        }
14938    });
14939
14940    cx.update_editor(|editor, window, cx| {
14941        editor.show_word_completions(&ShowWordCompletions, window, cx);
14942    });
14943    cx.executor().run_until_parked();
14944    cx.update_editor(|editor, window, cx| {
14945        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14946        {
14947            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");
14948        } else {
14949            panic!("expected completion menu to be open after the word completions are called with an action");
14950        }
14951
14952        editor.cancel(&Cancel, window, cx);
14953    });
14954    cx.update_editor(|editor, _, _| {
14955        if editor.context_menu.borrow_mut().is_some() {
14956            panic!("expected completion menu to be hidden after canceling");
14957        }
14958    });
14959
14960    cx.simulate_keystroke("o");
14961    cx.executor().run_until_parked();
14962    cx.update_editor(|editor, _, _| {
14963        if editor.context_menu.borrow_mut().is_some() {
14964            panic!(
14965                "expected completion menu to be hidden, as words completion threshold is not met still"
14966            );
14967        }
14968    });
14969
14970    cx.simulate_keystroke("w");
14971    cx.executor().run_until_parked();
14972    cx.update_editor(|editor, _, _| {
14973        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14974        {
14975            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14976        } else {
14977            panic!("expected completion menu to be open after the word completions threshold is met");
14978        }
14979    });
14980}
14981
14982#[gpui::test]
14983async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14984    init_test(cx, |language_settings| {
14985        language_settings.defaults.completions = Some(CompletionSettingsContent {
14986            words: Some(WordsCompletionMode::Enabled),
14987            words_min_length: Some(0),
14988            lsp_insert_mode: Some(LspInsertMode::Insert),
14989            ..Default::default()
14990        });
14991    });
14992
14993    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14994    cx.update_editor(|editor, _, _| {
14995        editor.disable_word_completions();
14996    });
14997    cx.set_state(indoc! {"ˇ
14998        wow
14999        wowen
15000        wowser
15001    "});
15002    cx.simulate_keystroke("w");
15003    cx.executor().run_until_parked();
15004    cx.update_editor(|editor, _, _| {
15005        if editor.context_menu.borrow_mut().is_some() {
15006            panic!(
15007                "expected completion menu to be hidden, as words completion are disabled for this editor"
15008            );
15009        }
15010    });
15011
15012    cx.update_editor(|editor, window, cx| {
15013        editor.show_word_completions(&ShowWordCompletions, window, cx);
15014    });
15015    cx.executor().run_until_parked();
15016    cx.update_editor(|editor, _, _| {
15017        if editor.context_menu.borrow_mut().is_some() {
15018            panic!(
15019                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15020            );
15021        }
15022    });
15023}
15024
15025fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15026    let position = || lsp::Position {
15027        line: params.text_document_position.position.line,
15028        character: params.text_document_position.position.character,
15029    };
15030    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15031        range: lsp::Range {
15032            start: position(),
15033            end: position(),
15034        },
15035        new_text: text.to_string(),
15036    }))
15037}
15038
15039#[gpui::test]
15040async fn test_multiline_completion(cx: &mut TestAppContext) {
15041    init_test(cx, |_| {});
15042
15043    let fs = FakeFs::new(cx.executor());
15044    fs.insert_tree(
15045        path!("/a"),
15046        json!({
15047            "main.ts": "a",
15048        }),
15049    )
15050    .await;
15051
15052    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15053    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15054    let typescript_language = Arc::new(Language::new(
15055        LanguageConfig {
15056            name: "TypeScript".into(),
15057            matcher: LanguageMatcher {
15058                path_suffixes: vec!["ts".to_string()],
15059                ..LanguageMatcher::default()
15060            },
15061            line_comments: vec!["// ".into()],
15062            ..LanguageConfig::default()
15063        },
15064        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15065    ));
15066    language_registry.add(typescript_language.clone());
15067    let mut fake_servers = language_registry.register_fake_lsp(
15068        "TypeScript",
15069        FakeLspAdapter {
15070            capabilities: lsp::ServerCapabilities {
15071                completion_provider: Some(lsp::CompletionOptions {
15072                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15073                    ..lsp::CompletionOptions::default()
15074                }),
15075                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15076                ..lsp::ServerCapabilities::default()
15077            },
15078            // Emulate vtsls label generation
15079            label_for_completion: Some(Box::new(|item, _| {
15080                let text = if let Some(description) = item
15081                    .label_details
15082                    .as_ref()
15083                    .and_then(|label_details| label_details.description.as_ref())
15084                {
15085                    format!("{} {}", item.label, description)
15086                } else if let Some(detail) = &item.detail {
15087                    format!("{} {}", item.label, detail)
15088                } else {
15089                    item.label.clone()
15090                };
15091                Some(language::CodeLabel::plain(text, None))
15092            })),
15093            ..FakeLspAdapter::default()
15094        },
15095    );
15096    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15097    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15098    let worktree_id = workspace
15099        .update(cx, |workspace, _window, cx| {
15100            workspace.project().update(cx, |project, cx| {
15101                project.worktrees(cx).next().unwrap().read(cx).id()
15102            })
15103        })
15104        .unwrap();
15105    let _buffer = project
15106        .update(cx, |project, cx| {
15107            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15108        })
15109        .await
15110        .unwrap();
15111    let editor = workspace
15112        .update(cx, |workspace, window, cx| {
15113            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15114        })
15115        .unwrap()
15116        .await
15117        .unwrap()
15118        .downcast::<Editor>()
15119        .unwrap();
15120    let fake_server = fake_servers.next().await.unwrap();
15121
15122    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15123    let multiline_label_2 = "a\nb\nc\n";
15124    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15125    let multiline_description = "d\ne\nf\n";
15126    let multiline_detail_2 = "g\nh\ni\n";
15127
15128    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15129        move |params, _| async move {
15130            Ok(Some(lsp::CompletionResponse::Array(vec![
15131                lsp::CompletionItem {
15132                    label: multiline_label.to_string(),
15133                    text_edit: gen_text_edit(&params, "new_text_1"),
15134                    ..lsp::CompletionItem::default()
15135                },
15136                lsp::CompletionItem {
15137                    label: "single line label 1".to_string(),
15138                    detail: Some(multiline_detail.to_string()),
15139                    text_edit: gen_text_edit(&params, "new_text_2"),
15140                    ..lsp::CompletionItem::default()
15141                },
15142                lsp::CompletionItem {
15143                    label: "single line label 2".to_string(),
15144                    label_details: Some(lsp::CompletionItemLabelDetails {
15145                        description: Some(multiline_description.to_string()),
15146                        detail: None,
15147                    }),
15148                    text_edit: gen_text_edit(&params, "new_text_2"),
15149                    ..lsp::CompletionItem::default()
15150                },
15151                lsp::CompletionItem {
15152                    label: multiline_label_2.to_string(),
15153                    detail: Some(multiline_detail_2.to_string()),
15154                    text_edit: gen_text_edit(&params, "new_text_3"),
15155                    ..lsp::CompletionItem::default()
15156                },
15157                lsp::CompletionItem {
15158                    label: "Label with many     spaces and \t but without newlines".to_string(),
15159                    detail: Some(
15160                        "Details with many     spaces and \t but without newlines".to_string(),
15161                    ),
15162                    text_edit: gen_text_edit(&params, "new_text_4"),
15163                    ..lsp::CompletionItem::default()
15164                },
15165            ])))
15166        },
15167    );
15168
15169    editor.update_in(cx, |editor, window, cx| {
15170        cx.focus_self(window);
15171        editor.move_to_end(&MoveToEnd, window, cx);
15172        editor.handle_input(".", window, cx);
15173    });
15174    cx.run_until_parked();
15175    completion_handle.next().await.unwrap();
15176
15177    editor.update(cx, |editor, _| {
15178        assert!(editor.context_menu_visible());
15179        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15180        {
15181            let completion_labels = menu
15182                .completions
15183                .borrow()
15184                .iter()
15185                .map(|c| c.label.text.clone())
15186                .collect::<Vec<_>>();
15187            assert_eq!(
15188                completion_labels,
15189                &[
15190                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15191                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15192                    "single line label 2 d e f ",
15193                    "a b c g h i ",
15194                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15195                ],
15196                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15197            );
15198
15199            for completion in menu
15200                .completions
15201                .borrow()
15202                .iter() {
15203                    assert_eq!(
15204                        completion.label.filter_range,
15205                        0..completion.label.text.len(),
15206                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15207                    );
15208                }
15209        } else {
15210            panic!("expected completion menu to be open");
15211        }
15212    });
15213}
15214
15215#[gpui::test]
15216async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15217    init_test(cx, |_| {});
15218    let mut cx = EditorLspTestContext::new_rust(
15219        lsp::ServerCapabilities {
15220            completion_provider: Some(lsp::CompletionOptions {
15221                trigger_characters: Some(vec![".".to_string()]),
15222                ..Default::default()
15223            }),
15224            ..Default::default()
15225        },
15226        cx,
15227    )
15228    .await;
15229    cx.lsp
15230        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15231            Ok(Some(lsp::CompletionResponse::Array(vec![
15232                lsp::CompletionItem {
15233                    label: "first".into(),
15234                    ..Default::default()
15235                },
15236                lsp::CompletionItem {
15237                    label: "last".into(),
15238                    ..Default::default()
15239                },
15240            ])))
15241        });
15242    cx.set_state("variableˇ");
15243    cx.simulate_keystroke(".");
15244    cx.executor().run_until_parked();
15245
15246    cx.update_editor(|editor, _, _| {
15247        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15248        {
15249            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15250        } else {
15251            panic!("expected completion menu to be open");
15252        }
15253    });
15254
15255    cx.update_editor(|editor, window, cx| {
15256        editor.move_page_down(&MovePageDown::default(), window, cx);
15257        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15258        {
15259            assert!(
15260                menu.selected_item == 1,
15261                "expected PageDown to select the last item from the context menu"
15262            );
15263        } else {
15264            panic!("expected completion menu to stay open after PageDown");
15265        }
15266    });
15267
15268    cx.update_editor(|editor, window, cx| {
15269        editor.move_page_up(&MovePageUp::default(), window, cx);
15270        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15271        {
15272            assert!(
15273                menu.selected_item == 0,
15274                "expected PageUp to select the first item from the context menu"
15275            );
15276        } else {
15277            panic!("expected completion menu to stay open after PageUp");
15278        }
15279    });
15280}
15281
15282#[gpui::test]
15283async fn test_as_is_completions(cx: &mut TestAppContext) {
15284    init_test(cx, |_| {});
15285    let mut cx = EditorLspTestContext::new_rust(
15286        lsp::ServerCapabilities {
15287            completion_provider: Some(lsp::CompletionOptions {
15288                ..Default::default()
15289            }),
15290            ..Default::default()
15291        },
15292        cx,
15293    )
15294    .await;
15295    cx.lsp
15296        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15297            Ok(Some(lsp::CompletionResponse::Array(vec![
15298                lsp::CompletionItem {
15299                    label: "unsafe".into(),
15300                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15301                        range: lsp::Range {
15302                            start: lsp::Position {
15303                                line: 1,
15304                                character: 2,
15305                            },
15306                            end: lsp::Position {
15307                                line: 1,
15308                                character: 3,
15309                            },
15310                        },
15311                        new_text: "unsafe".to_string(),
15312                    })),
15313                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15314                    ..Default::default()
15315                },
15316            ])))
15317        });
15318    cx.set_state("fn a() {}\n");
15319    cx.executor().run_until_parked();
15320    cx.update_editor(|editor, window, cx| {
15321        editor.show_completions(
15322            &ShowCompletions {
15323                trigger: Some("\n".into()),
15324                snippets_only: false,
15325            },
15326            window,
15327            cx,
15328        );
15329    });
15330    cx.executor().run_until_parked();
15331
15332    cx.update_editor(|editor, window, cx| {
15333        editor.confirm_completion(&Default::default(), window, cx)
15334    });
15335    cx.executor().run_until_parked();
15336    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15337}
15338
15339#[gpui::test]
15340async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15341    init_test(cx, |_| {});
15342    let language =
15343        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15344    let mut cx = EditorLspTestContext::new(
15345        language,
15346        lsp::ServerCapabilities {
15347            completion_provider: Some(lsp::CompletionOptions {
15348                ..lsp::CompletionOptions::default()
15349            }),
15350            ..lsp::ServerCapabilities::default()
15351        },
15352        cx,
15353    )
15354    .await;
15355
15356    cx.set_state(
15357        "#ifndef BAR_H
15358#define BAR_H
15359
15360#include <stdbool.h>
15361
15362int fn_branch(bool do_branch1, bool do_branch2);
15363
15364#endif // BAR_H
15365ˇ",
15366    );
15367    cx.executor().run_until_parked();
15368    cx.update_editor(|editor, window, cx| {
15369        editor.handle_input("#", window, cx);
15370    });
15371    cx.executor().run_until_parked();
15372    cx.update_editor(|editor, window, cx| {
15373        editor.handle_input("i", window, cx);
15374    });
15375    cx.executor().run_until_parked();
15376    cx.update_editor(|editor, window, cx| {
15377        editor.handle_input("n", window, cx);
15378    });
15379    cx.executor().run_until_parked();
15380    cx.assert_editor_state(
15381        "#ifndef BAR_H
15382#define BAR_H
15383
15384#include <stdbool.h>
15385
15386int fn_branch(bool do_branch1, bool do_branch2);
15387
15388#endif // BAR_H
15389#inˇ",
15390    );
15391
15392    cx.lsp
15393        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15394            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15395                is_incomplete: false,
15396                item_defaults: None,
15397                items: vec![lsp::CompletionItem {
15398                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15399                    label_details: Some(lsp::CompletionItemLabelDetails {
15400                        detail: Some("header".to_string()),
15401                        description: None,
15402                    }),
15403                    label: " include".to_string(),
15404                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15405                        range: lsp::Range {
15406                            start: lsp::Position {
15407                                line: 8,
15408                                character: 1,
15409                            },
15410                            end: lsp::Position {
15411                                line: 8,
15412                                character: 1,
15413                            },
15414                        },
15415                        new_text: "include \"$0\"".to_string(),
15416                    })),
15417                    sort_text: Some("40b67681include".to_string()),
15418                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15419                    filter_text: Some("include".to_string()),
15420                    insert_text: Some("include \"$0\"".to_string()),
15421                    ..lsp::CompletionItem::default()
15422                }],
15423            })))
15424        });
15425    cx.update_editor(|editor, window, cx| {
15426        editor.show_completions(
15427            &ShowCompletions {
15428                trigger: None,
15429                snippets_only: false,
15430            },
15431            window,
15432            cx,
15433        );
15434    });
15435    cx.executor().run_until_parked();
15436    cx.update_editor(|editor, window, cx| {
15437        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15438    });
15439    cx.executor().run_until_parked();
15440    cx.assert_editor_state(
15441        "#ifndef BAR_H
15442#define BAR_H
15443
15444#include <stdbool.h>
15445
15446int fn_branch(bool do_branch1, bool do_branch2);
15447
15448#endif // BAR_H
15449#include \"ˇ\"",
15450    );
15451
15452    cx.lsp
15453        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15454            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15455                is_incomplete: true,
15456                item_defaults: None,
15457                items: vec![lsp::CompletionItem {
15458                    kind: Some(lsp::CompletionItemKind::FILE),
15459                    label: "AGL/".to_string(),
15460                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15461                        range: lsp::Range {
15462                            start: lsp::Position {
15463                                line: 8,
15464                                character: 10,
15465                            },
15466                            end: lsp::Position {
15467                                line: 8,
15468                                character: 11,
15469                            },
15470                        },
15471                        new_text: "AGL/".to_string(),
15472                    })),
15473                    sort_text: Some("40b67681AGL/".to_string()),
15474                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15475                    filter_text: Some("AGL/".to_string()),
15476                    insert_text: Some("AGL/".to_string()),
15477                    ..lsp::CompletionItem::default()
15478                }],
15479            })))
15480        });
15481    cx.update_editor(|editor, window, cx| {
15482        editor.show_completions(
15483            &ShowCompletions {
15484                trigger: None,
15485                snippets_only: false,
15486            },
15487            window,
15488            cx,
15489        );
15490    });
15491    cx.executor().run_until_parked();
15492    cx.update_editor(|editor, window, cx| {
15493        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15494    });
15495    cx.executor().run_until_parked();
15496    cx.assert_editor_state(
15497        r##"#ifndef BAR_H
15498#define BAR_H
15499
15500#include <stdbool.h>
15501
15502int fn_branch(bool do_branch1, bool do_branch2);
15503
15504#endif // BAR_H
15505#include "AGL/ˇ"##,
15506    );
15507
15508    cx.update_editor(|editor, window, cx| {
15509        editor.handle_input("\"", window, cx);
15510    });
15511    cx.executor().run_until_parked();
15512    cx.assert_editor_state(
15513        r##"#ifndef BAR_H
15514#define BAR_H
15515
15516#include <stdbool.h>
15517
15518int fn_branch(bool do_branch1, bool do_branch2);
15519
15520#endif // BAR_H
15521#include "AGL/"ˇ"##,
15522    );
15523}
15524
15525#[gpui::test]
15526async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15527    init_test(cx, |_| {});
15528
15529    let mut cx = EditorLspTestContext::new_rust(
15530        lsp::ServerCapabilities {
15531            completion_provider: Some(lsp::CompletionOptions {
15532                trigger_characters: Some(vec![".".to_string()]),
15533                resolve_provider: Some(true),
15534                ..Default::default()
15535            }),
15536            ..Default::default()
15537        },
15538        cx,
15539    )
15540    .await;
15541
15542    cx.set_state("fn main() { let a = 2ˇ; }");
15543    cx.simulate_keystroke(".");
15544    let completion_item = lsp::CompletionItem {
15545        label: "Some".into(),
15546        kind: Some(lsp::CompletionItemKind::SNIPPET),
15547        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15548        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15549            kind: lsp::MarkupKind::Markdown,
15550            value: "```rust\nSome(2)\n```".to_string(),
15551        })),
15552        deprecated: Some(false),
15553        sort_text: Some("Some".to_string()),
15554        filter_text: Some("Some".to_string()),
15555        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15556        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15557            range: lsp::Range {
15558                start: lsp::Position {
15559                    line: 0,
15560                    character: 22,
15561                },
15562                end: lsp::Position {
15563                    line: 0,
15564                    character: 22,
15565                },
15566            },
15567            new_text: "Some(2)".to_string(),
15568        })),
15569        additional_text_edits: Some(vec![lsp::TextEdit {
15570            range: lsp::Range {
15571                start: lsp::Position {
15572                    line: 0,
15573                    character: 20,
15574                },
15575                end: lsp::Position {
15576                    line: 0,
15577                    character: 22,
15578                },
15579            },
15580            new_text: "".to_string(),
15581        }]),
15582        ..Default::default()
15583    };
15584
15585    let closure_completion_item = completion_item.clone();
15586    let counter = Arc::new(AtomicUsize::new(0));
15587    let counter_clone = counter.clone();
15588    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15589        let task_completion_item = closure_completion_item.clone();
15590        counter_clone.fetch_add(1, atomic::Ordering::Release);
15591        async move {
15592            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15593                is_incomplete: true,
15594                item_defaults: None,
15595                items: vec![task_completion_item],
15596            })))
15597        }
15598    });
15599
15600    cx.condition(|editor, _| editor.context_menu_visible())
15601        .await;
15602    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15603    assert!(request.next().await.is_some());
15604    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15605
15606    cx.simulate_keystrokes("S o m");
15607    cx.condition(|editor, _| editor.context_menu_visible())
15608        .await;
15609    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15610    assert!(request.next().await.is_some());
15611    assert!(request.next().await.is_some());
15612    assert!(request.next().await.is_some());
15613    request.close();
15614    assert!(request.next().await.is_none());
15615    assert_eq!(
15616        counter.load(atomic::Ordering::Acquire),
15617        4,
15618        "With the completions menu open, only one LSP request should happen per input"
15619    );
15620}
15621
15622#[gpui::test]
15623async fn test_toggle_comment(cx: &mut TestAppContext) {
15624    init_test(cx, |_| {});
15625    let mut cx = EditorTestContext::new(cx).await;
15626    let language = Arc::new(Language::new(
15627        LanguageConfig {
15628            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15629            ..Default::default()
15630        },
15631        Some(tree_sitter_rust::LANGUAGE.into()),
15632    ));
15633    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15634
15635    // If multiple selections intersect a line, the line is only toggled once.
15636    cx.set_state(indoc! {"
15637        fn a() {
15638            «//b();
15639            ˇ»// «c();
15640            //ˇ»  d();
15641        }
15642    "});
15643
15644    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15645
15646    cx.assert_editor_state(indoc! {"
15647        fn a() {
15648            «b();
15649            c();
15650            ˇ» d();
15651        }
15652    "});
15653
15654    // The comment prefix is inserted at the same column for every line in a
15655    // selection.
15656    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15657
15658    cx.assert_editor_state(indoc! {"
15659        fn a() {
15660            // «b();
15661            // c();
15662            ˇ»//  d();
15663        }
15664    "});
15665
15666    // If a selection ends at the beginning of a line, that line is not toggled.
15667    cx.set_selections_state(indoc! {"
15668        fn a() {
15669            // b();
15670            «// c();
15671        ˇ»    //  d();
15672        }
15673    "});
15674
15675    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15676
15677    cx.assert_editor_state(indoc! {"
15678        fn a() {
15679            // b();
15680            «c();
15681        ˇ»    //  d();
15682        }
15683    "});
15684
15685    // If a selection span a single line and is empty, the line is toggled.
15686    cx.set_state(indoc! {"
15687        fn a() {
15688            a();
15689            b();
15690        ˇ
15691        }
15692    "});
15693
15694    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15695
15696    cx.assert_editor_state(indoc! {"
15697        fn a() {
15698            a();
15699            b();
15700        //•ˇ
15701        }
15702    "});
15703
15704    // If a selection span multiple lines, empty lines are not toggled.
15705    cx.set_state(indoc! {"
15706        fn a() {
15707            «a();
15708
15709            c();ˇ»
15710        }
15711    "});
15712
15713    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15714
15715    cx.assert_editor_state(indoc! {"
15716        fn a() {
15717            // «a();
15718
15719            // c();ˇ»
15720        }
15721    "});
15722
15723    // If a selection includes multiple comment prefixes, all lines are uncommented.
15724    cx.set_state(indoc! {"
15725        fn a() {
15726            «// a();
15727            /// b();
15728            //! c();ˇ»
15729        }
15730    "});
15731
15732    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15733
15734    cx.assert_editor_state(indoc! {"
15735        fn a() {
15736            «a();
15737            b();
15738            c();ˇ»
15739        }
15740    "});
15741}
15742
15743#[gpui::test]
15744async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15745    init_test(cx, |_| {});
15746    let mut cx = EditorTestContext::new(cx).await;
15747    let language = Arc::new(Language::new(
15748        LanguageConfig {
15749            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15750            ..Default::default()
15751        },
15752        Some(tree_sitter_rust::LANGUAGE.into()),
15753    ));
15754    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15755
15756    let toggle_comments = &ToggleComments {
15757        advance_downwards: false,
15758        ignore_indent: true,
15759    };
15760
15761    // If multiple selections intersect a line, the line is only toggled once.
15762    cx.set_state(indoc! {"
15763        fn a() {
15764        //    «b();
15765        //    c();
15766        //    ˇ» d();
15767        }
15768    "});
15769
15770    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15771
15772    cx.assert_editor_state(indoc! {"
15773        fn a() {
15774            «b();
15775            c();
15776            ˇ» d();
15777        }
15778    "});
15779
15780    // The comment prefix is inserted at the beginning of each line
15781    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15782
15783    cx.assert_editor_state(indoc! {"
15784        fn a() {
15785        //    «b();
15786        //    c();
15787        //    ˇ» d();
15788        }
15789    "});
15790
15791    // If a selection ends at the beginning of a line, that line is not toggled.
15792    cx.set_selections_state(indoc! {"
15793        fn a() {
15794        //    b();
15795        //    «c();
15796        ˇ»//     d();
15797        }
15798    "});
15799
15800    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15801
15802    cx.assert_editor_state(indoc! {"
15803        fn a() {
15804        //    b();
15805            «c();
15806        ˇ»//     d();
15807        }
15808    "});
15809
15810    // If a selection span a single line and is empty, the line is toggled.
15811    cx.set_state(indoc! {"
15812        fn a() {
15813            a();
15814            b();
15815        ˇ
15816        }
15817    "});
15818
15819    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15820
15821    cx.assert_editor_state(indoc! {"
15822        fn a() {
15823            a();
15824            b();
15825        //ˇ
15826        }
15827    "});
15828
15829    // If a selection span multiple lines, empty lines are not toggled.
15830    cx.set_state(indoc! {"
15831        fn a() {
15832            «a();
15833
15834            c();ˇ»
15835        }
15836    "});
15837
15838    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15839
15840    cx.assert_editor_state(indoc! {"
15841        fn a() {
15842        //    «a();
15843
15844        //    c();ˇ»
15845        }
15846    "});
15847
15848    // If a selection includes multiple comment prefixes, all lines are uncommented.
15849    cx.set_state(indoc! {"
15850        fn a() {
15851        //    «a();
15852        ///    b();
15853        //!    c();ˇ»
15854        }
15855    "});
15856
15857    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15858
15859    cx.assert_editor_state(indoc! {"
15860        fn a() {
15861            «a();
15862            b();
15863            c();ˇ»
15864        }
15865    "});
15866}
15867
15868#[gpui::test]
15869async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15870    init_test(cx, |_| {});
15871
15872    let language = Arc::new(Language::new(
15873        LanguageConfig {
15874            line_comments: vec!["// ".into()],
15875            ..Default::default()
15876        },
15877        Some(tree_sitter_rust::LANGUAGE.into()),
15878    ));
15879
15880    let mut cx = EditorTestContext::new(cx).await;
15881
15882    cx.language_registry().add(language.clone());
15883    cx.update_buffer(|buffer, cx| {
15884        buffer.set_language(Some(language), cx);
15885    });
15886
15887    let toggle_comments = &ToggleComments {
15888        advance_downwards: true,
15889        ignore_indent: false,
15890    };
15891
15892    // Single cursor on one line -> advance
15893    // Cursor moves horizontally 3 characters as well on non-blank line
15894    cx.set_state(indoc!(
15895        "fn a() {
15896             ˇdog();
15897             cat();
15898        }"
15899    ));
15900    cx.update_editor(|editor, window, cx| {
15901        editor.toggle_comments(toggle_comments, window, cx);
15902    });
15903    cx.assert_editor_state(indoc!(
15904        "fn a() {
15905             // dog();
15906             catˇ();
15907        }"
15908    ));
15909
15910    // Single selection on one line -> don't advance
15911    cx.set_state(indoc!(
15912        "fn a() {
15913             «dog()ˇ»;
15914             cat();
15915        }"
15916    ));
15917    cx.update_editor(|editor, window, cx| {
15918        editor.toggle_comments(toggle_comments, window, cx);
15919    });
15920    cx.assert_editor_state(indoc!(
15921        "fn a() {
15922             // «dog()ˇ»;
15923             cat();
15924        }"
15925    ));
15926
15927    // Multiple cursors on one line -> advance
15928    cx.set_state(indoc!(
15929        "fn a() {
15930             ˇdˇog();
15931             cat();
15932        }"
15933    ));
15934    cx.update_editor(|editor, window, cx| {
15935        editor.toggle_comments(toggle_comments, window, cx);
15936    });
15937    cx.assert_editor_state(indoc!(
15938        "fn a() {
15939             // dog();
15940             catˇ(ˇ);
15941        }"
15942    ));
15943
15944    // Multiple cursors on one line, with selection -> don't advance
15945    cx.set_state(indoc!(
15946        "fn a() {
15947             ˇdˇog«()ˇ»;
15948             cat();
15949        }"
15950    ));
15951    cx.update_editor(|editor, window, cx| {
15952        editor.toggle_comments(toggle_comments, window, cx);
15953    });
15954    cx.assert_editor_state(indoc!(
15955        "fn a() {
15956             // ˇdˇog«()ˇ»;
15957             cat();
15958        }"
15959    ));
15960
15961    // Single cursor on one line -> advance
15962    // Cursor moves to column 0 on blank line
15963    cx.set_state(indoc!(
15964        "fn a() {
15965             ˇdog();
15966
15967             cat();
15968        }"
15969    ));
15970    cx.update_editor(|editor, window, cx| {
15971        editor.toggle_comments(toggle_comments, window, cx);
15972    });
15973    cx.assert_editor_state(indoc!(
15974        "fn a() {
15975             // dog();
15976        ˇ
15977             cat();
15978        }"
15979    ));
15980
15981    // Single cursor on one line -> advance
15982    // Cursor starts and ends at column 0
15983    cx.set_state(indoc!(
15984        "fn a() {
15985         ˇ    dog();
15986             cat();
15987        }"
15988    ));
15989    cx.update_editor(|editor, window, cx| {
15990        editor.toggle_comments(toggle_comments, window, cx);
15991    });
15992    cx.assert_editor_state(indoc!(
15993        "fn a() {
15994             // dog();
15995         ˇ    cat();
15996        }"
15997    ));
15998}
15999
16000#[gpui::test]
16001async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16002    init_test(cx, |_| {});
16003
16004    let mut cx = EditorTestContext::new(cx).await;
16005
16006    let html_language = Arc::new(
16007        Language::new(
16008            LanguageConfig {
16009                name: "HTML".into(),
16010                block_comment: Some(BlockCommentConfig {
16011                    start: "<!-- ".into(),
16012                    prefix: "".into(),
16013                    end: " -->".into(),
16014                    tab_size: 0,
16015                }),
16016                ..Default::default()
16017            },
16018            Some(tree_sitter_html::LANGUAGE.into()),
16019        )
16020        .with_injection_query(
16021            r#"
16022            (script_element
16023                (raw_text) @injection.content
16024                (#set! injection.language "javascript"))
16025            "#,
16026        )
16027        .unwrap(),
16028    );
16029
16030    let javascript_language = Arc::new(Language::new(
16031        LanguageConfig {
16032            name: "JavaScript".into(),
16033            line_comments: vec!["// ".into()],
16034            ..Default::default()
16035        },
16036        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16037    ));
16038
16039    cx.language_registry().add(html_language.clone());
16040    cx.language_registry().add(javascript_language);
16041    cx.update_buffer(|buffer, cx| {
16042        buffer.set_language(Some(html_language), cx);
16043    });
16044
16045    // Toggle comments for empty selections
16046    cx.set_state(
16047        &r#"
16048            <p>A</p>ˇ
16049            <p>B</p>ˇ
16050            <p>C</p>ˇ
16051        "#
16052        .unindent(),
16053    );
16054    cx.update_editor(|editor, window, cx| {
16055        editor.toggle_comments(&ToggleComments::default(), window, cx)
16056    });
16057    cx.assert_editor_state(
16058        &r#"
16059            <!-- <p>A</p>ˇ -->
16060            <!-- <p>B</p>ˇ -->
16061            <!-- <p>C</p>ˇ -->
16062        "#
16063        .unindent(),
16064    );
16065    cx.update_editor(|editor, window, cx| {
16066        editor.toggle_comments(&ToggleComments::default(), window, cx)
16067    });
16068    cx.assert_editor_state(
16069        &r#"
16070            <p>A</p>ˇ
16071            <p>B</p>ˇ
16072            <p>C</p>ˇ
16073        "#
16074        .unindent(),
16075    );
16076
16077    // Toggle comments for mixture of empty and non-empty selections, where
16078    // multiple selections occupy a given line.
16079    cx.set_state(
16080        &r#"
16081            <p>A«</p>
16082            <p>ˇ»B</p>ˇ
16083            <p>C«</p>
16084            <p>ˇ»D</p>ˇ
16085        "#
16086        .unindent(),
16087    );
16088
16089    cx.update_editor(|editor, window, cx| {
16090        editor.toggle_comments(&ToggleComments::default(), window, cx)
16091    });
16092    cx.assert_editor_state(
16093        &r#"
16094            <!-- <p>A«</p>
16095            <p>ˇ»B</p>ˇ -->
16096            <!-- <p>C«</p>
16097            <p>ˇ»D</p>ˇ -->
16098        "#
16099        .unindent(),
16100    );
16101    cx.update_editor(|editor, window, cx| {
16102        editor.toggle_comments(&ToggleComments::default(), window, cx)
16103    });
16104    cx.assert_editor_state(
16105        &r#"
16106            <p>A«</p>
16107            <p>ˇ»B</p>ˇ
16108            <p>C«</p>
16109            <p>ˇ»D</p>ˇ
16110        "#
16111        .unindent(),
16112    );
16113
16114    // Toggle comments when different languages are active for different
16115    // selections.
16116    cx.set_state(
16117        &r#"
16118            ˇ<script>
16119                ˇvar x = new Y();
16120            ˇ</script>
16121        "#
16122        .unindent(),
16123    );
16124    cx.executor().run_until_parked();
16125    cx.update_editor(|editor, window, cx| {
16126        editor.toggle_comments(&ToggleComments::default(), window, cx)
16127    });
16128    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16129    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16130    cx.assert_editor_state(
16131        &r#"
16132            <!-- ˇ<script> -->
16133                // ˇvar x = new Y();
16134            <!-- ˇ</script> -->
16135        "#
16136        .unindent(),
16137    );
16138}
16139
16140#[gpui::test]
16141fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16142    init_test(cx, |_| {});
16143
16144    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16145    let multibuffer = cx.new(|cx| {
16146        let mut multibuffer = MultiBuffer::new(ReadWrite);
16147        multibuffer.push_excerpts(
16148            buffer.clone(),
16149            [
16150                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16151                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16152            ],
16153            cx,
16154        );
16155        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16156        multibuffer
16157    });
16158
16159    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16160    editor.update_in(cx, |editor, window, cx| {
16161        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16163            s.select_ranges([
16164                Point::new(0, 0)..Point::new(0, 0),
16165                Point::new(1, 0)..Point::new(1, 0),
16166            ])
16167        });
16168
16169        editor.handle_input("X", window, cx);
16170        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16171        assert_eq!(
16172            editor.selections.ranges(&editor.display_snapshot(cx)),
16173            [
16174                Point::new(0, 1)..Point::new(0, 1),
16175                Point::new(1, 1)..Point::new(1, 1),
16176            ]
16177        );
16178
16179        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16180        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16181            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16182        });
16183        editor.backspace(&Default::default(), window, cx);
16184        assert_eq!(editor.text(cx), "Xa\nbbb");
16185        assert_eq!(
16186            editor.selections.ranges(&editor.display_snapshot(cx)),
16187            [Point::new(1, 0)..Point::new(1, 0)]
16188        );
16189
16190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16191            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16192        });
16193        editor.backspace(&Default::default(), window, cx);
16194        assert_eq!(editor.text(cx), "X\nbb");
16195        assert_eq!(
16196            editor.selections.ranges(&editor.display_snapshot(cx)),
16197            [Point::new(0, 1)..Point::new(0, 1)]
16198        );
16199    });
16200}
16201
16202#[gpui::test]
16203fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16204    init_test(cx, |_| {});
16205
16206    let markers = vec![('[', ']').into(), ('(', ')').into()];
16207    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16208        indoc! {"
16209            [aaaa
16210            (bbbb]
16211            cccc)",
16212        },
16213        markers.clone(),
16214    );
16215    let excerpt_ranges = markers.into_iter().map(|marker| {
16216        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16217        ExcerptRange::new(context)
16218    });
16219    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16220    let multibuffer = cx.new(|cx| {
16221        let mut multibuffer = MultiBuffer::new(ReadWrite);
16222        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16223        multibuffer
16224    });
16225
16226    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16227    editor.update_in(cx, |editor, window, cx| {
16228        let (expected_text, selection_ranges) = marked_text_ranges(
16229            indoc! {"
16230                aaaa
16231                bˇbbb
16232                bˇbbˇb
16233                cccc"
16234            },
16235            true,
16236        );
16237        assert_eq!(editor.text(cx), expected_text);
16238        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16239            s.select_ranges(selection_ranges)
16240        });
16241
16242        editor.handle_input("X", window, cx);
16243
16244        let (expected_text, expected_selections) = marked_text_ranges(
16245            indoc! {"
16246                aaaa
16247                bXˇbbXb
16248                bXˇbbXˇb
16249                cccc"
16250            },
16251            false,
16252        );
16253        assert_eq!(editor.text(cx), expected_text);
16254        assert_eq!(
16255            editor.selections.ranges(&editor.display_snapshot(cx)),
16256            expected_selections
16257        );
16258
16259        editor.newline(&Newline, window, cx);
16260        let (expected_text, expected_selections) = marked_text_ranges(
16261            indoc! {"
16262                aaaa
16263                bX
16264                ˇbbX
16265                b
16266                bX
16267                ˇbbX
16268                ˇb
16269                cccc"
16270            },
16271            false,
16272        );
16273        assert_eq!(editor.text(cx), expected_text);
16274        assert_eq!(
16275            editor.selections.ranges(&editor.display_snapshot(cx)),
16276            expected_selections
16277        );
16278    });
16279}
16280
16281#[gpui::test]
16282fn test_refresh_selections(cx: &mut TestAppContext) {
16283    init_test(cx, |_| {});
16284
16285    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16286    let mut excerpt1_id = None;
16287    let multibuffer = cx.new(|cx| {
16288        let mut multibuffer = MultiBuffer::new(ReadWrite);
16289        excerpt1_id = multibuffer
16290            .push_excerpts(
16291                buffer.clone(),
16292                [
16293                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16294                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16295                ],
16296                cx,
16297            )
16298            .into_iter()
16299            .next();
16300        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16301        multibuffer
16302    });
16303
16304    let editor = cx.add_window(|window, cx| {
16305        let mut editor = build_editor(multibuffer.clone(), window, cx);
16306        let snapshot = editor.snapshot(window, cx);
16307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16308            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16309        });
16310        editor.begin_selection(
16311            Point::new(2, 1).to_display_point(&snapshot),
16312            true,
16313            1,
16314            window,
16315            cx,
16316        );
16317        assert_eq!(
16318            editor.selections.ranges(&editor.display_snapshot(cx)),
16319            [
16320                Point::new(1, 3)..Point::new(1, 3),
16321                Point::new(2, 1)..Point::new(2, 1),
16322            ]
16323        );
16324        editor
16325    });
16326
16327    // Refreshing selections is a no-op when excerpts haven't changed.
16328    _ = editor.update(cx, |editor, window, cx| {
16329        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16330        assert_eq!(
16331            editor.selections.ranges(&editor.display_snapshot(cx)),
16332            [
16333                Point::new(1, 3)..Point::new(1, 3),
16334                Point::new(2, 1)..Point::new(2, 1),
16335            ]
16336        );
16337    });
16338
16339    multibuffer.update(cx, |multibuffer, cx| {
16340        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16341    });
16342    _ = editor.update(cx, |editor, window, cx| {
16343        // Removing an excerpt causes the first selection to become degenerate.
16344        assert_eq!(
16345            editor.selections.ranges(&editor.display_snapshot(cx)),
16346            [
16347                Point::new(0, 0)..Point::new(0, 0),
16348                Point::new(0, 1)..Point::new(0, 1)
16349            ]
16350        );
16351
16352        // Refreshing selections will relocate the first selection to the original buffer
16353        // location.
16354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16355        assert_eq!(
16356            editor.selections.ranges(&editor.display_snapshot(cx)),
16357            [
16358                Point::new(0, 1)..Point::new(0, 1),
16359                Point::new(0, 3)..Point::new(0, 3)
16360            ]
16361        );
16362        assert!(editor.selections.pending_anchor().is_some());
16363    });
16364}
16365
16366#[gpui::test]
16367fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16368    init_test(cx, |_| {});
16369
16370    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16371    let mut excerpt1_id = None;
16372    let multibuffer = cx.new(|cx| {
16373        let mut multibuffer = MultiBuffer::new(ReadWrite);
16374        excerpt1_id = multibuffer
16375            .push_excerpts(
16376                buffer.clone(),
16377                [
16378                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16379                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16380                ],
16381                cx,
16382            )
16383            .into_iter()
16384            .next();
16385        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16386        multibuffer
16387    });
16388
16389    let editor = cx.add_window(|window, cx| {
16390        let mut editor = build_editor(multibuffer.clone(), window, cx);
16391        let snapshot = editor.snapshot(window, cx);
16392        editor.begin_selection(
16393            Point::new(1, 3).to_display_point(&snapshot),
16394            false,
16395            1,
16396            window,
16397            cx,
16398        );
16399        assert_eq!(
16400            editor.selections.ranges(&editor.display_snapshot(cx)),
16401            [Point::new(1, 3)..Point::new(1, 3)]
16402        );
16403        editor
16404    });
16405
16406    multibuffer.update(cx, |multibuffer, cx| {
16407        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16408    });
16409    _ = editor.update(cx, |editor, window, cx| {
16410        assert_eq!(
16411            editor.selections.ranges(&editor.display_snapshot(cx)),
16412            [Point::new(0, 0)..Point::new(0, 0)]
16413        );
16414
16415        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16417        assert_eq!(
16418            editor.selections.ranges(&editor.display_snapshot(cx)),
16419            [Point::new(0, 3)..Point::new(0, 3)]
16420        );
16421        assert!(editor.selections.pending_anchor().is_some());
16422    });
16423}
16424
16425#[gpui::test]
16426async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16427    init_test(cx, |_| {});
16428
16429    let language = Arc::new(
16430        Language::new(
16431            LanguageConfig {
16432                brackets: BracketPairConfig {
16433                    pairs: vec![
16434                        BracketPair {
16435                            start: "{".to_string(),
16436                            end: "}".to_string(),
16437                            close: true,
16438                            surround: true,
16439                            newline: true,
16440                        },
16441                        BracketPair {
16442                            start: "/* ".to_string(),
16443                            end: " */".to_string(),
16444                            close: true,
16445                            surround: true,
16446                            newline: true,
16447                        },
16448                    ],
16449                    ..Default::default()
16450                },
16451                ..Default::default()
16452            },
16453            Some(tree_sitter_rust::LANGUAGE.into()),
16454        )
16455        .with_indents_query("")
16456        .unwrap(),
16457    );
16458
16459    let text = concat!(
16460        "{   }\n",     //
16461        "  x\n",       //
16462        "  /*   */\n", //
16463        "x\n",         //
16464        "{{} }\n",     //
16465    );
16466
16467    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16468    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16469    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16470    editor
16471        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16472        .await;
16473
16474    editor.update_in(cx, |editor, window, cx| {
16475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16476            s.select_display_ranges([
16477                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16478                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16479                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16480            ])
16481        });
16482        editor.newline(&Newline, window, cx);
16483
16484        assert_eq!(
16485            editor.buffer().read(cx).read(cx).text(),
16486            concat!(
16487                "{ \n",    // Suppress rustfmt
16488                "\n",      //
16489                "}\n",     //
16490                "  x\n",   //
16491                "  /* \n", //
16492                "  \n",    //
16493                "  */\n",  //
16494                "x\n",     //
16495                "{{} \n",  //
16496                "}\n",     //
16497            )
16498        );
16499    });
16500}
16501
16502#[gpui::test]
16503fn test_highlighted_ranges(cx: &mut TestAppContext) {
16504    init_test(cx, |_| {});
16505
16506    let editor = cx.add_window(|window, cx| {
16507        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16508        build_editor(buffer, window, cx)
16509    });
16510
16511    _ = editor.update(cx, |editor, window, cx| {
16512        struct Type1;
16513        struct Type2;
16514
16515        let buffer = editor.buffer.read(cx).snapshot(cx);
16516
16517        let anchor_range =
16518            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16519
16520        editor.highlight_background::<Type1>(
16521            &[
16522                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16523                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16524                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16525                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16526            ],
16527            |_| Hsla::red(),
16528            cx,
16529        );
16530        editor.highlight_background::<Type2>(
16531            &[
16532                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16533                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16534                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16535                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16536            ],
16537            |_| Hsla::green(),
16538            cx,
16539        );
16540
16541        let snapshot = editor.snapshot(window, cx);
16542        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16543            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16544            &snapshot,
16545            cx.theme(),
16546        );
16547        assert_eq!(
16548            highlighted_ranges,
16549            &[
16550                (
16551                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16552                    Hsla::green(),
16553                ),
16554                (
16555                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16556                    Hsla::red(),
16557                ),
16558                (
16559                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16560                    Hsla::green(),
16561                ),
16562                (
16563                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16564                    Hsla::red(),
16565                ),
16566            ]
16567        );
16568        assert_eq!(
16569            editor.sorted_background_highlights_in_range(
16570                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16571                &snapshot,
16572                cx.theme(),
16573            ),
16574            &[(
16575                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16576                Hsla::red(),
16577            )]
16578        );
16579    });
16580}
16581
16582#[gpui::test]
16583async fn test_following(cx: &mut TestAppContext) {
16584    init_test(cx, |_| {});
16585
16586    let fs = FakeFs::new(cx.executor());
16587    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16588
16589    let buffer = project.update(cx, |project, cx| {
16590        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16591        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16592    });
16593    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16594    let follower = cx.update(|cx| {
16595        cx.open_window(
16596            WindowOptions {
16597                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16598                    gpui::Point::new(px(0.), px(0.)),
16599                    gpui::Point::new(px(10.), px(80.)),
16600                ))),
16601                ..Default::default()
16602            },
16603            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16604        )
16605        .unwrap()
16606    });
16607
16608    let is_still_following = Rc::new(RefCell::new(true));
16609    let follower_edit_event_count = Rc::new(RefCell::new(0));
16610    let pending_update = Rc::new(RefCell::new(None));
16611    let leader_entity = leader.root(cx).unwrap();
16612    let follower_entity = follower.root(cx).unwrap();
16613    _ = follower.update(cx, {
16614        let update = pending_update.clone();
16615        let is_still_following = is_still_following.clone();
16616        let follower_edit_event_count = follower_edit_event_count.clone();
16617        |_, window, cx| {
16618            cx.subscribe_in(
16619                &leader_entity,
16620                window,
16621                move |_, leader, event, window, cx| {
16622                    leader.read(cx).add_event_to_update_proto(
16623                        event,
16624                        &mut update.borrow_mut(),
16625                        window,
16626                        cx,
16627                    );
16628                },
16629            )
16630            .detach();
16631
16632            cx.subscribe_in(
16633                &follower_entity,
16634                window,
16635                move |_, _, event: &EditorEvent, _window, _cx| {
16636                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16637                        *is_still_following.borrow_mut() = false;
16638                    }
16639
16640                    if let EditorEvent::BufferEdited = event {
16641                        *follower_edit_event_count.borrow_mut() += 1;
16642                    }
16643                },
16644            )
16645            .detach();
16646        }
16647    });
16648
16649    // Update the selections only
16650    _ = leader.update(cx, |leader, window, cx| {
16651        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16652            s.select_ranges([1..1])
16653        });
16654    });
16655    follower
16656        .update(cx, |follower, window, cx| {
16657            follower.apply_update_proto(
16658                &project,
16659                pending_update.borrow_mut().take().unwrap(),
16660                window,
16661                cx,
16662            )
16663        })
16664        .unwrap()
16665        .await
16666        .unwrap();
16667    _ = follower.update(cx, |follower, _, cx| {
16668        assert_eq!(
16669            follower.selections.ranges(&follower.display_snapshot(cx)),
16670            vec![1..1]
16671        );
16672    });
16673    assert!(*is_still_following.borrow());
16674    assert_eq!(*follower_edit_event_count.borrow(), 0);
16675
16676    // Update the scroll position only
16677    _ = leader.update(cx, |leader, window, cx| {
16678        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16679    });
16680    follower
16681        .update(cx, |follower, window, cx| {
16682            follower.apply_update_proto(
16683                &project,
16684                pending_update.borrow_mut().take().unwrap(),
16685                window,
16686                cx,
16687            )
16688        })
16689        .unwrap()
16690        .await
16691        .unwrap();
16692    assert_eq!(
16693        follower
16694            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16695            .unwrap(),
16696        gpui::Point::new(1.5, 3.5)
16697    );
16698    assert!(*is_still_following.borrow());
16699    assert_eq!(*follower_edit_event_count.borrow(), 0);
16700
16701    // Update the selections and scroll position. The follower's scroll position is updated
16702    // via autoscroll, not via the leader's exact scroll position.
16703    _ = leader.update(cx, |leader, window, cx| {
16704        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16705            s.select_ranges([0..0])
16706        });
16707        leader.request_autoscroll(Autoscroll::newest(), cx);
16708        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16709    });
16710    follower
16711        .update(cx, |follower, window, cx| {
16712            follower.apply_update_proto(
16713                &project,
16714                pending_update.borrow_mut().take().unwrap(),
16715                window,
16716                cx,
16717            )
16718        })
16719        .unwrap()
16720        .await
16721        .unwrap();
16722    _ = follower.update(cx, |follower, _, cx| {
16723        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16724        assert_eq!(
16725            follower.selections.ranges(&follower.display_snapshot(cx)),
16726            vec![0..0]
16727        );
16728    });
16729    assert!(*is_still_following.borrow());
16730
16731    // Creating a pending selection that precedes another selection
16732    _ = leader.update(cx, |leader, window, cx| {
16733        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16734            s.select_ranges([1..1])
16735        });
16736        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16737    });
16738    follower
16739        .update(cx, |follower, window, cx| {
16740            follower.apply_update_proto(
16741                &project,
16742                pending_update.borrow_mut().take().unwrap(),
16743                window,
16744                cx,
16745            )
16746        })
16747        .unwrap()
16748        .await
16749        .unwrap();
16750    _ = follower.update(cx, |follower, _, cx| {
16751        assert_eq!(
16752            follower.selections.ranges(&follower.display_snapshot(cx)),
16753            vec![0..0, 1..1]
16754        );
16755    });
16756    assert!(*is_still_following.borrow());
16757
16758    // Extend the pending selection so that it surrounds another selection
16759    _ = leader.update(cx, |leader, window, cx| {
16760        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16761    });
16762    follower
16763        .update(cx, |follower, window, cx| {
16764            follower.apply_update_proto(
16765                &project,
16766                pending_update.borrow_mut().take().unwrap(),
16767                window,
16768                cx,
16769            )
16770        })
16771        .unwrap()
16772        .await
16773        .unwrap();
16774    _ = follower.update(cx, |follower, _, cx| {
16775        assert_eq!(
16776            follower.selections.ranges(&follower.display_snapshot(cx)),
16777            vec![0..2]
16778        );
16779    });
16780
16781    // Scrolling locally breaks the follow
16782    _ = follower.update(cx, |follower, window, cx| {
16783        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16784        follower.set_scroll_anchor(
16785            ScrollAnchor {
16786                anchor: top_anchor,
16787                offset: gpui::Point::new(0.0, 0.5),
16788            },
16789            window,
16790            cx,
16791        );
16792    });
16793    assert!(!(*is_still_following.borrow()));
16794}
16795
16796#[gpui::test]
16797async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16798    init_test(cx, |_| {});
16799
16800    let fs = FakeFs::new(cx.executor());
16801    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16803    let pane = workspace
16804        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16805        .unwrap();
16806
16807    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16808
16809    let leader = pane.update_in(cx, |_, window, cx| {
16810        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16811        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16812    });
16813
16814    // Start following the editor when it has no excerpts.
16815    let mut state_message =
16816        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16817    let workspace_entity = workspace.root(cx).unwrap();
16818    let follower_1 = cx
16819        .update_window(*workspace.deref(), |_, window, cx| {
16820            Editor::from_state_proto(
16821                workspace_entity,
16822                ViewId {
16823                    creator: CollaboratorId::PeerId(PeerId::default()),
16824                    id: 0,
16825                },
16826                &mut state_message,
16827                window,
16828                cx,
16829            )
16830        })
16831        .unwrap()
16832        .unwrap()
16833        .await
16834        .unwrap();
16835
16836    let update_message = Rc::new(RefCell::new(None));
16837    follower_1.update_in(cx, {
16838        let update = update_message.clone();
16839        |_, window, cx| {
16840            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16841                leader.read(cx).add_event_to_update_proto(
16842                    event,
16843                    &mut update.borrow_mut(),
16844                    window,
16845                    cx,
16846                );
16847            })
16848            .detach();
16849        }
16850    });
16851
16852    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16853        (
16854            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16855            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16856        )
16857    });
16858
16859    // Insert some excerpts.
16860    leader.update(cx, |leader, cx| {
16861        leader.buffer.update(cx, |multibuffer, cx| {
16862            multibuffer.set_excerpts_for_path(
16863                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16864                buffer_1.clone(),
16865                vec![
16866                    Point::row_range(0..3),
16867                    Point::row_range(1..6),
16868                    Point::row_range(12..15),
16869                ],
16870                0,
16871                cx,
16872            );
16873            multibuffer.set_excerpts_for_path(
16874                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16875                buffer_2.clone(),
16876                vec![Point::row_range(0..6), Point::row_range(8..12)],
16877                0,
16878                cx,
16879            );
16880        });
16881    });
16882
16883    // Apply the update of adding the excerpts.
16884    follower_1
16885        .update_in(cx, |follower, window, cx| {
16886            follower.apply_update_proto(
16887                &project,
16888                update_message.borrow().clone().unwrap(),
16889                window,
16890                cx,
16891            )
16892        })
16893        .await
16894        .unwrap();
16895    assert_eq!(
16896        follower_1.update(cx, |editor, cx| editor.text(cx)),
16897        leader.update(cx, |editor, cx| editor.text(cx))
16898    );
16899    update_message.borrow_mut().take();
16900
16901    // Start following separately after it already has excerpts.
16902    let mut state_message =
16903        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16904    let workspace_entity = workspace.root(cx).unwrap();
16905    let follower_2 = cx
16906        .update_window(*workspace.deref(), |_, window, cx| {
16907            Editor::from_state_proto(
16908                workspace_entity,
16909                ViewId {
16910                    creator: CollaboratorId::PeerId(PeerId::default()),
16911                    id: 0,
16912                },
16913                &mut state_message,
16914                window,
16915                cx,
16916            )
16917        })
16918        .unwrap()
16919        .unwrap()
16920        .await
16921        .unwrap();
16922    assert_eq!(
16923        follower_2.update(cx, |editor, cx| editor.text(cx)),
16924        leader.update(cx, |editor, cx| editor.text(cx))
16925    );
16926
16927    // Remove some excerpts.
16928    leader.update(cx, |leader, cx| {
16929        leader.buffer.update(cx, |multibuffer, cx| {
16930            let excerpt_ids = multibuffer.excerpt_ids();
16931            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16932            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16933        });
16934    });
16935
16936    // Apply the update of removing the excerpts.
16937    follower_1
16938        .update_in(cx, |follower, window, cx| {
16939            follower.apply_update_proto(
16940                &project,
16941                update_message.borrow().clone().unwrap(),
16942                window,
16943                cx,
16944            )
16945        })
16946        .await
16947        .unwrap();
16948    follower_2
16949        .update_in(cx, |follower, window, cx| {
16950            follower.apply_update_proto(
16951                &project,
16952                update_message.borrow().clone().unwrap(),
16953                window,
16954                cx,
16955            )
16956        })
16957        .await
16958        .unwrap();
16959    update_message.borrow_mut().take();
16960    assert_eq!(
16961        follower_1.update(cx, |editor, cx| editor.text(cx)),
16962        leader.update(cx, |editor, cx| editor.text(cx))
16963    );
16964}
16965
16966#[gpui::test]
16967async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16968    init_test(cx, |_| {});
16969
16970    let mut cx = EditorTestContext::new(cx).await;
16971    let lsp_store =
16972        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16973
16974    cx.set_state(indoc! {"
16975        ˇfn func(abc def: i32) -> u32 {
16976        }
16977    "});
16978
16979    cx.update(|_, cx| {
16980        lsp_store.update(cx, |lsp_store, cx| {
16981            lsp_store
16982                .update_diagnostics(
16983                    LanguageServerId(0),
16984                    lsp::PublishDiagnosticsParams {
16985                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16986                        version: None,
16987                        diagnostics: vec![
16988                            lsp::Diagnostic {
16989                                range: lsp::Range::new(
16990                                    lsp::Position::new(0, 11),
16991                                    lsp::Position::new(0, 12),
16992                                ),
16993                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16994                                ..Default::default()
16995                            },
16996                            lsp::Diagnostic {
16997                                range: lsp::Range::new(
16998                                    lsp::Position::new(0, 12),
16999                                    lsp::Position::new(0, 15),
17000                                ),
17001                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17002                                ..Default::default()
17003                            },
17004                            lsp::Diagnostic {
17005                                range: lsp::Range::new(
17006                                    lsp::Position::new(0, 25),
17007                                    lsp::Position::new(0, 28),
17008                                ),
17009                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17010                                ..Default::default()
17011                            },
17012                        ],
17013                    },
17014                    None,
17015                    DiagnosticSourceKind::Pushed,
17016                    &[],
17017                    cx,
17018                )
17019                .unwrap()
17020        });
17021    });
17022
17023    executor.run_until_parked();
17024
17025    cx.update_editor(|editor, window, cx| {
17026        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17027    });
17028
17029    cx.assert_editor_state(indoc! {"
17030        fn func(abc def: i32) -> ˇu32 {
17031        }
17032    "});
17033
17034    cx.update_editor(|editor, window, cx| {
17035        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17036    });
17037
17038    cx.assert_editor_state(indoc! {"
17039        fn func(abc ˇdef: i32) -> u32 {
17040        }
17041    "});
17042
17043    cx.update_editor(|editor, window, cx| {
17044        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17045    });
17046
17047    cx.assert_editor_state(indoc! {"
17048        fn func(abcˇ def: i32) -> u32 {
17049        }
17050    "});
17051
17052    cx.update_editor(|editor, window, cx| {
17053        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17054    });
17055
17056    cx.assert_editor_state(indoc! {"
17057        fn func(abc def: i32) -> ˇu32 {
17058        }
17059    "});
17060}
17061
17062#[gpui::test]
17063async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17064    init_test(cx, |_| {});
17065
17066    let mut cx = EditorTestContext::new(cx).await;
17067
17068    let diff_base = r#"
17069        use some::mod;
17070
17071        const A: u32 = 42;
17072
17073        fn main() {
17074            println!("hello");
17075
17076            println!("world");
17077        }
17078        "#
17079    .unindent();
17080
17081    // Edits are modified, removed, modified, added
17082    cx.set_state(
17083        &r#"
17084        use some::modified;
17085
17086        ˇ
17087        fn main() {
17088            println!("hello there");
17089
17090            println!("around the");
17091            println!("world");
17092        }
17093        "#
17094        .unindent(),
17095    );
17096
17097    cx.set_head_text(&diff_base);
17098    executor.run_until_parked();
17099
17100    cx.update_editor(|editor, window, cx| {
17101        //Wrap around the bottom of the buffer
17102        for _ in 0..3 {
17103            editor.go_to_next_hunk(&GoToHunk, window, cx);
17104        }
17105    });
17106
17107    cx.assert_editor_state(
17108        &r#"
17109        ˇuse some::modified;
17110
17111
17112        fn main() {
17113            println!("hello there");
17114
17115            println!("around the");
17116            println!("world");
17117        }
17118        "#
17119        .unindent(),
17120    );
17121
17122    cx.update_editor(|editor, window, cx| {
17123        //Wrap around the top of the buffer
17124        for _ in 0..2 {
17125            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17126        }
17127    });
17128
17129    cx.assert_editor_state(
17130        &r#"
17131        use some::modified;
17132
17133
17134        fn main() {
17135        ˇ    println!("hello there");
17136
17137            println!("around the");
17138            println!("world");
17139        }
17140        "#
17141        .unindent(),
17142    );
17143
17144    cx.update_editor(|editor, window, cx| {
17145        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17146    });
17147
17148    cx.assert_editor_state(
17149        &r#"
17150        use some::modified;
17151
17152        ˇ
17153        fn main() {
17154            println!("hello there");
17155
17156            println!("around the");
17157            println!("world");
17158        }
17159        "#
17160        .unindent(),
17161    );
17162
17163    cx.update_editor(|editor, window, cx| {
17164        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17165    });
17166
17167    cx.assert_editor_state(
17168        &r#"
17169        ˇuse some::modified;
17170
17171
17172        fn main() {
17173            println!("hello there");
17174
17175            println!("around the");
17176            println!("world");
17177        }
17178        "#
17179        .unindent(),
17180    );
17181
17182    cx.update_editor(|editor, window, cx| {
17183        for _ in 0..2 {
17184            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17185        }
17186    });
17187
17188    cx.assert_editor_state(
17189        &r#"
17190        use some::modified;
17191
17192
17193        fn main() {
17194        ˇ    println!("hello there");
17195
17196            println!("around the");
17197            println!("world");
17198        }
17199        "#
17200        .unindent(),
17201    );
17202
17203    cx.update_editor(|editor, window, cx| {
17204        editor.fold(&Fold, window, cx);
17205    });
17206
17207    cx.update_editor(|editor, window, cx| {
17208        editor.go_to_next_hunk(&GoToHunk, window, cx);
17209    });
17210
17211    cx.assert_editor_state(
17212        &r#"
17213        ˇuse some::modified;
17214
17215
17216        fn main() {
17217            println!("hello there");
17218
17219            println!("around the");
17220            println!("world");
17221        }
17222        "#
17223        .unindent(),
17224    );
17225}
17226
17227#[test]
17228fn test_split_words() {
17229    fn split(text: &str) -> Vec<&str> {
17230        split_words(text).collect()
17231    }
17232
17233    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17234    assert_eq!(split("hello_world"), &["hello_", "world"]);
17235    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17236    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17237    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17238    assert_eq!(split("helloworld"), &["helloworld"]);
17239
17240    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17241}
17242
17243#[test]
17244fn test_split_words_for_snippet_prefix() {
17245    fn split(text: &str) -> Vec<&str> {
17246        snippet_candidate_suffixes(text).collect()
17247    }
17248
17249    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17250    assert_eq!(split("hello_world"), &["hello_world"]);
17251    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17252    assert_eq!(split("Hello_World"), &["Hello_World"]);
17253    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17254    assert_eq!(split("helloworld"), &["helloworld"]);
17255    assert_eq!(
17256        split("this@is!@#$^many   . symbols"),
17257        &[
17258            "this@is!@#$^many   . symbols",
17259            "@is!@#$^many   . symbols",
17260            "is!@#$^many   . symbols",
17261            "!@#$^many   . symbols",
17262            "@#$^many   . symbols",
17263            "#$^many   . symbols",
17264            "$^many   . symbols",
17265            "^many   . symbols",
17266            "many   . symbols",
17267            "   . symbols",
17268            "  . symbols",
17269            " . symbols",
17270            ". symbols",
17271            " symbols",
17272            "symbols"
17273        ],
17274    );
17275}
17276
17277#[gpui::test]
17278async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17279    init_test(cx, |_| {});
17280
17281    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17282    let mut assert = |before, after| {
17283        let _state_context = cx.set_state(before);
17284        cx.run_until_parked();
17285        cx.update_editor(|editor, window, cx| {
17286            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17287        });
17288        cx.run_until_parked();
17289        cx.assert_editor_state(after);
17290    };
17291
17292    // Outside bracket jumps to outside of matching bracket
17293    assert("console.logˇ(var);", "console.log(var)ˇ;");
17294    assert("console.log(var)ˇ;", "console.logˇ(var);");
17295
17296    // Inside bracket jumps to inside of matching bracket
17297    assert("console.log(ˇvar);", "console.log(varˇ);");
17298    assert("console.log(varˇ);", "console.log(ˇvar);");
17299
17300    // When outside a bracket and inside, favor jumping to the inside bracket
17301    assert(
17302        "console.log('foo', [1, 2, 3]ˇ);",
17303        "console.log(ˇ'foo', [1, 2, 3]);",
17304    );
17305    assert(
17306        "console.log(ˇ'foo', [1, 2, 3]);",
17307        "console.log('foo', [1, 2, 3]ˇ);",
17308    );
17309
17310    // Bias forward if two options are equally likely
17311    assert(
17312        "let result = curried_fun()ˇ();",
17313        "let result = curried_fun()()ˇ;",
17314    );
17315
17316    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17317    assert(
17318        indoc! {"
17319            function test() {
17320                console.log('test')ˇ
17321            }"},
17322        indoc! {"
17323            function test() {
17324                console.logˇ('test')
17325            }"},
17326    );
17327}
17328
17329#[gpui::test]
17330async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17331    init_test(cx, |_| {});
17332
17333    let fs = FakeFs::new(cx.executor());
17334    fs.insert_tree(
17335        path!("/a"),
17336        json!({
17337            "main.rs": "fn main() { let a = 5; }",
17338            "other.rs": "// Test file",
17339        }),
17340    )
17341    .await;
17342    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17343
17344    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17345    language_registry.add(Arc::new(Language::new(
17346        LanguageConfig {
17347            name: "Rust".into(),
17348            matcher: LanguageMatcher {
17349                path_suffixes: vec!["rs".to_string()],
17350                ..Default::default()
17351            },
17352            brackets: BracketPairConfig {
17353                pairs: vec![BracketPair {
17354                    start: "{".to_string(),
17355                    end: "}".to_string(),
17356                    close: true,
17357                    surround: true,
17358                    newline: true,
17359                }],
17360                disabled_scopes_by_bracket_ix: Vec::new(),
17361            },
17362            ..Default::default()
17363        },
17364        Some(tree_sitter_rust::LANGUAGE.into()),
17365    )));
17366    let mut fake_servers = language_registry.register_fake_lsp(
17367        "Rust",
17368        FakeLspAdapter {
17369            capabilities: lsp::ServerCapabilities {
17370                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17371                    first_trigger_character: "{".to_string(),
17372                    more_trigger_character: None,
17373                }),
17374                ..Default::default()
17375            },
17376            ..Default::default()
17377        },
17378    );
17379
17380    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17381
17382    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17383
17384    let worktree_id = workspace
17385        .update(cx, |workspace, _, cx| {
17386            workspace.project().update(cx, |project, cx| {
17387                project.worktrees(cx).next().unwrap().read(cx).id()
17388            })
17389        })
17390        .unwrap();
17391
17392    let buffer = project
17393        .update(cx, |project, cx| {
17394            project.open_local_buffer(path!("/a/main.rs"), cx)
17395        })
17396        .await
17397        .unwrap();
17398    let editor_handle = workspace
17399        .update(cx, |workspace, window, cx| {
17400            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17401        })
17402        .unwrap()
17403        .await
17404        .unwrap()
17405        .downcast::<Editor>()
17406        .unwrap();
17407
17408    cx.executor().start_waiting();
17409    let fake_server = fake_servers.next().await.unwrap();
17410
17411    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17412        |params, _| async move {
17413            assert_eq!(
17414                params.text_document_position.text_document.uri,
17415                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17416            );
17417            assert_eq!(
17418                params.text_document_position.position,
17419                lsp::Position::new(0, 21),
17420            );
17421
17422            Ok(Some(vec![lsp::TextEdit {
17423                new_text: "]".to_string(),
17424                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17425            }]))
17426        },
17427    );
17428
17429    editor_handle.update_in(cx, |editor, window, cx| {
17430        window.focus(&editor.focus_handle(cx));
17431        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17432            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17433        });
17434        editor.handle_input("{", window, cx);
17435    });
17436
17437    cx.executor().run_until_parked();
17438
17439    buffer.update(cx, |buffer, _| {
17440        assert_eq!(
17441            buffer.text(),
17442            "fn main() { let a = {5}; }",
17443            "No extra braces from on type formatting should appear in the buffer"
17444        )
17445    });
17446}
17447
17448#[gpui::test(iterations = 20, seeds(31))]
17449async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17450    init_test(cx, |_| {});
17451
17452    let mut cx = EditorLspTestContext::new_rust(
17453        lsp::ServerCapabilities {
17454            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17455                first_trigger_character: ".".to_string(),
17456                more_trigger_character: None,
17457            }),
17458            ..Default::default()
17459        },
17460        cx,
17461    )
17462    .await;
17463
17464    cx.update_buffer(|buffer, _| {
17465        // This causes autoindent to be async.
17466        buffer.set_sync_parse_timeout(Duration::ZERO)
17467    });
17468
17469    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17470    cx.simulate_keystroke("\n");
17471    cx.run_until_parked();
17472
17473    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17474    let mut request =
17475        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17476            let buffer_cloned = buffer_cloned.clone();
17477            async move {
17478                buffer_cloned.update(&mut cx, |buffer, _| {
17479                    assert_eq!(
17480                        buffer.text(),
17481                        "fn c() {\n    d()\n        .\n}\n",
17482                        "OnTypeFormatting should triggered after autoindent applied"
17483                    )
17484                })?;
17485
17486                Ok(Some(vec![]))
17487            }
17488        });
17489
17490    cx.simulate_keystroke(".");
17491    cx.run_until_parked();
17492
17493    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17494    assert!(request.next().await.is_some());
17495    request.close();
17496    assert!(request.next().await.is_none());
17497}
17498
17499#[gpui::test]
17500async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17501    init_test(cx, |_| {});
17502
17503    let fs = FakeFs::new(cx.executor());
17504    fs.insert_tree(
17505        path!("/a"),
17506        json!({
17507            "main.rs": "fn main() { let a = 5; }",
17508            "other.rs": "// Test file",
17509        }),
17510    )
17511    .await;
17512
17513    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17514
17515    let server_restarts = Arc::new(AtomicUsize::new(0));
17516    let closure_restarts = Arc::clone(&server_restarts);
17517    let language_server_name = "test language server";
17518    let language_name: LanguageName = "Rust".into();
17519
17520    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17521    language_registry.add(Arc::new(Language::new(
17522        LanguageConfig {
17523            name: language_name.clone(),
17524            matcher: LanguageMatcher {
17525                path_suffixes: vec!["rs".to_string()],
17526                ..Default::default()
17527            },
17528            ..Default::default()
17529        },
17530        Some(tree_sitter_rust::LANGUAGE.into()),
17531    )));
17532    let mut fake_servers = language_registry.register_fake_lsp(
17533        "Rust",
17534        FakeLspAdapter {
17535            name: language_server_name,
17536            initialization_options: Some(json!({
17537                "testOptionValue": true
17538            })),
17539            initializer: Some(Box::new(move |fake_server| {
17540                let task_restarts = Arc::clone(&closure_restarts);
17541                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17542                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17543                    futures::future::ready(Ok(()))
17544                });
17545            })),
17546            ..Default::default()
17547        },
17548    );
17549
17550    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17551    let _buffer = project
17552        .update(cx, |project, cx| {
17553            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17554        })
17555        .await
17556        .unwrap();
17557    let _fake_server = fake_servers.next().await.unwrap();
17558    update_test_language_settings(cx, |language_settings| {
17559        language_settings.languages.0.insert(
17560            language_name.clone().0,
17561            LanguageSettingsContent {
17562                tab_size: NonZeroU32::new(8),
17563                ..Default::default()
17564            },
17565        );
17566    });
17567    cx.executor().run_until_parked();
17568    assert_eq!(
17569        server_restarts.load(atomic::Ordering::Acquire),
17570        0,
17571        "Should not restart LSP server on an unrelated change"
17572    );
17573
17574    update_test_project_settings(cx, |project_settings| {
17575        project_settings.lsp.insert(
17576            "Some other server name".into(),
17577            LspSettings {
17578                binary: None,
17579                settings: None,
17580                initialization_options: Some(json!({
17581                    "some other init value": false
17582                })),
17583                enable_lsp_tasks: false,
17584                fetch: None,
17585            },
17586        );
17587    });
17588    cx.executor().run_until_parked();
17589    assert_eq!(
17590        server_restarts.load(atomic::Ordering::Acquire),
17591        0,
17592        "Should not restart LSP server on an unrelated LSP settings change"
17593    );
17594
17595    update_test_project_settings(cx, |project_settings| {
17596        project_settings.lsp.insert(
17597            language_server_name.into(),
17598            LspSettings {
17599                binary: None,
17600                settings: None,
17601                initialization_options: Some(json!({
17602                    "anotherInitValue": false
17603                })),
17604                enable_lsp_tasks: false,
17605                fetch: None,
17606            },
17607        );
17608    });
17609    cx.executor().run_until_parked();
17610    assert_eq!(
17611        server_restarts.load(atomic::Ordering::Acquire),
17612        1,
17613        "Should restart LSP server on a related LSP settings change"
17614    );
17615
17616    update_test_project_settings(cx, |project_settings| {
17617        project_settings.lsp.insert(
17618            language_server_name.into(),
17619            LspSettings {
17620                binary: None,
17621                settings: None,
17622                initialization_options: Some(json!({
17623                    "anotherInitValue": false
17624                })),
17625                enable_lsp_tasks: false,
17626                fetch: None,
17627            },
17628        );
17629    });
17630    cx.executor().run_until_parked();
17631    assert_eq!(
17632        server_restarts.load(atomic::Ordering::Acquire),
17633        1,
17634        "Should not restart LSP server on a related LSP settings change that is the same"
17635    );
17636
17637    update_test_project_settings(cx, |project_settings| {
17638        project_settings.lsp.insert(
17639            language_server_name.into(),
17640            LspSettings {
17641                binary: None,
17642                settings: None,
17643                initialization_options: None,
17644                enable_lsp_tasks: false,
17645                fetch: None,
17646            },
17647        );
17648    });
17649    cx.executor().run_until_parked();
17650    assert_eq!(
17651        server_restarts.load(atomic::Ordering::Acquire),
17652        2,
17653        "Should restart LSP server on another related LSP settings change"
17654    );
17655}
17656
17657#[gpui::test]
17658async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17659    init_test(cx, |_| {});
17660
17661    let mut cx = EditorLspTestContext::new_rust(
17662        lsp::ServerCapabilities {
17663            completion_provider: Some(lsp::CompletionOptions {
17664                trigger_characters: Some(vec![".".to_string()]),
17665                resolve_provider: Some(true),
17666                ..Default::default()
17667            }),
17668            ..Default::default()
17669        },
17670        cx,
17671    )
17672    .await;
17673
17674    cx.set_state("fn main() { let a = 2ˇ; }");
17675    cx.simulate_keystroke(".");
17676    let completion_item = lsp::CompletionItem {
17677        label: "some".into(),
17678        kind: Some(lsp::CompletionItemKind::SNIPPET),
17679        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17680        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17681            kind: lsp::MarkupKind::Markdown,
17682            value: "```rust\nSome(2)\n```".to_string(),
17683        })),
17684        deprecated: Some(false),
17685        sort_text: Some("fffffff2".to_string()),
17686        filter_text: Some("some".to_string()),
17687        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17688        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17689            range: lsp::Range {
17690                start: lsp::Position {
17691                    line: 0,
17692                    character: 22,
17693                },
17694                end: lsp::Position {
17695                    line: 0,
17696                    character: 22,
17697                },
17698            },
17699            new_text: "Some(2)".to_string(),
17700        })),
17701        additional_text_edits: Some(vec![lsp::TextEdit {
17702            range: lsp::Range {
17703                start: lsp::Position {
17704                    line: 0,
17705                    character: 20,
17706                },
17707                end: lsp::Position {
17708                    line: 0,
17709                    character: 22,
17710                },
17711            },
17712            new_text: "".to_string(),
17713        }]),
17714        ..Default::default()
17715    };
17716
17717    let closure_completion_item = completion_item.clone();
17718    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17719        let task_completion_item = closure_completion_item.clone();
17720        async move {
17721            Ok(Some(lsp::CompletionResponse::Array(vec![
17722                task_completion_item,
17723            ])))
17724        }
17725    });
17726
17727    request.next().await;
17728
17729    cx.condition(|editor, _| editor.context_menu_visible())
17730        .await;
17731    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17732        editor
17733            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17734            .unwrap()
17735    });
17736    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17737
17738    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17739        let task_completion_item = completion_item.clone();
17740        async move { Ok(task_completion_item) }
17741    })
17742    .next()
17743    .await
17744    .unwrap();
17745    apply_additional_edits.await.unwrap();
17746    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17747}
17748
17749#[gpui::test]
17750async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17751    init_test(cx, |_| {});
17752
17753    let mut cx = EditorLspTestContext::new_rust(
17754        lsp::ServerCapabilities {
17755            completion_provider: Some(lsp::CompletionOptions {
17756                trigger_characters: Some(vec![".".to_string()]),
17757                resolve_provider: Some(true),
17758                ..Default::default()
17759            }),
17760            ..Default::default()
17761        },
17762        cx,
17763    )
17764    .await;
17765
17766    cx.set_state("fn main() { let a = 2ˇ; }");
17767    cx.simulate_keystroke(".");
17768
17769    let item1 = lsp::CompletionItem {
17770        label: "method id()".to_string(),
17771        filter_text: Some("id".to_string()),
17772        detail: None,
17773        documentation: None,
17774        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17775            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17776            new_text: ".id".to_string(),
17777        })),
17778        ..lsp::CompletionItem::default()
17779    };
17780
17781    let item2 = lsp::CompletionItem {
17782        label: "other".to_string(),
17783        filter_text: Some("other".to_string()),
17784        detail: None,
17785        documentation: None,
17786        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17787            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17788            new_text: ".other".to_string(),
17789        })),
17790        ..lsp::CompletionItem::default()
17791    };
17792
17793    let item1 = item1.clone();
17794    cx.set_request_handler::<lsp::request::Completion, _, _>({
17795        let item1 = item1.clone();
17796        move |_, _, _| {
17797            let item1 = item1.clone();
17798            let item2 = item2.clone();
17799            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17800        }
17801    })
17802    .next()
17803    .await;
17804
17805    cx.condition(|editor, _| editor.context_menu_visible())
17806        .await;
17807    cx.update_editor(|editor, _, _| {
17808        let context_menu = editor.context_menu.borrow_mut();
17809        let context_menu = context_menu
17810            .as_ref()
17811            .expect("Should have the context menu deployed");
17812        match context_menu {
17813            CodeContextMenu::Completions(completions_menu) => {
17814                let completions = completions_menu.completions.borrow_mut();
17815                assert_eq!(
17816                    completions
17817                        .iter()
17818                        .map(|completion| &completion.label.text)
17819                        .collect::<Vec<_>>(),
17820                    vec!["method id()", "other"]
17821                )
17822            }
17823            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17824        }
17825    });
17826
17827    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17828        let item1 = item1.clone();
17829        move |_, item_to_resolve, _| {
17830            let item1 = item1.clone();
17831            async move {
17832                if item1 == item_to_resolve {
17833                    Ok(lsp::CompletionItem {
17834                        label: "method id()".to_string(),
17835                        filter_text: Some("id".to_string()),
17836                        detail: Some("Now resolved!".to_string()),
17837                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17838                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17839                            range: lsp::Range::new(
17840                                lsp::Position::new(0, 22),
17841                                lsp::Position::new(0, 22),
17842                            ),
17843                            new_text: ".id".to_string(),
17844                        })),
17845                        ..lsp::CompletionItem::default()
17846                    })
17847                } else {
17848                    Ok(item_to_resolve)
17849                }
17850            }
17851        }
17852    })
17853    .next()
17854    .await
17855    .unwrap();
17856    cx.run_until_parked();
17857
17858    cx.update_editor(|editor, window, cx| {
17859        editor.context_menu_next(&Default::default(), window, cx);
17860    });
17861
17862    cx.update_editor(|editor, _, _| {
17863        let context_menu = editor.context_menu.borrow_mut();
17864        let context_menu = context_menu
17865            .as_ref()
17866            .expect("Should have the context menu deployed");
17867        match context_menu {
17868            CodeContextMenu::Completions(completions_menu) => {
17869                let completions = completions_menu.completions.borrow_mut();
17870                assert_eq!(
17871                    completions
17872                        .iter()
17873                        .map(|completion| &completion.label.text)
17874                        .collect::<Vec<_>>(),
17875                    vec!["method id() Now resolved!", "other"],
17876                    "Should update first completion label, but not second as the filter text did not match."
17877                );
17878            }
17879            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17880        }
17881    });
17882}
17883
17884#[gpui::test]
17885async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17886    init_test(cx, |_| {});
17887    let mut cx = EditorLspTestContext::new_rust(
17888        lsp::ServerCapabilities {
17889            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17890            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17891            completion_provider: Some(lsp::CompletionOptions {
17892                resolve_provider: Some(true),
17893                ..Default::default()
17894            }),
17895            ..Default::default()
17896        },
17897        cx,
17898    )
17899    .await;
17900    cx.set_state(indoc! {"
17901        struct TestStruct {
17902            field: i32
17903        }
17904
17905        fn mainˇ() {
17906            let unused_var = 42;
17907            let test_struct = TestStruct { field: 42 };
17908        }
17909    "});
17910    let symbol_range = cx.lsp_range(indoc! {"
17911        struct TestStruct {
17912            field: i32
17913        }
17914
17915        «fn main»() {
17916            let unused_var = 42;
17917            let test_struct = TestStruct { field: 42 };
17918        }
17919    "});
17920    let mut hover_requests =
17921        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17922            Ok(Some(lsp::Hover {
17923                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17924                    kind: lsp::MarkupKind::Markdown,
17925                    value: "Function documentation".to_string(),
17926                }),
17927                range: Some(symbol_range),
17928            }))
17929        });
17930
17931    // Case 1: Test that code action menu hide hover popover
17932    cx.dispatch_action(Hover);
17933    hover_requests.next().await;
17934    cx.condition(|editor, _| editor.hover_state.visible()).await;
17935    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17936        move |_, _, _| async move {
17937            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17938                lsp::CodeAction {
17939                    title: "Remove unused variable".to_string(),
17940                    kind: Some(CodeActionKind::QUICKFIX),
17941                    edit: Some(lsp::WorkspaceEdit {
17942                        changes: Some(
17943                            [(
17944                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17945                                vec![lsp::TextEdit {
17946                                    range: lsp::Range::new(
17947                                        lsp::Position::new(5, 4),
17948                                        lsp::Position::new(5, 27),
17949                                    ),
17950                                    new_text: "".to_string(),
17951                                }],
17952                            )]
17953                            .into_iter()
17954                            .collect(),
17955                        ),
17956                        ..Default::default()
17957                    }),
17958                    ..Default::default()
17959                },
17960            )]))
17961        },
17962    );
17963    cx.update_editor(|editor, window, cx| {
17964        editor.toggle_code_actions(
17965            &ToggleCodeActions {
17966                deployed_from: None,
17967                quick_launch: false,
17968            },
17969            window,
17970            cx,
17971        );
17972    });
17973    code_action_requests.next().await;
17974    cx.run_until_parked();
17975    cx.condition(|editor, _| editor.context_menu_visible())
17976        .await;
17977    cx.update_editor(|editor, _, _| {
17978        assert!(
17979            !editor.hover_state.visible(),
17980            "Hover popover should be hidden when code action menu is shown"
17981        );
17982        // Hide code actions
17983        editor.context_menu.take();
17984    });
17985
17986    // Case 2: Test that code completions hide hover popover
17987    cx.dispatch_action(Hover);
17988    hover_requests.next().await;
17989    cx.condition(|editor, _| editor.hover_state.visible()).await;
17990    let counter = Arc::new(AtomicUsize::new(0));
17991    let mut completion_requests =
17992        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17993            let counter = counter.clone();
17994            async move {
17995                counter.fetch_add(1, atomic::Ordering::Release);
17996                Ok(Some(lsp::CompletionResponse::Array(vec![
17997                    lsp::CompletionItem {
17998                        label: "main".into(),
17999                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18000                        detail: Some("() -> ()".to_string()),
18001                        ..Default::default()
18002                    },
18003                    lsp::CompletionItem {
18004                        label: "TestStruct".into(),
18005                        kind: Some(lsp::CompletionItemKind::STRUCT),
18006                        detail: Some("struct TestStruct".to_string()),
18007                        ..Default::default()
18008                    },
18009                ])))
18010            }
18011        });
18012    cx.update_editor(|editor, window, cx| {
18013        editor.show_completions(
18014            &ShowCompletions {
18015                trigger: None,
18016                snippets_only: false,
18017            },
18018            window,
18019            cx,
18020        );
18021    });
18022    completion_requests.next().await;
18023    cx.condition(|editor, _| editor.context_menu_visible())
18024        .await;
18025    cx.update_editor(|editor, _, _| {
18026        assert!(
18027            !editor.hover_state.visible(),
18028            "Hover popover should be hidden when completion menu is shown"
18029        );
18030    });
18031}
18032
18033#[gpui::test]
18034async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18035    init_test(cx, |_| {});
18036
18037    let mut cx = EditorLspTestContext::new_rust(
18038        lsp::ServerCapabilities {
18039            completion_provider: Some(lsp::CompletionOptions {
18040                trigger_characters: Some(vec![".".to_string()]),
18041                resolve_provider: Some(true),
18042                ..Default::default()
18043            }),
18044            ..Default::default()
18045        },
18046        cx,
18047    )
18048    .await;
18049
18050    cx.set_state("fn main() { let a = 2ˇ; }");
18051    cx.simulate_keystroke(".");
18052
18053    let unresolved_item_1 = lsp::CompletionItem {
18054        label: "id".to_string(),
18055        filter_text: Some("id".to_string()),
18056        detail: None,
18057        documentation: None,
18058        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18059            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18060            new_text: ".id".to_string(),
18061        })),
18062        ..lsp::CompletionItem::default()
18063    };
18064    let resolved_item_1 = lsp::CompletionItem {
18065        additional_text_edits: Some(vec![lsp::TextEdit {
18066            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18067            new_text: "!!".to_string(),
18068        }]),
18069        ..unresolved_item_1.clone()
18070    };
18071    let unresolved_item_2 = lsp::CompletionItem {
18072        label: "other".to_string(),
18073        filter_text: Some("other".to_string()),
18074        detail: None,
18075        documentation: None,
18076        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18077            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18078            new_text: ".other".to_string(),
18079        })),
18080        ..lsp::CompletionItem::default()
18081    };
18082    let resolved_item_2 = lsp::CompletionItem {
18083        additional_text_edits: Some(vec![lsp::TextEdit {
18084            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18085            new_text: "??".to_string(),
18086        }]),
18087        ..unresolved_item_2.clone()
18088    };
18089
18090    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18091    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18092    cx.lsp
18093        .server
18094        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18095            let unresolved_item_1 = unresolved_item_1.clone();
18096            let resolved_item_1 = resolved_item_1.clone();
18097            let unresolved_item_2 = unresolved_item_2.clone();
18098            let resolved_item_2 = resolved_item_2.clone();
18099            let resolve_requests_1 = resolve_requests_1.clone();
18100            let resolve_requests_2 = resolve_requests_2.clone();
18101            move |unresolved_request, _| {
18102                let unresolved_item_1 = unresolved_item_1.clone();
18103                let resolved_item_1 = resolved_item_1.clone();
18104                let unresolved_item_2 = unresolved_item_2.clone();
18105                let resolved_item_2 = resolved_item_2.clone();
18106                let resolve_requests_1 = resolve_requests_1.clone();
18107                let resolve_requests_2 = resolve_requests_2.clone();
18108                async move {
18109                    if unresolved_request == unresolved_item_1 {
18110                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18111                        Ok(resolved_item_1.clone())
18112                    } else if unresolved_request == unresolved_item_2 {
18113                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18114                        Ok(resolved_item_2.clone())
18115                    } else {
18116                        panic!("Unexpected completion item {unresolved_request:?}")
18117                    }
18118                }
18119            }
18120        })
18121        .detach();
18122
18123    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18124        let unresolved_item_1 = unresolved_item_1.clone();
18125        let unresolved_item_2 = unresolved_item_2.clone();
18126        async move {
18127            Ok(Some(lsp::CompletionResponse::Array(vec![
18128                unresolved_item_1,
18129                unresolved_item_2,
18130            ])))
18131        }
18132    })
18133    .next()
18134    .await;
18135
18136    cx.condition(|editor, _| editor.context_menu_visible())
18137        .await;
18138    cx.update_editor(|editor, _, _| {
18139        let context_menu = editor.context_menu.borrow_mut();
18140        let context_menu = context_menu
18141            .as_ref()
18142            .expect("Should have the context menu deployed");
18143        match context_menu {
18144            CodeContextMenu::Completions(completions_menu) => {
18145                let completions = completions_menu.completions.borrow_mut();
18146                assert_eq!(
18147                    completions
18148                        .iter()
18149                        .map(|completion| &completion.label.text)
18150                        .collect::<Vec<_>>(),
18151                    vec!["id", "other"]
18152                )
18153            }
18154            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18155        }
18156    });
18157    cx.run_until_parked();
18158
18159    cx.update_editor(|editor, window, cx| {
18160        editor.context_menu_next(&ContextMenuNext, window, cx);
18161    });
18162    cx.run_until_parked();
18163    cx.update_editor(|editor, window, cx| {
18164        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18165    });
18166    cx.run_until_parked();
18167    cx.update_editor(|editor, window, cx| {
18168        editor.context_menu_next(&ContextMenuNext, window, cx);
18169    });
18170    cx.run_until_parked();
18171    cx.update_editor(|editor, window, cx| {
18172        editor
18173            .compose_completion(&ComposeCompletion::default(), window, cx)
18174            .expect("No task returned")
18175    })
18176    .await
18177    .expect("Completion failed");
18178    cx.run_until_parked();
18179
18180    cx.update_editor(|editor, _, cx| {
18181        assert_eq!(
18182            resolve_requests_1.load(atomic::Ordering::Acquire),
18183            1,
18184            "Should always resolve once despite multiple selections"
18185        );
18186        assert_eq!(
18187            resolve_requests_2.load(atomic::Ordering::Acquire),
18188            1,
18189            "Should always resolve once after multiple selections and applying the completion"
18190        );
18191        assert_eq!(
18192            editor.text(cx),
18193            "fn main() { let a = ??.other; }",
18194            "Should use resolved data when applying the completion"
18195        );
18196    });
18197}
18198
18199#[gpui::test]
18200async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18201    init_test(cx, |_| {});
18202
18203    let item_0 = lsp::CompletionItem {
18204        label: "abs".into(),
18205        insert_text: Some("abs".into()),
18206        data: Some(json!({ "very": "special"})),
18207        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18208        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18209            lsp::InsertReplaceEdit {
18210                new_text: "abs".to_string(),
18211                insert: lsp::Range::default(),
18212                replace: lsp::Range::default(),
18213            },
18214        )),
18215        ..lsp::CompletionItem::default()
18216    };
18217    let items = iter::once(item_0.clone())
18218        .chain((11..51).map(|i| lsp::CompletionItem {
18219            label: format!("item_{}", i),
18220            insert_text: Some(format!("item_{}", i)),
18221            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18222            ..lsp::CompletionItem::default()
18223        }))
18224        .collect::<Vec<_>>();
18225
18226    let default_commit_characters = vec!["?".to_string()];
18227    let default_data = json!({ "default": "data"});
18228    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18229    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18230    let default_edit_range = lsp::Range {
18231        start: lsp::Position {
18232            line: 0,
18233            character: 5,
18234        },
18235        end: lsp::Position {
18236            line: 0,
18237            character: 5,
18238        },
18239    };
18240
18241    let mut cx = EditorLspTestContext::new_rust(
18242        lsp::ServerCapabilities {
18243            completion_provider: Some(lsp::CompletionOptions {
18244                trigger_characters: Some(vec![".".to_string()]),
18245                resolve_provider: Some(true),
18246                ..Default::default()
18247            }),
18248            ..Default::default()
18249        },
18250        cx,
18251    )
18252    .await;
18253
18254    cx.set_state("fn main() { let a = 2ˇ; }");
18255    cx.simulate_keystroke(".");
18256
18257    let completion_data = default_data.clone();
18258    let completion_characters = default_commit_characters.clone();
18259    let completion_items = items.clone();
18260    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18261        let default_data = completion_data.clone();
18262        let default_commit_characters = completion_characters.clone();
18263        let items = completion_items.clone();
18264        async move {
18265            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18266                items,
18267                item_defaults: Some(lsp::CompletionListItemDefaults {
18268                    data: Some(default_data.clone()),
18269                    commit_characters: Some(default_commit_characters.clone()),
18270                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18271                        default_edit_range,
18272                    )),
18273                    insert_text_format: Some(default_insert_text_format),
18274                    insert_text_mode: Some(default_insert_text_mode),
18275                }),
18276                ..lsp::CompletionList::default()
18277            })))
18278        }
18279    })
18280    .next()
18281    .await;
18282
18283    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18284    cx.lsp
18285        .server
18286        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18287            let closure_resolved_items = resolved_items.clone();
18288            move |item_to_resolve, _| {
18289                let closure_resolved_items = closure_resolved_items.clone();
18290                async move {
18291                    closure_resolved_items.lock().push(item_to_resolve.clone());
18292                    Ok(item_to_resolve)
18293                }
18294            }
18295        })
18296        .detach();
18297
18298    cx.condition(|editor, _| editor.context_menu_visible())
18299        .await;
18300    cx.run_until_parked();
18301    cx.update_editor(|editor, _, _| {
18302        let menu = editor.context_menu.borrow_mut();
18303        match menu.as_ref().expect("should have the completions menu") {
18304            CodeContextMenu::Completions(completions_menu) => {
18305                assert_eq!(
18306                    completions_menu
18307                        .entries
18308                        .borrow()
18309                        .iter()
18310                        .map(|mat| mat.string.clone())
18311                        .collect::<Vec<String>>(),
18312                    items
18313                        .iter()
18314                        .map(|completion| completion.label.clone())
18315                        .collect::<Vec<String>>()
18316                );
18317            }
18318            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18319        }
18320    });
18321    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18322    // with 4 from the end.
18323    assert_eq!(
18324        *resolved_items.lock(),
18325        [&items[0..16], &items[items.len() - 4..items.len()]]
18326            .concat()
18327            .iter()
18328            .cloned()
18329            .map(|mut item| {
18330                if item.data.is_none() {
18331                    item.data = Some(default_data.clone());
18332                }
18333                item
18334            })
18335            .collect::<Vec<lsp::CompletionItem>>(),
18336        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18337    );
18338    resolved_items.lock().clear();
18339
18340    cx.update_editor(|editor, window, cx| {
18341        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18342    });
18343    cx.run_until_parked();
18344    // Completions that have already been resolved are skipped.
18345    assert_eq!(
18346        *resolved_items.lock(),
18347        items[items.len() - 17..items.len() - 4]
18348            .iter()
18349            .cloned()
18350            .map(|mut item| {
18351                if item.data.is_none() {
18352                    item.data = Some(default_data.clone());
18353                }
18354                item
18355            })
18356            .collect::<Vec<lsp::CompletionItem>>()
18357    );
18358    resolved_items.lock().clear();
18359}
18360
18361#[gpui::test]
18362async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18363    init_test(cx, |_| {});
18364
18365    let mut cx = EditorLspTestContext::new(
18366        Language::new(
18367            LanguageConfig {
18368                matcher: LanguageMatcher {
18369                    path_suffixes: vec!["jsx".into()],
18370                    ..Default::default()
18371                },
18372                overrides: [(
18373                    "element".into(),
18374                    LanguageConfigOverride {
18375                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18376                        ..Default::default()
18377                    },
18378                )]
18379                .into_iter()
18380                .collect(),
18381                ..Default::default()
18382            },
18383            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18384        )
18385        .with_override_query("(jsx_self_closing_element) @element")
18386        .unwrap(),
18387        lsp::ServerCapabilities {
18388            completion_provider: Some(lsp::CompletionOptions {
18389                trigger_characters: Some(vec![":".to_string()]),
18390                ..Default::default()
18391            }),
18392            ..Default::default()
18393        },
18394        cx,
18395    )
18396    .await;
18397
18398    cx.lsp
18399        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18400            Ok(Some(lsp::CompletionResponse::Array(vec![
18401                lsp::CompletionItem {
18402                    label: "bg-blue".into(),
18403                    ..Default::default()
18404                },
18405                lsp::CompletionItem {
18406                    label: "bg-red".into(),
18407                    ..Default::default()
18408                },
18409                lsp::CompletionItem {
18410                    label: "bg-yellow".into(),
18411                    ..Default::default()
18412                },
18413            ])))
18414        });
18415
18416    cx.set_state(r#"<p class="bgˇ" />"#);
18417
18418    // Trigger completion when typing a dash, because the dash is an extra
18419    // word character in the 'element' scope, which contains the cursor.
18420    cx.simulate_keystroke("-");
18421    cx.executor().run_until_parked();
18422    cx.update_editor(|editor, _, _| {
18423        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18424        {
18425            assert_eq!(
18426                completion_menu_entries(menu),
18427                &["bg-blue", "bg-red", "bg-yellow"]
18428            );
18429        } else {
18430            panic!("expected completion menu to be open");
18431        }
18432    });
18433
18434    cx.simulate_keystroke("l");
18435    cx.executor().run_until_parked();
18436    cx.update_editor(|editor, _, _| {
18437        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18438        {
18439            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18440        } else {
18441            panic!("expected completion menu to be open");
18442        }
18443    });
18444
18445    // When filtering completions, consider the character after the '-' to
18446    // be the start of a subword.
18447    cx.set_state(r#"<p class="yelˇ" />"#);
18448    cx.simulate_keystroke("l");
18449    cx.executor().run_until_parked();
18450    cx.update_editor(|editor, _, _| {
18451        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18452        {
18453            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18454        } else {
18455            panic!("expected completion menu to be open");
18456        }
18457    });
18458}
18459
18460fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18461    let entries = menu.entries.borrow();
18462    entries.iter().map(|mat| mat.string.clone()).collect()
18463}
18464
18465#[gpui::test]
18466async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18467    init_test(cx, |settings| {
18468        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18469    });
18470
18471    let fs = FakeFs::new(cx.executor());
18472    fs.insert_file(path!("/file.ts"), Default::default()).await;
18473
18474    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18475    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18476
18477    language_registry.add(Arc::new(Language::new(
18478        LanguageConfig {
18479            name: "TypeScript".into(),
18480            matcher: LanguageMatcher {
18481                path_suffixes: vec!["ts".to_string()],
18482                ..Default::default()
18483            },
18484            ..Default::default()
18485        },
18486        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18487    )));
18488    update_test_language_settings(cx, |settings| {
18489        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18490    });
18491
18492    let test_plugin = "test_plugin";
18493    let _ = language_registry.register_fake_lsp(
18494        "TypeScript",
18495        FakeLspAdapter {
18496            prettier_plugins: vec![test_plugin],
18497            ..Default::default()
18498        },
18499    );
18500
18501    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18502    let buffer = project
18503        .update(cx, |project, cx| {
18504            project.open_local_buffer(path!("/file.ts"), cx)
18505        })
18506        .await
18507        .unwrap();
18508
18509    let buffer_text = "one\ntwo\nthree\n";
18510    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18511    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18512    editor.update_in(cx, |editor, window, cx| {
18513        editor.set_text(buffer_text, window, cx)
18514    });
18515
18516    editor
18517        .update_in(cx, |editor, window, cx| {
18518            editor.perform_format(
18519                project.clone(),
18520                FormatTrigger::Manual,
18521                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18522                window,
18523                cx,
18524            )
18525        })
18526        .unwrap()
18527        .await;
18528    assert_eq!(
18529        editor.update(cx, |editor, cx| editor.text(cx)),
18530        buffer_text.to_string() + prettier_format_suffix,
18531        "Test prettier formatting was not applied to the original buffer text",
18532    );
18533
18534    update_test_language_settings(cx, |settings| {
18535        settings.defaults.formatter = Some(FormatterList::default())
18536    });
18537    let format = editor.update_in(cx, |editor, window, cx| {
18538        editor.perform_format(
18539            project.clone(),
18540            FormatTrigger::Manual,
18541            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18542            window,
18543            cx,
18544        )
18545    });
18546    format.await.unwrap();
18547    assert_eq!(
18548        editor.update(cx, |editor, cx| editor.text(cx)),
18549        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18550        "Autoformatting (via test prettier) was not applied to the original buffer text",
18551    );
18552}
18553
18554#[gpui::test]
18555async fn test_addition_reverts(cx: &mut TestAppContext) {
18556    init_test(cx, |_| {});
18557    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18558    let base_text = indoc! {r#"
18559        struct Row;
18560        struct Row1;
18561        struct Row2;
18562
18563        struct Row4;
18564        struct Row5;
18565        struct Row6;
18566
18567        struct Row8;
18568        struct Row9;
18569        struct Row10;"#};
18570
18571    // When addition hunks are not adjacent to carets, no hunk revert is performed
18572    assert_hunk_revert(
18573        indoc! {r#"struct Row;
18574                   struct Row1;
18575                   struct Row1.1;
18576                   struct Row1.2;
18577                   struct Row2;ˇ
18578
18579                   struct Row4;
18580                   struct Row5;
18581                   struct Row6;
18582
18583                   struct Row8;
18584                   ˇstruct Row9;
18585                   struct Row9.1;
18586                   struct Row9.2;
18587                   struct Row9.3;
18588                   struct Row10;"#},
18589        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18590        indoc! {r#"struct Row;
18591                   struct Row1;
18592                   struct Row1.1;
18593                   struct Row1.2;
18594                   struct Row2;ˇ
18595
18596                   struct Row4;
18597                   struct Row5;
18598                   struct Row6;
18599
18600                   struct Row8;
18601                   ˇstruct Row9;
18602                   struct Row9.1;
18603                   struct Row9.2;
18604                   struct Row9.3;
18605                   struct Row10;"#},
18606        base_text,
18607        &mut cx,
18608    );
18609    // Same for selections
18610    assert_hunk_revert(
18611        indoc! {r#"struct Row;
18612                   struct Row1;
18613                   struct Row2;
18614                   struct Row2.1;
18615                   struct Row2.2;
18616                   «ˇ
18617                   struct Row4;
18618                   struct» Row5;
18619                   «struct Row6;
18620                   ˇ»
18621                   struct Row9.1;
18622                   struct Row9.2;
18623                   struct Row9.3;
18624                   struct Row8;
18625                   struct Row9;
18626                   struct Row10;"#},
18627        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18628        indoc! {r#"struct Row;
18629                   struct Row1;
18630                   struct Row2;
18631                   struct Row2.1;
18632                   struct Row2.2;
18633                   «ˇ
18634                   struct Row4;
18635                   struct» Row5;
18636                   «struct Row6;
18637                   ˇ»
18638                   struct Row9.1;
18639                   struct Row9.2;
18640                   struct Row9.3;
18641                   struct Row8;
18642                   struct Row9;
18643                   struct Row10;"#},
18644        base_text,
18645        &mut cx,
18646    );
18647
18648    // When carets and selections intersect the addition hunks, those are reverted.
18649    // Adjacent carets got merged.
18650    assert_hunk_revert(
18651        indoc! {r#"struct Row;
18652                   ˇ// something on the top
18653                   struct Row1;
18654                   struct Row2;
18655                   struct Roˇw3.1;
18656                   struct Row2.2;
18657                   struct Row2.3;ˇ
18658
18659                   struct Row4;
18660                   struct ˇRow5.1;
18661                   struct Row5.2;
18662                   struct «Rowˇ»5.3;
18663                   struct Row5;
18664                   struct Row6;
18665                   ˇ
18666                   struct Row9.1;
18667                   struct «Rowˇ»9.2;
18668                   struct «ˇRow»9.3;
18669                   struct Row8;
18670                   struct Row9;
18671                   «ˇ// something on bottom»
18672                   struct Row10;"#},
18673        vec![
18674            DiffHunkStatusKind::Added,
18675            DiffHunkStatusKind::Added,
18676            DiffHunkStatusKind::Added,
18677            DiffHunkStatusKind::Added,
18678            DiffHunkStatusKind::Added,
18679        ],
18680        indoc! {r#"struct Row;
18681                   ˇstruct Row1;
18682                   struct Row2;
18683                   ˇ
18684                   struct Row4;
18685                   ˇstruct Row5;
18686                   struct Row6;
18687                   ˇ
18688                   ˇstruct Row8;
18689                   struct Row9;
18690                   ˇstruct Row10;"#},
18691        base_text,
18692        &mut cx,
18693    );
18694}
18695
18696#[gpui::test]
18697async fn test_modification_reverts(cx: &mut TestAppContext) {
18698    init_test(cx, |_| {});
18699    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18700    let base_text = indoc! {r#"
18701        struct Row;
18702        struct Row1;
18703        struct Row2;
18704
18705        struct Row4;
18706        struct Row5;
18707        struct Row6;
18708
18709        struct Row8;
18710        struct Row9;
18711        struct Row10;"#};
18712
18713    // Modification hunks behave the same as the addition ones.
18714    assert_hunk_revert(
18715        indoc! {r#"struct Row;
18716                   struct Row1;
18717                   struct Row33;
18718                   ˇ
18719                   struct Row4;
18720                   struct Row5;
18721                   struct Row6;
18722                   ˇ
18723                   struct Row99;
18724                   struct Row9;
18725                   struct Row10;"#},
18726        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18727        indoc! {r#"struct Row;
18728                   struct Row1;
18729                   struct Row33;
18730                   ˇ
18731                   struct Row4;
18732                   struct Row5;
18733                   struct Row6;
18734                   ˇ
18735                   struct Row99;
18736                   struct Row9;
18737                   struct Row10;"#},
18738        base_text,
18739        &mut cx,
18740    );
18741    assert_hunk_revert(
18742        indoc! {r#"struct Row;
18743                   struct Row1;
18744                   struct Row33;
18745                   «ˇ
18746                   struct Row4;
18747                   struct» Row5;
18748                   «struct Row6;
18749                   ˇ»
18750                   struct Row99;
18751                   struct Row9;
18752                   struct Row10;"#},
18753        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18754        indoc! {r#"struct Row;
18755                   struct Row1;
18756                   struct Row33;
18757                   «ˇ
18758                   struct Row4;
18759                   struct» Row5;
18760                   «struct Row6;
18761                   ˇ»
18762                   struct Row99;
18763                   struct Row9;
18764                   struct Row10;"#},
18765        base_text,
18766        &mut cx,
18767    );
18768
18769    assert_hunk_revert(
18770        indoc! {r#"ˇstruct Row1.1;
18771                   struct Row1;
18772                   «ˇstr»uct Row22;
18773
18774                   struct ˇRow44;
18775                   struct Row5;
18776                   struct «Rˇ»ow66;ˇ
18777
18778                   «struˇ»ct Row88;
18779                   struct Row9;
18780                   struct Row1011;ˇ"#},
18781        vec![
18782            DiffHunkStatusKind::Modified,
18783            DiffHunkStatusKind::Modified,
18784            DiffHunkStatusKind::Modified,
18785            DiffHunkStatusKind::Modified,
18786            DiffHunkStatusKind::Modified,
18787            DiffHunkStatusKind::Modified,
18788        ],
18789        indoc! {r#"struct Row;
18790                   ˇstruct Row1;
18791                   struct Row2;
18792                   ˇ
18793                   struct Row4;
18794                   ˇstruct Row5;
18795                   struct Row6;
18796                   ˇ
18797                   struct Row8;
18798                   ˇstruct Row9;
18799                   struct Row10;ˇ"#},
18800        base_text,
18801        &mut cx,
18802    );
18803}
18804
18805#[gpui::test]
18806async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18807    init_test(cx, |_| {});
18808    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18809    let base_text = indoc! {r#"
18810        one
18811
18812        two
18813        three
18814        "#};
18815
18816    cx.set_head_text(base_text);
18817    cx.set_state("\nˇ\n");
18818    cx.executor().run_until_parked();
18819    cx.update_editor(|editor, _window, cx| {
18820        editor.expand_selected_diff_hunks(cx);
18821    });
18822    cx.executor().run_until_parked();
18823    cx.update_editor(|editor, window, cx| {
18824        editor.backspace(&Default::default(), window, cx);
18825    });
18826    cx.run_until_parked();
18827    cx.assert_state_with_diff(
18828        indoc! {r#"
18829
18830        - two
18831        - threeˇ
18832        +
18833        "#}
18834        .to_string(),
18835    );
18836}
18837
18838#[gpui::test]
18839async fn test_deletion_reverts(cx: &mut TestAppContext) {
18840    init_test(cx, |_| {});
18841    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18842    let base_text = indoc! {r#"struct Row;
18843struct Row1;
18844struct Row2;
18845
18846struct Row4;
18847struct Row5;
18848struct Row6;
18849
18850struct Row8;
18851struct Row9;
18852struct Row10;"#};
18853
18854    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18855    assert_hunk_revert(
18856        indoc! {r#"struct Row;
18857                   struct Row2;
18858
18859                   ˇstruct Row4;
18860                   struct Row5;
18861                   struct Row6;
18862                   ˇ
18863                   struct Row8;
18864                   struct Row10;"#},
18865        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18866        indoc! {r#"struct Row;
18867                   struct Row2;
18868
18869                   ˇstruct Row4;
18870                   struct Row5;
18871                   struct Row6;
18872                   ˇ
18873                   struct Row8;
18874                   struct Row10;"#},
18875        base_text,
18876        &mut cx,
18877    );
18878    assert_hunk_revert(
18879        indoc! {r#"struct Row;
18880                   struct Row2;
18881
18882                   «ˇstruct Row4;
18883                   struct» Row5;
18884                   «struct Row6;
18885                   ˇ»
18886                   struct Row8;
18887                   struct Row10;"#},
18888        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18889        indoc! {r#"struct Row;
18890                   struct Row2;
18891
18892                   «ˇstruct Row4;
18893                   struct» Row5;
18894                   «struct Row6;
18895                   ˇ»
18896                   struct Row8;
18897                   struct Row10;"#},
18898        base_text,
18899        &mut cx,
18900    );
18901
18902    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18903    assert_hunk_revert(
18904        indoc! {r#"struct Row;
18905                   ˇstruct Row2;
18906
18907                   struct Row4;
18908                   struct Row5;
18909                   struct Row6;
18910
18911                   struct Row8;ˇ
18912                   struct Row10;"#},
18913        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18914        indoc! {r#"struct Row;
18915                   struct Row1;
18916                   ˇstruct Row2;
18917
18918                   struct Row4;
18919                   struct Row5;
18920                   struct Row6;
18921
18922                   struct Row8;ˇ
18923                   struct Row9;
18924                   struct Row10;"#},
18925        base_text,
18926        &mut cx,
18927    );
18928    assert_hunk_revert(
18929        indoc! {r#"struct Row;
18930                   struct Row2«ˇ;
18931                   struct Row4;
18932                   struct» Row5;
18933                   «struct Row6;
18934
18935                   struct Row8;ˇ»
18936                   struct Row10;"#},
18937        vec![
18938            DiffHunkStatusKind::Deleted,
18939            DiffHunkStatusKind::Deleted,
18940            DiffHunkStatusKind::Deleted,
18941        ],
18942        indoc! {r#"struct Row;
18943                   struct Row1;
18944                   struct Row2«ˇ;
18945
18946                   struct Row4;
18947                   struct» Row5;
18948                   «struct Row6;
18949
18950                   struct Row8;ˇ»
18951                   struct Row9;
18952                   struct Row10;"#},
18953        base_text,
18954        &mut cx,
18955    );
18956}
18957
18958#[gpui::test]
18959async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18960    init_test(cx, |_| {});
18961
18962    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18963    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18964    let base_text_3 =
18965        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18966
18967    let text_1 = edit_first_char_of_every_line(base_text_1);
18968    let text_2 = edit_first_char_of_every_line(base_text_2);
18969    let text_3 = edit_first_char_of_every_line(base_text_3);
18970
18971    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18972    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18973    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18974
18975    let multibuffer = cx.new(|cx| {
18976        let mut multibuffer = MultiBuffer::new(ReadWrite);
18977        multibuffer.push_excerpts(
18978            buffer_1.clone(),
18979            [
18980                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18981                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18982                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18983            ],
18984            cx,
18985        );
18986        multibuffer.push_excerpts(
18987            buffer_2.clone(),
18988            [
18989                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18990                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18991                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18992            ],
18993            cx,
18994        );
18995        multibuffer.push_excerpts(
18996            buffer_3.clone(),
18997            [
18998                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18999                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19000                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19001            ],
19002            cx,
19003        );
19004        multibuffer
19005    });
19006
19007    let fs = FakeFs::new(cx.executor());
19008    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19009    let (editor, cx) = cx
19010        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19011    editor.update_in(cx, |editor, _window, cx| {
19012        for (buffer, diff_base) in [
19013            (buffer_1.clone(), base_text_1),
19014            (buffer_2.clone(), base_text_2),
19015            (buffer_3.clone(), base_text_3),
19016        ] {
19017            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19018            editor
19019                .buffer
19020                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19021        }
19022    });
19023    cx.executor().run_until_parked();
19024
19025    editor.update_in(cx, |editor, window, cx| {
19026        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}");
19027        editor.select_all(&SelectAll, window, cx);
19028        editor.git_restore(&Default::default(), window, cx);
19029    });
19030    cx.executor().run_until_parked();
19031
19032    // When all ranges are selected, all buffer hunks are reverted.
19033    editor.update(cx, |editor, cx| {
19034        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");
19035    });
19036    buffer_1.update(cx, |buffer, _| {
19037        assert_eq!(buffer.text(), base_text_1);
19038    });
19039    buffer_2.update(cx, |buffer, _| {
19040        assert_eq!(buffer.text(), base_text_2);
19041    });
19042    buffer_3.update(cx, |buffer, _| {
19043        assert_eq!(buffer.text(), base_text_3);
19044    });
19045
19046    editor.update_in(cx, |editor, window, cx| {
19047        editor.undo(&Default::default(), window, cx);
19048    });
19049
19050    editor.update_in(cx, |editor, window, cx| {
19051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19052            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19053        });
19054        editor.git_restore(&Default::default(), window, cx);
19055    });
19056
19057    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19058    // but not affect buffer_2 and its related excerpts.
19059    editor.update(cx, |editor, cx| {
19060        assert_eq!(
19061            editor.text(cx),
19062            "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}"
19063        );
19064    });
19065    buffer_1.update(cx, |buffer, _| {
19066        assert_eq!(buffer.text(), base_text_1);
19067    });
19068    buffer_2.update(cx, |buffer, _| {
19069        assert_eq!(
19070            buffer.text(),
19071            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19072        );
19073    });
19074    buffer_3.update(cx, |buffer, _| {
19075        assert_eq!(
19076            buffer.text(),
19077            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19078        );
19079    });
19080
19081    fn edit_first_char_of_every_line(text: &str) -> String {
19082        text.split('\n')
19083            .map(|line| format!("X{}", &line[1..]))
19084            .collect::<Vec<_>>()
19085            .join("\n")
19086    }
19087}
19088
19089#[gpui::test]
19090async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19091    init_test(cx, |_| {});
19092
19093    let cols = 4;
19094    let rows = 10;
19095    let sample_text_1 = sample_text(rows, cols, 'a');
19096    assert_eq!(
19097        sample_text_1,
19098        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19099    );
19100    let sample_text_2 = sample_text(rows, cols, 'l');
19101    assert_eq!(
19102        sample_text_2,
19103        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19104    );
19105    let sample_text_3 = sample_text(rows, cols, 'v');
19106    assert_eq!(
19107        sample_text_3,
19108        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19109    );
19110
19111    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19112    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19113    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19114
19115    let multi_buffer = cx.new(|cx| {
19116        let mut multibuffer = MultiBuffer::new(ReadWrite);
19117        multibuffer.push_excerpts(
19118            buffer_1.clone(),
19119            [
19120                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19121                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19122                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19123            ],
19124            cx,
19125        );
19126        multibuffer.push_excerpts(
19127            buffer_2.clone(),
19128            [
19129                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19130                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19131                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19132            ],
19133            cx,
19134        );
19135        multibuffer.push_excerpts(
19136            buffer_3.clone(),
19137            [
19138                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19139                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19140                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19141            ],
19142            cx,
19143        );
19144        multibuffer
19145    });
19146
19147    let fs = FakeFs::new(cx.executor());
19148    fs.insert_tree(
19149        "/a",
19150        json!({
19151            "main.rs": sample_text_1,
19152            "other.rs": sample_text_2,
19153            "lib.rs": sample_text_3,
19154        }),
19155    )
19156    .await;
19157    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19159    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19160    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19161        Editor::new(
19162            EditorMode::full(),
19163            multi_buffer,
19164            Some(project.clone()),
19165            window,
19166            cx,
19167        )
19168    });
19169    let multibuffer_item_id = workspace
19170        .update(cx, |workspace, window, cx| {
19171            assert!(
19172                workspace.active_item(cx).is_none(),
19173                "active item should be None before the first item is added"
19174            );
19175            workspace.add_item_to_active_pane(
19176                Box::new(multi_buffer_editor.clone()),
19177                None,
19178                true,
19179                window,
19180                cx,
19181            );
19182            let active_item = workspace
19183                .active_item(cx)
19184                .expect("should have an active item after adding the multi buffer");
19185            assert_eq!(
19186                active_item.buffer_kind(cx),
19187                ItemBufferKind::Multibuffer,
19188                "A multi buffer was expected to active after adding"
19189            );
19190            active_item.item_id()
19191        })
19192        .unwrap();
19193    cx.executor().run_until_parked();
19194
19195    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19196        editor.change_selections(
19197            SelectionEffects::scroll(Autoscroll::Next),
19198            window,
19199            cx,
19200            |s| s.select_ranges(Some(1..2)),
19201        );
19202        editor.open_excerpts(&OpenExcerpts, window, cx);
19203    });
19204    cx.executor().run_until_parked();
19205    let first_item_id = workspace
19206        .update(cx, |workspace, window, cx| {
19207            let active_item = workspace
19208                .active_item(cx)
19209                .expect("should have an active item after navigating into the 1st buffer");
19210            let first_item_id = active_item.item_id();
19211            assert_ne!(
19212                first_item_id, multibuffer_item_id,
19213                "Should navigate into the 1st buffer and activate it"
19214            );
19215            assert_eq!(
19216                active_item.buffer_kind(cx),
19217                ItemBufferKind::Singleton,
19218                "New active item should be a singleton buffer"
19219            );
19220            assert_eq!(
19221                active_item
19222                    .act_as::<Editor>(cx)
19223                    .expect("should have navigated into an editor for the 1st buffer")
19224                    .read(cx)
19225                    .text(cx),
19226                sample_text_1
19227            );
19228
19229            workspace
19230                .go_back(workspace.active_pane().downgrade(), window, cx)
19231                .detach_and_log_err(cx);
19232
19233            first_item_id
19234        })
19235        .unwrap();
19236    cx.executor().run_until_parked();
19237    workspace
19238        .update(cx, |workspace, _, cx| {
19239            let active_item = workspace
19240                .active_item(cx)
19241                .expect("should have an active item after navigating back");
19242            assert_eq!(
19243                active_item.item_id(),
19244                multibuffer_item_id,
19245                "Should navigate back to the multi buffer"
19246            );
19247            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19248        })
19249        .unwrap();
19250
19251    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19252        editor.change_selections(
19253            SelectionEffects::scroll(Autoscroll::Next),
19254            window,
19255            cx,
19256            |s| s.select_ranges(Some(39..40)),
19257        );
19258        editor.open_excerpts(&OpenExcerpts, window, cx);
19259    });
19260    cx.executor().run_until_parked();
19261    let second_item_id = workspace
19262        .update(cx, |workspace, window, cx| {
19263            let active_item = workspace
19264                .active_item(cx)
19265                .expect("should have an active item after navigating into the 2nd buffer");
19266            let second_item_id = active_item.item_id();
19267            assert_ne!(
19268                second_item_id, multibuffer_item_id,
19269                "Should navigate away from the multibuffer"
19270            );
19271            assert_ne!(
19272                second_item_id, first_item_id,
19273                "Should navigate into the 2nd buffer and activate it"
19274            );
19275            assert_eq!(
19276                active_item.buffer_kind(cx),
19277                ItemBufferKind::Singleton,
19278                "New active item should be a singleton buffer"
19279            );
19280            assert_eq!(
19281                active_item
19282                    .act_as::<Editor>(cx)
19283                    .expect("should have navigated into an editor")
19284                    .read(cx)
19285                    .text(cx),
19286                sample_text_2
19287            );
19288
19289            workspace
19290                .go_back(workspace.active_pane().downgrade(), window, cx)
19291                .detach_and_log_err(cx);
19292
19293            second_item_id
19294        })
19295        .unwrap();
19296    cx.executor().run_until_parked();
19297    workspace
19298        .update(cx, |workspace, _, cx| {
19299            let active_item = workspace
19300                .active_item(cx)
19301                .expect("should have an active item after navigating back from the 2nd buffer");
19302            assert_eq!(
19303                active_item.item_id(),
19304                multibuffer_item_id,
19305                "Should navigate back from the 2nd buffer to the multi buffer"
19306            );
19307            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19308        })
19309        .unwrap();
19310
19311    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19312        editor.change_selections(
19313            SelectionEffects::scroll(Autoscroll::Next),
19314            window,
19315            cx,
19316            |s| s.select_ranges(Some(70..70)),
19317        );
19318        editor.open_excerpts(&OpenExcerpts, window, cx);
19319    });
19320    cx.executor().run_until_parked();
19321    workspace
19322        .update(cx, |workspace, window, cx| {
19323            let active_item = workspace
19324                .active_item(cx)
19325                .expect("should have an active item after navigating into the 3rd buffer");
19326            let third_item_id = active_item.item_id();
19327            assert_ne!(
19328                third_item_id, multibuffer_item_id,
19329                "Should navigate into the 3rd buffer and activate it"
19330            );
19331            assert_ne!(third_item_id, first_item_id);
19332            assert_ne!(third_item_id, second_item_id);
19333            assert_eq!(
19334                active_item.buffer_kind(cx),
19335                ItemBufferKind::Singleton,
19336                "New active item should be a singleton buffer"
19337            );
19338            assert_eq!(
19339                active_item
19340                    .act_as::<Editor>(cx)
19341                    .expect("should have navigated into an editor")
19342                    .read(cx)
19343                    .text(cx),
19344                sample_text_3
19345            );
19346
19347            workspace
19348                .go_back(workspace.active_pane().downgrade(), window, cx)
19349                .detach_and_log_err(cx);
19350        })
19351        .unwrap();
19352    cx.executor().run_until_parked();
19353    workspace
19354        .update(cx, |workspace, _, cx| {
19355            let active_item = workspace
19356                .active_item(cx)
19357                .expect("should have an active item after navigating back from the 3rd buffer");
19358            assert_eq!(
19359                active_item.item_id(),
19360                multibuffer_item_id,
19361                "Should navigate back from the 3rd buffer to the multi buffer"
19362            );
19363            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19364        })
19365        .unwrap();
19366}
19367
19368#[gpui::test]
19369async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19370    init_test(cx, |_| {});
19371
19372    let mut cx = EditorTestContext::new(cx).await;
19373
19374    let diff_base = r#"
19375        use some::mod;
19376
19377        const A: u32 = 42;
19378
19379        fn main() {
19380            println!("hello");
19381
19382            println!("world");
19383        }
19384        "#
19385    .unindent();
19386
19387    cx.set_state(
19388        &r#"
19389        use some::modified;
19390
19391        ˇ
19392        fn main() {
19393            println!("hello there");
19394
19395            println!("around the");
19396            println!("world");
19397        }
19398        "#
19399        .unindent(),
19400    );
19401
19402    cx.set_head_text(&diff_base);
19403    executor.run_until_parked();
19404
19405    cx.update_editor(|editor, window, cx| {
19406        editor.go_to_next_hunk(&GoToHunk, window, cx);
19407        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19408    });
19409    executor.run_until_parked();
19410    cx.assert_state_with_diff(
19411        r#"
19412          use some::modified;
19413
19414
19415          fn main() {
19416        -     println!("hello");
19417        + ˇ    println!("hello there");
19418
19419              println!("around the");
19420              println!("world");
19421          }
19422        "#
19423        .unindent(),
19424    );
19425
19426    cx.update_editor(|editor, window, cx| {
19427        for _ in 0..2 {
19428            editor.go_to_next_hunk(&GoToHunk, window, cx);
19429            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19430        }
19431    });
19432    executor.run_until_parked();
19433    cx.assert_state_with_diff(
19434        r#"
19435        - use some::mod;
19436        + ˇuse some::modified;
19437
19438
19439          fn main() {
19440        -     println!("hello");
19441        +     println!("hello there");
19442
19443        +     println!("around the");
19444              println!("world");
19445          }
19446        "#
19447        .unindent(),
19448    );
19449
19450    cx.update_editor(|editor, window, cx| {
19451        editor.go_to_next_hunk(&GoToHunk, window, cx);
19452        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19453    });
19454    executor.run_until_parked();
19455    cx.assert_state_with_diff(
19456        r#"
19457        - use some::mod;
19458        + use some::modified;
19459
19460        - const A: u32 = 42;
19461          ˇ
19462          fn main() {
19463        -     println!("hello");
19464        +     println!("hello there");
19465
19466        +     println!("around the");
19467              println!("world");
19468          }
19469        "#
19470        .unindent(),
19471    );
19472
19473    cx.update_editor(|editor, window, cx| {
19474        editor.cancel(&Cancel, window, cx);
19475    });
19476
19477    cx.assert_state_with_diff(
19478        r#"
19479          use some::modified;
19480
19481          ˇ
19482          fn main() {
19483              println!("hello there");
19484
19485              println!("around the");
19486              println!("world");
19487          }
19488        "#
19489        .unindent(),
19490    );
19491}
19492
19493#[gpui::test]
19494async fn test_diff_base_change_with_expanded_diff_hunks(
19495    executor: BackgroundExecutor,
19496    cx: &mut TestAppContext,
19497) {
19498    init_test(cx, |_| {});
19499
19500    let mut cx = EditorTestContext::new(cx).await;
19501
19502    let diff_base = r#"
19503        use some::mod1;
19504        use some::mod2;
19505
19506        const A: u32 = 42;
19507        const B: u32 = 42;
19508        const C: u32 = 42;
19509
19510        fn main() {
19511            println!("hello");
19512
19513            println!("world");
19514        }
19515        "#
19516    .unindent();
19517
19518    cx.set_state(
19519        &r#"
19520        use some::mod2;
19521
19522        const A: u32 = 42;
19523        const C: u32 = 42;
19524
19525        fn main(ˇ) {
19526            //println!("hello");
19527
19528            println!("world");
19529            //
19530            //
19531        }
19532        "#
19533        .unindent(),
19534    );
19535
19536    cx.set_head_text(&diff_base);
19537    executor.run_until_parked();
19538
19539    cx.update_editor(|editor, window, cx| {
19540        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19541    });
19542    executor.run_until_parked();
19543    cx.assert_state_with_diff(
19544        r#"
19545        - use some::mod1;
19546          use some::mod2;
19547
19548          const A: u32 = 42;
19549        - const B: u32 = 42;
19550          const C: u32 = 42;
19551
19552          fn main(ˇ) {
19553        -     println!("hello");
19554        +     //println!("hello");
19555
19556              println!("world");
19557        +     //
19558        +     //
19559          }
19560        "#
19561        .unindent(),
19562    );
19563
19564    cx.set_head_text("new diff base!");
19565    executor.run_until_parked();
19566    cx.assert_state_with_diff(
19567        r#"
19568        - new diff base!
19569        + use some::mod2;
19570        +
19571        + const A: u32 = 42;
19572        + const C: u32 = 42;
19573        +
19574        + fn main(ˇ) {
19575        +     //println!("hello");
19576        +
19577        +     println!("world");
19578        +     //
19579        +     //
19580        + }
19581        "#
19582        .unindent(),
19583    );
19584}
19585
19586#[gpui::test]
19587async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19588    init_test(cx, |_| {});
19589
19590    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19591    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19592    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19593    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19594    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19595    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19596
19597    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19598    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19599    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19600
19601    let multi_buffer = cx.new(|cx| {
19602        let mut multibuffer = MultiBuffer::new(ReadWrite);
19603        multibuffer.push_excerpts(
19604            buffer_1.clone(),
19605            [
19606                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19607                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19608                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19609            ],
19610            cx,
19611        );
19612        multibuffer.push_excerpts(
19613            buffer_2.clone(),
19614            [
19615                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19616                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19617                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19618            ],
19619            cx,
19620        );
19621        multibuffer.push_excerpts(
19622            buffer_3.clone(),
19623            [
19624                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19625                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19626                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19627            ],
19628            cx,
19629        );
19630        multibuffer
19631    });
19632
19633    let editor =
19634        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19635    editor
19636        .update(cx, |editor, _window, cx| {
19637            for (buffer, diff_base) in [
19638                (buffer_1.clone(), file_1_old),
19639                (buffer_2.clone(), file_2_old),
19640                (buffer_3.clone(), file_3_old),
19641            ] {
19642                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19643                editor
19644                    .buffer
19645                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19646            }
19647        })
19648        .unwrap();
19649
19650    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19651    cx.run_until_parked();
19652
19653    cx.assert_editor_state(
19654        &"
19655            ˇaaa
19656            ccc
19657            ddd
19658
19659            ggg
19660            hhh
19661
19662
19663            lll
19664            mmm
19665            NNN
19666
19667            qqq
19668            rrr
19669
19670            uuu
19671            111
19672            222
19673            333
19674
19675            666
19676            777
19677
19678            000
19679            !!!"
19680        .unindent(),
19681    );
19682
19683    cx.update_editor(|editor, window, cx| {
19684        editor.select_all(&SelectAll, window, cx);
19685        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19686    });
19687    cx.executor().run_until_parked();
19688
19689    cx.assert_state_with_diff(
19690        "
19691            «aaa
19692          - bbb
19693            ccc
19694            ddd
19695
19696            ggg
19697            hhh
19698
19699
19700            lll
19701            mmm
19702          - nnn
19703          + NNN
19704
19705            qqq
19706            rrr
19707
19708            uuu
19709            111
19710            222
19711            333
19712
19713          + 666
19714            777
19715
19716            000
19717            !!!ˇ»"
19718            .unindent(),
19719    );
19720}
19721
19722#[gpui::test]
19723async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19724    init_test(cx, |_| {});
19725
19726    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19727    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19728
19729    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19730    let multi_buffer = cx.new(|cx| {
19731        let mut multibuffer = MultiBuffer::new(ReadWrite);
19732        multibuffer.push_excerpts(
19733            buffer.clone(),
19734            [
19735                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19736                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19737                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19738            ],
19739            cx,
19740        );
19741        multibuffer
19742    });
19743
19744    let editor =
19745        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19746    editor
19747        .update(cx, |editor, _window, cx| {
19748            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19749            editor
19750                .buffer
19751                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19752        })
19753        .unwrap();
19754
19755    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19756    cx.run_until_parked();
19757
19758    cx.update_editor(|editor, window, cx| {
19759        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19760    });
19761    cx.executor().run_until_parked();
19762
19763    // When the start of a hunk coincides with the start of its excerpt,
19764    // the hunk is expanded. When the start of a hunk is earlier than
19765    // the start of its excerpt, the hunk is not expanded.
19766    cx.assert_state_with_diff(
19767        "
19768            ˇaaa
19769          - bbb
19770          + BBB
19771
19772          - ddd
19773          - eee
19774          + DDD
19775          + EEE
19776            fff
19777
19778            iii
19779        "
19780        .unindent(),
19781    );
19782}
19783
19784#[gpui::test]
19785async fn test_edits_around_expanded_insertion_hunks(
19786    executor: BackgroundExecutor,
19787    cx: &mut TestAppContext,
19788) {
19789    init_test(cx, |_| {});
19790
19791    let mut cx = EditorTestContext::new(cx).await;
19792
19793    let diff_base = r#"
19794        use some::mod1;
19795        use some::mod2;
19796
19797        const A: u32 = 42;
19798
19799        fn main() {
19800            println!("hello");
19801
19802            println!("world");
19803        }
19804        "#
19805    .unindent();
19806    executor.run_until_parked();
19807    cx.set_state(
19808        &r#"
19809        use some::mod1;
19810        use some::mod2;
19811
19812        const A: u32 = 42;
19813        const B: u32 = 42;
19814        const C: u32 = 42;
19815        ˇ
19816
19817        fn main() {
19818            println!("hello");
19819
19820            println!("world");
19821        }
19822        "#
19823        .unindent(),
19824    );
19825
19826    cx.set_head_text(&diff_base);
19827    executor.run_until_parked();
19828
19829    cx.update_editor(|editor, window, cx| {
19830        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19831    });
19832    executor.run_until_parked();
19833
19834    cx.assert_state_with_diff(
19835        r#"
19836        use some::mod1;
19837        use some::mod2;
19838
19839        const A: u32 = 42;
19840      + const B: u32 = 42;
19841      + const C: u32 = 42;
19842      + ˇ
19843
19844        fn main() {
19845            println!("hello");
19846
19847            println!("world");
19848        }
19849      "#
19850        .unindent(),
19851    );
19852
19853    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19854    executor.run_until_parked();
19855
19856    cx.assert_state_with_diff(
19857        r#"
19858        use some::mod1;
19859        use some::mod2;
19860
19861        const A: u32 = 42;
19862      + const B: u32 = 42;
19863      + const C: u32 = 42;
19864      + const D: u32 = 42;
19865      + ˇ
19866
19867        fn main() {
19868            println!("hello");
19869
19870            println!("world");
19871        }
19872      "#
19873        .unindent(),
19874    );
19875
19876    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19877    executor.run_until_parked();
19878
19879    cx.assert_state_with_diff(
19880        r#"
19881        use some::mod1;
19882        use some::mod2;
19883
19884        const A: u32 = 42;
19885      + const B: u32 = 42;
19886      + const C: u32 = 42;
19887      + const D: u32 = 42;
19888      + const E: u32 = 42;
19889      + ˇ
19890
19891        fn main() {
19892            println!("hello");
19893
19894            println!("world");
19895        }
19896      "#
19897        .unindent(),
19898    );
19899
19900    cx.update_editor(|editor, window, cx| {
19901        editor.delete_line(&DeleteLine, window, cx);
19902    });
19903    executor.run_until_parked();
19904
19905    cx.assert_state_with_diff(
19906        r#"
19907        use some::mod1;
19908        use some::mod2;
19909
19910        const A: u32 = 42;
19911      + const B: u32 = 42;
19912      + const C: u32 = 42;
19913      + const D: u32 = 42;
19914      + const E: u32 = 42;
19915        ˇ
19916        fn main() {
19917            println!("hello");
19918
19919            println!("world");
19920        }
19921      "#
19922        .unindent(),
19923    );
19924
19925    cx.update_editor(|editor, window, cx| {
19926        editor.move_up(&MoveUp, window, cx);
19927        editor.delete_line(&DeleteLine, window, cx);
19928        editor.move_up(&MoveUp, window, cx);
19929        editor.delete_line(&DeleteLine, window, cx);
19930        editor.move_up(&MoveUp, window, cx);
19931        editor.delete_line(&DeleteLine, window, cx);
19932    });
19933    executor.run_until_parked();
19934    cx.assert_state_with_diff(
19935        r#"
19936        use some::mod1;
19937        use some::mod2;
19938
19939        const A: u32 = 42;
19940      + const B: u32 = 42;
19941        ˇ
19942        fn main() {
19943            println!("hello");
19944
19945            println!("world");
19946        }
19947      "#
19948        .unindent(),
19949    );
19950
19951    cx.update_editor(|editor, window, cx| {
19952        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19953        editor.delete_line(&DeleteLine, window, cx);
19954    });
19955    executor.run_until_parked();
19956    cx.assert_state_with_diff(
19957        r#"
19958        ˇ
19959        fn main() {
19960            println!("hello");
19961
19962            println!("world");
19963        }
19964      "#
19965        .unindent(),
19966    );
19967}
19968
19969#[gpui::test]
19970async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19971    init_test(cx, |_| {});
19972
19973    let mut cx = EditorTestContext::new(cx).await;
19974    cx.set_head_text(indoc! { "
19975        one
19976        two
19977        three
19978        four
19979        five
19980        "
19981    });
19982    cx.set_state(indoc! { "
19983        one
19984        ˇthree
19985        five
19986    "});
19987    cx.run_until_parked();
19988    cx.update_editor(|editor, window, cx| {
19989        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19990    });
19991    cx.assert_state_with_diff(
19992        indoc! { "
19993        one
19994      - two
19995        ˇthree
19996      - four
19997        five
19998    "}
19999        .to_string(),
20000    );
20001    cx.update_editor(|editor, window, cx| {
20002        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20003    });
20004
20005    cx.assert_state_with_diff(
20006        indoc! { "
20007        one
20008        ˇthree
20009        five
20010    "}
20011        .to_string(),
20012    );
20013
20014    cx.set_state(indoc! { "
20015        one
20016        ˇTWO
20017        three
20018        four
20019        five
20020    "});
20021    cx.run_until_parked();
20022    cx.update_editor(|editor, window, cx| {
20023        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20024    });
20025
20026    cx.assert_state_with_diff(
20027        indoc! { "
20028            one
20029          - two
20030          + ˇTWO
20031            three
20032            four
20033            five
20034        "}
20035        .to_string(),
20036    );
20037    cx.update_editor(|editor, window, cx| {
20038        editor.move_up(&Default::default(), window, cx);
20039        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20040    });
20041    cx.assert_state_with_diff(
20042        indoc! { "
20043            one
20044            ˇTWO
20045            three
20046            four
20047            five
20048        "}
20049        .to_string(),
20050    );
20051}
20052
20053#[gpui::test]
20054async fn test_edits_around_expanded_deletion_hunks(
20055    executor: BackgroundExecutor,
20056    cx: &mut TestAppContext,
20057) {
20058    init_test(cx, |_| {});
20059
20060    let mut cx = EditorTestContext::new(cx).await;
20061
20062    let diff_base = r#"
20063        use some::mod1;
20064        use some::mod2;
20065
20066        const A: u32 = 42;
20067        const B: u32 = 42;
20068        const C: u32 = 42;
20069
20070
20071        fn main() {
20072            println!("hello");
20073
20074            println!("world");
20075        }
20076    "#
20077    .unindent();
20078    executor.run_until_parked();
20079    cx.set_state(
20080        &r#"
20081        use some::mod1;
20082        use some::mod2;
20083
20084        ˇconst B: u32 = 42;
20085        const C: u32 = 42;
20086
20087
20088        fn main() {
20089            println!("hello");
20090
20091            println!("world");
20092        }
20093        "#
20094        .unindent(),
20095    );
20096
20097    cx.set_head_text(&diff_base);
20098    executor.run_until_parked();
20099
20100    cx.update_editor(|editor, window, cx| {
20101        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20102    });
20103    executor.run_until_parked();
20104
20105    cx.assert_state_with_diff(
20106        r#"
20107        use some::mod1;
20108        use some::mod2;
20109
20110      - const A: u32 = 42;
20111        ˇconst B: u32 = 42;
20112        const C: u32 = 42;
20113
20114
20115        fn main() {
20116            println!("hello");
20117
20118            println!("world");
20119        }
20120      "#
20121        .unindent(),
20122    );
20123
20124    cx.update_editor(|editor, window, cx| {
20125        editor.delete_line(&DeleteLine, window, cx);
20126    });
20127    executor.run_until_parked();
20128    cx.assert_state_with_diff(
20129        r#"
20130        use some::mod1;
20131        use some::mod2;
20132
20133      - const A: u32 = 42;
20134      - const B: u32 = 42;
20135        ˇconst C: u32 = 42;
20136
20137
20138        fn main() {
20139            println!("hello");
20140
20141            println!("world");
20142        }
20143      "#
20144        .unindent(),
20145    );
20146
20147    cx.update_editor(|editor, window, cx| {
20148        editor.delete_line(&DeleteLine, window, cx);
20149    });
20150    executor.run_until_parked();
20151    cx.assert_state_with_diff(
20152        r#"
20153        use some::mod1;
20154        use some::mod2;
20155
20156      - const A: u32 = 42;
20157      - const B: u32 = 42;
20158      - const C: u32 = 42;
20159        ˇ
20160
20161        fn main() {
20162            println!("hello");
20163
20164            println!("world");
20165        }
20166      "#
20167        .unindent(),
20168    );
20169
20170    cx.update_editor(|editor, window, cx| {
20171        editor.handle_input("replacement", window, cx);
20172    });
20173    executor.run_until_parked();
20174    cx.assert_state_with_diff(
20175        r#"
20176        use some::mod1;
20177        use some::mod2;
20178
20179      - const A: u32 = 42;
20180      - const B: u32 = 42;
20181      - const C: u32 = 42;
20182      -
20183      + replacementˇ
20184
20185        fn main() {
20186            println!("hello");
20187
20188            println!("world");
20189        }
20190      "#
20191        .unindent(),
20192    );
20193}
20194
20195#[gpui::test]
20196async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20197    init_test(cx, |_| {});
20198
20199    let mut cx = EditorTestContext::new(cx).await;
20200
20201    let base_text = r#"
20202        one
20203        two
20204        three
20205        four
20206        five
20207    "#
20208    .unindent();
20209    executor.run_until_parked();
20210    cx.set_state(
20211        &r#"
20212        one
20213        two
20214        fˇour
20215        five
20216        "#
20217        .unindent(),
20218    );
20219
20220    cx.set_head_text(&base_text);
20221    executor.run_until_parked();
20222
20223    cx.update_editor(|editor, window, cx| {
20224        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20225    });
20226    executor.run_until_parked();
20227
20228    cx.assert_state_with_diff(
20229        r#"
20230          one
20231          two
20232        - three
20233          fˇour
20234          five
20235        "#
20236        .unindent(),
20237    );
20238
20239    cx.update_editor(|editor, window, cx| {
20240        editor.backspace(&Backspace, window, cx);
20241        editor.backspace(&Backspace, window, cx);
20242    });
20243    executor.run_until_parked();
20244    cx.assert_state_with_diff(
20245        r#"
20246          one
20247          two
20248        - threeˇ
20249        - four
20250        + our
20251          five
20252        "#
20253        .unindent(),
20254    );
20255}
20256
20257#[gpui::test]
20258async fn test_edit_after_expanded_modification_hunk(
20259    executor: BackgroundExecutor,
20260    cx: &mut TestAppContext,
20261) {
20262    init_test(cx, |_| {});
20263
20264    let mut cx = EditorTestContext::new(cx).await;
20265
20266    let diff_base = r#"
20267        use some::mod1;
20268        use some::mod2;
20269
20270        const A: u32 = 42;
20271        const B: u32 = 42;
20272        const C: u32 = 42;
20273        const D: u32 = 42;
20274
20275
20276        fn main() {
20277            println!("hello");
20278
20279            println!("world");
20280        }"#
20281    .unindent();
20282
20283    cx.set_state(
20284        &r#"
20285        use some::mod1;
20286        use some::mod2;
20287
20288        const A: u32 = 42;
20289        const B: u32 = 42;
20290        const C: u32 = 43ˇ
20291        const D: u32 = 42;
20292
20293
20294        fn main() {
20295            println!("hello");
20296
20297            println!("world");
20298        }"#
20299        .unindent(),
20300    );
20301
20302    cx.set_head_text(&diff_base);
20303    executor.run_until_parked();
20304    cx.update_editor(|editor, window, cx| {
20305        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20306    });
20307    executor.run_until_parked();
20308
20309    cx.assert_state_with_diff(
20310        r#"
20311        use some::mod1;
20312        use some::mod2;
20313
20314        const A: u32 = 42;
20315        const B: u32 = 42;
20316      - const C: u32 = 42;
20317      + const C: u32 = 43ˇ
20318        const D: u32 = 42;
20319
20320
20321        fn main() {
20322            println!("hello");
20323
20324            println!("world");
20325        }"#
20326        .unindent(),
20327    );
20328
20329    cx.update_editor(|editor, window, cx| {
20330        editor.handle_input("\nnew_line\n", window, cx);
20331    });
20332    executor.run_until_parked();
20333
20334    cx.assert_state_with_diff(
20335        r#"
20336        use some::mod1;
20337        use some::mod2;
20338
20339        const A: u32 = 42;
20340        const B: u32 = 42;
20341      - const C: u32 = 42;
20342      + const C: u32 = 43
20343      + new_line
20344      + ˇ
20345        const D: u32 = 42;
20346
20347
20348        fn main() {
20349            println!("hello");
20350
20351            println!("world");
20352        }"#
20353        .unindent(),
20354    );
20355}
20356
20357#[gpui::test]
20358async fn test_stage_and_unstage_added_file_hunk(
20359    executor: BackgroundExecutor,
20360    cx: &mut TestAppContext,
20361) {
20362    init_test(cx, |_| {});
20363
20364    let mut cx = EditorTestContext::new(cx).await;
20365    cx.update_editor(|editor, _, cx| {
20366        editor.set_expand_all_diff_hunks(cx);
20367    });
20368
20369    let working_copy = r#"
20370            ˇfn main() {
20371                println!("hello, world!");
20372            }
20373        "#
20374    .unindent();
20375
20376    cx.set_state(&working_copy);
20377    executor.run_until_parked();
20378
20379    cx.assert_state_with_diff(
20380        r#"
20381            + ˇfn main() {
20382            +     println!("hello, world!");
20383            + }
20384        "#
20385        .unindent(),
20386    );
20387    cx.assert_index_text(None);
20388
20389    cx.update_editor(|editor, window, cx| {
20390        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20391    });
20392    executor.run_until_parked();
20393    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20394    cx.assert_state_with_diff(
20395        r#"
20396            + ˇfn main() {
20397            +     println!("hello, world!");
20398            + }
20399        "#
20400        .unindent(),
20401    );
20402
20403    cx.update_editor(|editor, window, cx| {
20404        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20405    });
20406    executor.run_until_parked();
20407    cx.assert_index_text(None);
20408}
20409
20410async fn setup_indent_guides_editor(
20411    text: &str,
20412    cx: &mut TestAppContext,
20413) -> (BufferId, EditorTestContext) {
20414    init_test(cx, |_| {});
20415
20416    let mut cx = EditorTestContext::new(cx).await;
20417
20418    let buffer_id = cx.update_editor(|editor, window, cx| {
20419        editor.set_text(text, window, cx);
20420        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20421
20422        buffer_ids[0]
20423    });
20424
20425    (buffer_id, cx)
20426}
20427
20428fn assert_indent_guides(
20429    range: Range<u32>,
20430    expected: Vec<IndentGuide>,
20431    active_indices: Option<Vec<usize>>,
20432    cx: &mut EditorTestContext,
20433) {
20434    let indent_guides = cx.update_editor(|editor, window, cx| {
20435        let snapshot = editor.snapshot(window, cx).display_snapshot;
20436        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20437            editor,
20438            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20439            true,
20440            &snapshot,
20441            cx,
20442        );
20443
20444        indent_guides.sort_by(|a, b| {
20445            a.depth.cmp(&b.depth).then(
20446                a.start_row
20447                    .cmp(&b.start_row)
20448                    .then(a.end_row.cmp(&b.end_row)),
20449            )
20450        });
20451        indent_guides
20452    });
20453
20454    if let Some(expected) = active_indices {
20455        let active_indices = cx.update_editor(|editor, window, cx| {
20456            let snapshot = editor.snapshot(window, cx).display_snapshot;
20457            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20458        });
20459
20460        assert_eq!(
20461            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20462            expected,
20463            "Active indent guide indices do not match"
20464        );
20465    }
20466
20467    assert_eq!(indent_guides, expected, "Indent guides do not match");
20468}
20469
20470fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20471    IndentGuide {
20472        buffer_id,
20473        start_row: MultiBufferRow(start_row),
20474        end_row: MultiBufferRow(end_row),
20475        depth,
20476        tab_size: 4,
20477        settings: IndentGuideSettings {
20478            enabled: true,
20479            line_width: 1,
20480            active_line_width: 1,
20481            coloring: IndentGuideColoring::default(),
20482            background_coloring: IndentGuideBackgroundColoring::default(),
20483        },
20484    }
20485}
20486
20487#[gpui::test]
20488async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20489    let (buffer_id, mut cx) = setup_indent_guides_editor(
20490        &"
20491        fn main() {
20492            let a = 1;
20493        }"
20494        .unindent(),
20495        cx,
20496    )
20497    .await;
20498
20499    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20500}
20501
20502#[gpui::test]
20503async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20504    let (buffer_id, mut cx) = setup_indent_guides_editor(
20505        &"
20506        fn main() {
20507            let a = 1;
20508            let b = 2;
20509        }"
20510        .unindent(),
20511        cx,
20512    )
20513    .await;
20514
20515    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20516}
20517
20518#[gpui::test]
20519async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20520    let (buffer_id, mut cx) = setup_indent_guides_editor(
20521        &"
20522        fn main() {
20523            let a = 1;
20524            if a == 3 {
20525                let b = 2;
20526            } else {
20527                let c = 3;
20528            }
20529        }"
20530        .unindent(),
20531        cx,
20532    )
20533    .await;
20534
20535    assert_indent_guides(
20536        0..8,
20537        vec![
20538            indent_guide(buffer_id, 1, 6, 0),
20539            indent_guide(buffer_id, 3, 3, 1),
20540            indent_guide(buffer_id, 5, 5, 1),
20541        ],
20542        None,
20543        &mut cx,
20544    );
20545}
20546
20547#[gpui::test]
20548async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20549    let (buffer_id, mut cx) = setup_indent_guides_editor(
20550        &"
20551        fn main() {
20552            let a = 1;
20553                let b = 2;
20554            let c = 3;
20555        }"
20556        .unindent(),
20557        cx,
20558    )
20559    .await;
20560
20561    assert_indent_guides(
20562        0..5,
20563        vec![
20564            indent_guide(buffer_id, 1, 3, 0),
20565            indent_guide(buffer_id, 2, 2, 1),
20566        ],
20567        None,
20568        &mut cx,
20569    );
20570}
20571
20572#[gpui::test]
20573async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20574    let (buffer_id, mut cx) = setup_indent_guides_editor(
20575        &"
20576        fn main() {
20577            let a = 1;
20578
20579            let c = 3;
20580        }"
20581        .unindent(),
20582        cx,
20583    )
20584    .await;
20585
20586    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20587}
20588
20589#[gpui::test]
20590async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20591    let (buffer_id, mut cx) = setup_indent_guides_editor(
20592        &"
20593        fn main() {
20594            let a = 1;
20595
20596            let c = 3;
20597
20598            if a == 3 {
20599                let b = 2;
20600            } else {
20601                let c = 3;
20602            }
20603        }"
20604        .unindent(),
20605        cx,
20606    )
20607    .await;
20608
20609    assert_indent_guides(
20610        0..11,
20611        vec![
20612            indent_guide(buffer_id, 1, 9, 0),
20613            indent_guide(buffer_id, 6, 6, 1),
20614            indent_guide(buffer_id, 8, 8, 1),
20615        ],
20616        None,
20617        &mut cx,
20618    );
20619}
20620
20621#[gpui::test]
20622async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20623    let (buffer_id, mut cx) = setup_indent_guides_editor(
20624        &"
20625        fn main() {
20626            let a = 1;
20627
20628            let c = 3;
20629
20630            if a == 3 {
20631                let b = 2;
20632            } else {
20633                let c = 3;
20634            }
20635        }"
20636        .unindent(),
20637        cx,
20638    )
20639    .await;
20640
20641    assert_indent_guides(
20642        1..11,
20643        vec![
20644            indent_guide(buffer_id, 1, 9, 0),
20645            indent_guide(buffer_id, 6, 6, 1),
20646            indent_guide(buffer_id, 8, 8, 1),
20647        ],
20648        None,
20649        &mut cx,
20650    );
20651}
20652
20653#[gpui::test]
20654async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20655    let (buffer_id, mut cx) = setup_indent_guides_editor(
20656        &"
20657        fn main() {
20658            let a = 1;
20659
20660            let c = 3;
20661
20662            if a == 3 {
20663                let b = 2;
20664            } else {
20665                let c = 3;
20666            }
20667        }"
20668        .unindent(),
20669        cx,
20670    )
20671    .await;
20672
20673    assert_indent_guides(
20674        1..10,
20675        vec![
20676            indent_guide(buffer_id, 1, 9, 0),
20677            indent_guide(buffer_id, 6, 6, 1),
20678            indent_guide(buffer_id, 8, 8, 1),
20679        ],
20680        None,
20681        &mut cx,
20682    );
20683}
20684
20685#[gpui::test]
20686async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20687    let (buffer_id, mut cx) = setup_indent_guides_editor(
20688        &"
20689        fn main() {
20690            if a {
20691                b(
20692                    c,
20693                    d,
20694                )
20695            } else {
20696                e(
20697                    f
20698                )
20699            }
20700        }"
20701        .unindent(),
20702        cx,
20703    )
20704    .await;
20705
20706    assert_indent_guides(
20707        0..11,
20708        vec![
20709            indent_guide(buffer_id, 1, 10, 0),
20710            indent_guide(buffer_id, 2, 5, 1),
20711            indent_guide(buffer_id, 7, 9, 1),
20712            indent_guide(buffer_id, 3, 4, 2),
20713            indent_guide(buffer_id, 8, 8, 2),
20714        ],
20715        None,
20716        &mut cx,
20717    );
20718
20719    cx.update_editor(|editor, window, cx| {
20720        editor.fold_at(MultiBufferRow(2), window, cx);
20721        assert_eq!(
20722            editor.display_text(cx),
20723            "
20724            fn main() {
20725                if a {
20726                    b(⋯
20727                    )
20728                } else {
20729                    e(
20730                        f
20731                    )
20732                }
20733            }"
20734            .unindent()
20735        );
20736    });
20737
20738    assert_indent_guides(
20739        0..11,
20740        vec![
20741            indent_guide(buffer_id, 1, 10, 0),
20742            indent_guide(buffer_id, 2, 5, 1),
20743            indent_guide(buffer_id, 7, 9, 1),
20744            indent_guide(buffer_id, 8, 8, 2),
20745        ],
20746        None,
20747        &mut cx,
20748    );
20749}
20750
20751#[gpui::test]
20752async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20753    let (buffer_id, mut cx) = setup_indent_guides_editor(
20754        &"
20755        block1
20756            block2
20757                block3
20758                    block4
20759            block2
20760        block1
20761        block1"
20762            .unindent(),
20763        cx,
20764    )
20765    .await;
20766
20767    assert_indent_guides(
20768        1..10,
20769        vec![
20770            indent_guide(buffer_id, 1, 4, 0),
20771            indent_guide(buffer_id, 2, 3, 1),
20772            indent_guide(buffer_id, 3, 3, 2),
20773        ],
20774        None,
20775        &mut cx,
20776    );
20777}
20778
20779#[gpui::test]
20780async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20781    let (buffer_id, mut cx) = setup_indent_guides_editor(
20782        &"
20783        block1
20784            block2
20785                block3
20786
20787        block1
20788        block1"
20789            .unindent(),
20790        cx,
20791    )
20792    .await;
20793
20794    assert_indent_guides(
20795        0..6,
20796        vec![
20797            indent_guide(buffer_id, 1, 2, 0),
20798            indent_guide(buffer_id, 2, 2, 1),
20799        ],
20800        None,
20801        &mut cx,
20802    );
20803}
20804
20805#[gpui::test]
20806async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20807    let (buffer_id, mut cx) = setup_indent_guides_editor(
20808        &"
20809        function component() {
20810        \treturn (
20811        \t\t\t
20812        \t\t<div>
20813        \t\t\t<abc></abc>
20814        \t\t</div>
20815        \t)
20816        }"
20817        .unindent(),
20818        cx,
20819    )
20820    .await;
20821
20822    assert_indent_guides(
20823        0..8,
20824        vec![
20825            indent_guide(buffer_id, 1, 6, 0),
20826            indent_guide(buffer_id, 2, 5, 1),
20827            indent_guide(buffer_id, 4, 4, 2),
20828        ],
20829        None,
20830        &mut cx,
20831    );
20832}
20833
20834#[gpui::test]
20835async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20836    let (buffer_id, mut cx) = setup_indent_guides_editor(
20837        &"
20838        function component() {
20839        \treturn (
20840        \t
20841        \t\t<div>
20842        \t\t\t<abc></abc>
20843        \t\t</div>
20844        \t)
20845        }"
20846        .unindent(),
20847        cx,
20848    )
20849    .await;
20850
20851    assert_indent_guides(
20852        0..8,
20853        vec![
20854            indent_guide(buffer_id, 1, 6, 0),
20855            indent_guide(buffer_id, 2, 5, 1),
20856            indent_guide(buffer_id, 4, 4, 2),
20857        ],
20858        None,
20859        &mut cx,
20860    );
20861}
20862
20863#[gpui::test]
20864async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20865    let (buffer_id, mut cx) = setup_indent_guides_editor(
20866        &"
20867        block1
20868
20869
20870
20871            block2
20872        "
20873        .unindent(),
20874        cx,
20875    )
20876    .await;
20877
20878    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20879}
20880
20881#[gpui::test]
20882async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20883    let (buffer_id, mut cx) = setup_indent_guides_editor(
20884        &"
20885        def a:
20886        \tb = 3
20887        \tif True:
20888        \t\tc = 4
20889        \t\td = 5
20890        \tprint(b)
20891        "
20892        .unindent(),
20893        cx,
20894    )
20895    .await;
20896
20897    assert_indent_guides(
20898        0..6,
20899        vec![
20900            indent_guide(buffer_id, 1, 5, 0),
20901            indent_guide(buffer_id, 3, 4, 1),
20902        ],
20903        None,
20904        &mut cx,
20905    );
20906}
20907
20908#[gpui::test]
20909async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20910    let (buffer_id, mut cx) = setup_indent_guides_editor(
20911        &"
20912    fn main() {
20913        let a = 1;
20914    }"
20915        .unindent(),
20916        cx,
20917    )
20918    .await;
20919
20920    cx.update_editor(|editor, window, cx| {
20921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20922            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20923        });
20924    });
20925
20926    assert_indent_guides(
20927        0..3,
20928        vec![indent_guide(buffer_id, 1, 1, 0)],
20929        Some(vec![0]),
20930        &mut cx,
20931    );
20932}
20933
20934#[gpui::test]
20935async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20936    let (buffer_id, mut cx) = setup_indent_guides_editor(
20937        &"
20938    fn main() {
20939        if 1 == 2 {
20940            let a = 1;
20941        }
20942    }"
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..4,
20956        vec![
20957            indent_guide(buffer_id, 1, 3, 0),
20958            indent_guide(buffer_id, 2, 2, 1),
20959        ],
20960        Some(vec![1]),
20961        &mut cx,
20962    );
20963
20964    cx.update_editor(|editor, window, cx| {
20965        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20966            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20967        });
20968    });
20969
20970    assert_indent_guides(
20971        0..4,
20972        vec![
20973            indent_guide(buffer_id, 1, 3, 0),
20974            indent_guide(buffer_id, 2, 2, 1),
20975        ],
20976        Some(vec![1]),
20977        &mut cx,
20978    );
20979
20980    cx.update_editor(|editor, window, cx| {
20981        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20982            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20983        });
20984    });
20985
20986    assert_indent_guides(
20987        0..4,
20988        vec![
20989            indent_guide(buffer_id, 1, 3, 0),
20990            indent_guide(buffer_id, 2, 2, 1),
20991        ],
20992        Some(vec![0]),
20993        &mut cx,
20994    );
20995}
20996
20997#[gpui::test]
20998async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20999    let (buffer_id, mut cx) = setup_indent_guides_editor(
21000        &"
21001    fn main() {
21002        let a = 1;
21003
21004        let b = 2;
21005    }"
21006        .unindent(),
21007        cx,
21008    )
21009    .await;
21010
21011    cx.update_editor(|editor, window, cx| {
21012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21013            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21014        });
21015    });
21016
21017    assert_indent_guides(
21018        0..5,
21019        vec![indent_guide(buffer_id, 1, 3, 0)],
21020        Some(vec![0]),
21021        &mut cx,
21022    );
21023}
21024
21025#[gpui::test]
21026async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21027    let (buffer_id, mut cx) = setup_indent_guides_editor(
21028        &"
21029    def m:
21030        a = 1
21031        pass"
21032            .unindent(),
21033        cx,
21034    )
21035    .await;
21036
21037    cx.update_editor(|editor, window, cx| {
21038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21039            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21040        });
21041    });
21042
21043    assert_indent_guides(
21044        0..3,
21045        vec![indent_guide(buffer_id, 1, 2, 0)],
21046        Some(vec![0]),
21047        &mut cx,
21048    );
21049}
21050
21051#[gpui::test]
21052async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21053    init_test(cx, |_| {});
21054    let mut cx = EditorTestContext::new(cx).await;
21055    let text = indoc! {
21056        "
21057        impl A {
21058            fn b() {
21059                0;
21060                3;
21061                5;
21062                6;
21063                7;
21064            }
21065        }
21066        "
21067    };
21068    let base_text = indoc! {
21069        "
21070        impl A {
21071            fn b() {
21072                0;
21073                1;
21074                2;
21075                3;
21076                4;
21077            }
21078            fn c() {
21079                5;
21080                6;
21081                7;
21082            }
21083        }
21084        "
21085    };
21086
21087    cx.update_editor(|editor, window, cx| {
21088        editor.set_text(text, window, cx);
21089
21090        editor.buffer().update(cx, |multibuffer, cx| {
21091            let buffer = multibuffer.as_singleton().unwrap();
21092            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21093
21094            multibuffer.set_all_diff_hunks_expanded(cx);
21095            multibuffer.add_diff(diff, cx);
21096
21097            buffer.read(cx).remote_id()
21098        })
21099    });
21100    cx.run_until_parked();
21101
21102    cx.assert_state_with_diff(
21103        indoc! { "
21104          impl A {
21105              fn b() {
21106                  0;
21107        -         1;
21108        -         2;
21109                  3;
21110        -         4;
21111        -     }
21112        -     fn c() {
21113                  5;
21114                  6;
21115                  7;
21116              }
21117          }
21118          ˇ"
21119        }
21120        .to_string(),
21121    );
21122
21123    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21124        editor
21125            .snapshot(window, cx)
21126            .buffer_snapshot()
21127            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21128            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21129            .collect::<Vec<_>>()
21130    });
21131    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21132    assert_eq!(
21133        actual_guides,
21134        vec![
21135            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21136            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21137            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21138        ]
21139    );
21140}
21141
21142#[gpui::test]
21143async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21144    init_test(cx, |_| {});
21145    let mut cx = EditorTestContext::new(cx).await;
21146
21147    let diff_base = r#"
21148        a
21149        b
21150        c
21151        "#
21152    .unindent();
21153
21154    cx.set_state(
21155        &r#"
21156        ˇA
21157        b
21158        C
21159        "#
21160        .unindent(),
21161    );
21162    cx.set_head_text(&diff_base);
21163    cx.update_editor(|editor, window, cx| {
21164        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21165    });
21166    executor.run_until_parked();
21167
21168    let both_hunks_expanded = r#"
21169        - a
21170        + ˇA
21171          b
21172        - c
21173        + C
21174        "#
21175    .unindent();
21176
21177    cx.assert_state_with_diff(both_hunks_expanded.clone());
21178
21179    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21180        let snapshot = editor.snapshot(window, cx);
21181        let hunks = editor
21182            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21183            .collect::<Vec<_>>();
21184        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21185        let buffer_id = hunks[0].buffer_id;
21186        hunks
21187            .into_iter()
21188            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21189            .collect::<Vec<_>>()
21190    });
21191    assert_eq!(hunk_ranges.len(), 2);
21192
21193    cx.update_editor(|editor, _, cx| {
21194        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21195    });
21196    executor.run_until_parked();
21197
21198    let second_hunk_expanded = r#"
21199          ˇA
21200          b
21201        - c
21202        + C
21203        "#
21204    .unindent();
21205
21206    cx.assert_state_with_diff(second_hunk_expanded);
21207
21208    cx.update_editor(|editor, _, cx| {
21209        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21210    });
21211    executor.run_until_parked();
21212
21213    cx.assert_state_with_diff(both_hunks_expanded.clone());
21214
21215    cx.update_editor(|editor, _, cx| {
21216        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21217    });
21218    executor.run_until_parked();
21219
21220    let first_hunk_expanded = r#"
21221        - a
21222        + ˇA
21223          b
21224          C
21225        "#
21226    .unindent();
21227
21228    cx.assert_state_with_diff(first_hunk_expanded);
21229
21230    cx.update_editor(|editor, _, cx| {
21231        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21232    });
21233    executor.run_until_parked();
21234
21235    cx.assert_state_with_diff(both_hunks_expanded);
21236
21237    cx.set_state(
21238        &r#"
21239        ˇA
21240        b
21241        "#
21242        .unindent(),
21243    );
21244    cx.run_until_parked();
21245
21246    // TODO this cursor position seems bad
21247    cx.assert_state_with_diff(
21248        r#"
21249        - ˇa
21250        + A
21251          b
21252        "#
21253        .unindent(),
21254    );
21255
21256    cx.update_editor(|editor, window, cx| {
21257        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21258    });
21259
21260    cx.assert_state_with_diff(
21261        r#"
21262            - ˇa
21263            + A
21264              b
21265            - c
21266            "#
21267        .unindent(),
21268    );
21269
21270    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21271        let snapshot = editor.snapshot(window, cx);
21272        let hunks = editor
21273            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21274            .collect::<Vec<_>>();
21275        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21276        let buffer_id = hunks[0].buffer_id;
21277        hunks
21278            .into_iter()
21279            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21280            .collect::<Vec<_>>()
21281    });
21282    assert_eq!(hunk_ranges.len(), 2);
21283
21284    cx.update_editor(|editor, _, cx| {
21285        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21286    });
21287    executor.run_until_parked();
21288
21289    cx.assert_state_with_diff(
21290        r#"
21291        - ˇa
21292        + A
21293          b
21294        "#
21295        .unindent(),
21296    );
21297}
21298
21299#[gpui::test]
21300async fn test_toggle_deletion_hunk_at_start_of_file(
21301    executor: BackgroundExecutor,
21302    cx: &mut TestAppContext,
21303) {
21304    init_test(cx, |_| {});
21305    let mut cx = EditorTestContext::new(cx).await;
21306
21307    let diff_base = r#"
21308        a
21309        b
21310        c
21311        "#
21312    .unindent();
21313
21314    cx.set_state(
21315        &r#"
21316        ˇb
21317        c
21318        "#
21319        .unindent(),
21320    );
21321    cx.set_head_text(&diff_base);
21322    cx.update_editor(|editor, window, cx| {
21323        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21324    });
21325    executor.run_until_parked();
21326
21327    let hunk_expanded = r#"
21328        - a
21329          ˇb
21330          c
21331        "#
21332    .unindent();
21333
21334    cx.assert_state_with_diff(hunk_expanded.clone());
21335
21336    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21337        let snapshot = editor.snapshot(window, cx);
21338        let hunks = editor
21339            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21340            .collect::<Vec<_>>();
21341        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21342        let buffer_id = hunks[0].buffer_id;
21343        hunks
21344            .into_iter()
21345            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21346            .collect::<Vec<_>>()
21347    });
21348    assert_eq!(hunk_ranges.len(), 1);
21349
21350    cx.update_editor(|editor, _, cx| {
21351        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21352    });
21353    executor.run_until_parked();
21354
21355    let hunk_collapsed = r#"
21356          ˇb
21357          c
21358        "#
21359    .unindent();
21360
21361    cx.assert_state_with_diff(hunk_collapsed);
21362
21363    cx.update_editor(|editor, _, cx| {
21364        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21365    });
21366    executor.run_until_parked();
21367
21368    cx.assert_state_with_diff(hunk_expanded);
21369}
21370
21371#[gpui::test]
21372async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21373    init_test(cx, |_| {});
21374
21375    let fs = FakeFs::new(cx.executor());
21376    fs.insert_tree(
21377        path!("/test"),
21378        json!({
21379            ".git": {},
21380            "file-1": "ONE\n",
21381            "file-2": "TWO\n",
21382            "file-3": "THREE\n",
21383        }),
21384    )
21385    .await;
21386
21387    fs.set_head_for_repo(
21388        path!("/test/.git").as_ref(),
21389        &[
21390            ("file-1", "one\n".into()),
21391            ("file-2", "two\n".into()),
21392            ("file-3", "three\n".into()),
21393        ],
21394        "deadbeef",
21395    );
21396
21397    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21398    let mut buffers = vec![];
21399    for i in 1..=3 {
21400        let buffer = project
21401            .update(cx, |project, cx| {
21402                let path = format!(path!("/test/file-{}"), i);
21403                project.open_local_buffer(path, cx)
21404            })
21405            .await
21406            .unwrap();
21407        buffers.push(buffer);
21408    }
21409
21410    let multibuffer = cx.new(|cx| {
21411        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21412        multibuffer.set_all_diff_hunks_expanded(cx);
21413        for buffer in &buffers {
21414            let snapshot = buffer.read(cx).snapshot();
21415            multibuffer.set_excerpts_for_path(
21416                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21417                buffer.clone(),
21418                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21419                2,
21420                cx,
21421            );
21422        }
21423        multibuffer
21424    });
21425
21426    let editor = cx.add_window(|window, cx| {
21427        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21428    });
21429    cx.run_until_parked();
21430
21431    let snapshot = editor
21432        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21433        .unwrap();
21434    let hunks = snapshot
21435        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21436        .map(|hunk| match hunk {
21437            DisplayDiffHunk::Unfolded {
21438                display_row_range, ..
21439            } => display_row_range,
21440            DisplayDiffHunk::Folded { .. } => unreachable!(),
21441        })
21442        .collect::<Vec<_>>();
21443    assert_eq!(
21444        hunks,
21445        [
21446            DisplayRow(2)..DisplayRow(4),
21447            DisplayRow(7)..DisplayRow(9),
21448            DisplayRow(12)..DisplayRow(14),
21449        ]
21450    );
21451}
21452
21453#[gpui::test]
21454async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21455    init_test(cx, |_| {});
21456
21457    let mut cx = EditorTestContext::new(cx).await;
21458    cx.set_head_text(indoc! { "
21459        one
21460        two
21461        three
21462        four
21463        five
21464        "
21465    });
21466    cx.set_index_text(indoc! { "
21467        one
21468        two
21469        three
21470        four
21471        five
21472        "
21473    });
21474    cx.set_state(indoc! {"
21475        one
21476        TWO
21477        ˇTHREE
21478        FOUR
21479        five
21480    "});
21481    cx.run_until_parked();
21482    cx.update_editor(|editor, window, cx| {
21483        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21484    });
21485    cx.run_until_parked();
21486    cx.assert_index_text(Some(indoc! {"
21487        one
21488        TWO
21489        THREE
21490        FOUR
21491        five
21492    "}));
21493    cx.set_state(indoc! { "
21494        one
21495        TWO
21496        ˇTHREE-HUNDRED
21497        FOUR
21498        five
21499    "});
21500    cx.run_until_parked();
21501    cx.update_editor(|editor, window, cx| {
21502        let snapshot = editor.snapshot(window, cx);
21503        let hunks = editor
21504            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21505            .collect::<Vec<_>>();
21506        assert_eq!(hunks.len(), 1);
21507        assert_eq!(
21508            hunks[0].status(),
21509            DiffHunkStatus {
21510                kind: DiffHunkStatusKind::Modified,
21511                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21512            }
21513        );
21514
21515        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21516    });
21517    cx.run_until_parked();
21518    cx.assert_index_text(Some(indoc! {"
21519        one
21520        TWO
21521        THREE-HUNDRED
21522        FOUR
21523        five
21524    "}));
21525}
21526
21527#[gpui::test]
21528fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21529    init_test(cx, |_| {});
21530
21531    let editor = cx.add_window(|window, cx| {
21532        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21533        build_editor(buffer, window, cx)
21534    });
21535
21536    let render_args = Arc::new(Mutex::new(None));
21537    let snapshot = editor
21538        .update(cx, |editor, window, cx| {
21539            let snapshot = editor.buffer().read(cx).snapshot(cx);
21540            let range =
21541                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21542
21543            struct RenderArgs {
21544                row: MultiBufferRow,
21545                folded: bool,
21546                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21547            }
21548
21549            let crease = Crease::inline(
21550                range,
21551                FoldPlaceholder::test(),
21552                {
21553                    let toggle_callback = render_args.clone();
21554                    move |row, folded, callback, _window, _cx| {
21555                        *toggle_callback.lock() = Some(RenderArgs {
21556                            row,
21557                            folded,
21558                            callback,
21559                        });
21560                        div()
21561                    }
21562                },
21563                |_row, _folded, _window, _cx| div(),
21564            );
21565
21566            editor.insert_creases(Some(crease), cx);
21567            let snapshot = editor.snapshot(window, cx);
21568            let _div =
21569                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21570            snapshot
21571        })
21572        .unwrap();
21573
21574    let render_args = render_args.lock().take().unwrap();
21575    assert_eq!(render_args.row, MultiBufferRow(1));
21576    assert!(!render_args.folded);
21577    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21578
21579    cx.update_window(*editor, |_, window, cx| {
21580        (render_args.callback)(true, window, cx)
21581    })
21582    .unwrap();
21583    let snapshot = editor
21584        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21585        .unwrap();
21586    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21587
21588    cx.update_window(*editor, |_, window, cx| {
21589        (render_args.callback)(false, window, cx)
21590    })
21591    .unwrap();
21592    let snapshot = editor
21593        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21594        .unwrap();
21595    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21596}
21597
21598#[gpui::test]
21599async fn test_input_text(cx: &mut TestAppContext) {
21600    init_test(cx, |_| {});
21601    let mut cx = EditorTestContext::new(cx).await;
21602
21603    cx.set_state(
21604        &r#"ˇone
21605        two
21606
21607        three
21608        fourˇ
21609        five
21610
21611        siˇx"#
21612            .unindent(),
21613    );
21614
21615    cx.dispatch_action(HandleInput(String::new()));
21616    cx.assert_editor_state(
21617        &r#"ˇone
21618        two
21619
21620        three
21621        fourˇ
21622        five
21623
21624        siˇx"#
21625            .unindent(),
21626    );
21627
21628    cx.dispatch_action(HandleInput("AAAA".to_string()));
21629    cx.assert_editor_state(
21630        &r#"AAAAˇone
21631        two
21632
21633        three
21634        fourAAAAˇ
21635        five
21636
21637        siAAAAˇx"#
21638            .unindent(),
21639    );
21640}
21641
21642#[gpui::test]
21643async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21644    init_test(cx, |_| {});
21645
21646    let mut cx = EditorTestContext::new(cx).await;
21647    cx.set_state(
21648        r#"let foo = 1;
21649let foo = 2;
21650let foo = 3;
21651let fooˇ = 4;
21652let foo = 5;
21653let foo = 6;
21654let foo = 7;
21655let foo = 8;
21656let foo = 9;
21657let foo = 10;
21658let foo = 11;
21659let foo = 12;
21660let foo = 13;
21661let foo = 14;
21662let foo = 15;"#,
21663    );
21664
21665    cx.update_editor(|e, window, cx| {
21666        assert_eq!(
21667            e.next_scroll_position,
21668            NextScrollCursorCenterTopBottom::Center,
21669            "Default next scroll direction is center",
21670        );
21671
21672        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21673        assert_eq!(
21674            e.next_scroll_position,
21675            NextScrollCursorCenterTopBottom::Top,
21676            "After center, next scroll direction should be top",
21677        );
21678
21679        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21680        assert_eq!(
21681            e.next_scroll_position,
21682            NextScrollCursorCenterTopBottom::Bottom,
21683            "After top, next scroll direction should be bottom",
21684        );
21685
21686        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21687        assert_eq!(
21688            e.next_scroll_position,
21689            NextScrollCursorCenterTopBottom::Center,
21690            "After bottom, scrolling should start over",
21691        );
21692
21693        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21694        assert_eq!(
21695            e.next_scroll_position,
21696            NextScrollCursorCenterTopBottom::Top,
21697            "Scrolling continues if retriggered fast enough"
21698        );
21699    });
21700
21701    cx.executor()
21702        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21703    cx.executor().run_until_parked();
21704    cx.update_editor(|e, _, _| {
21705        assert_eq!(
21706            e.next_scroll_position,
21707            NextScrollCursorCenterTopBottom::Center,
21708            "If scrolling is not triggered fast enough, it should reset"
21709        );
21710    });
21711}
21712
21713#[gpui::test]
21714async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21715    init_test(cx, |_| {});
21716    let mut cx = EditorLspTestContext::new_rust(
21717        lsp::ServerCapabilities {
21718            definition_provider: Some(lsp::OneOf::Left(true)),
21719            references_provider: Some(lsp::OneOf::Left(true)),
21720            ..lsp::ServerCapabilities::default()
21721        },
21722        cx,
21723    )
21724    .await;
21725
21726    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21727        let go_to_definition = cx
21728            .lsp
21729            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21730                move |params, _| async move {
21731                    if empty_go_to_definition {
21732                        Ok(None)
21733                    } else {
21734                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21735                            uri: params.text_document_position_params.text_document.uri,
21736                            range: lsp::Range::new(
21737                                lsp::Position::new(4, 3),
21738                                lsp::Position::new(4, 6),
21739                            ),
21740                        })))
21741                    }
21742                },
21743            );
21744        let references = cx
21745            .lsp
21746            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21747                Ok(Some(vec![lsp::Location {
21748                    uri: params.text_document_position.text_document.uri,
21749                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21750                }]))
21751            });
21752        (go_to_definition, references)
21753    };
21754
21755    cx.set_state(
21756        &r#"fn one() {
21757            let mut a = ˇtwo();
21758        }
21759
21760        fn two() {}"#
21761            .unindent(),
21762    );
21763    set_up_lsp_handlers(false, &mut cx);
21764    let navigated = cx
21765        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21766        .await
21767        .expect("Failed to navigate to definition");
21768    assert_eq!(
21769        navigated,
21770        Navigated::Yes,
21771        "Should have navigated to definition from the GetDefinition response"
21772    );
21773    cx.assert_editor_state(
21774        &r#"fn one() {
21775            let mut a = two();
21776        }
21777
21778        fn «twoˇ»() {}"#
21779            .unindent(),
21780    );
21781
21782    let editors = cx.update_workspace(|workspace, _, cx| {
21783        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21784    });
21785    cx.update_editor(|_, _, test_editor_cx| {
21786        assert_eq!(
21787            editors.len(),
21788            1,
21789            "Initially, only one, test, editor should be open in the workspace"
21790        );
21791        assert_eq!(
21792            test_editor_cx.entity(),
21793            editors.last().expect("Asserted len is 1").clone()
21794        );
21795    });
21796
21797    set_up_lsp_handlers(true, &mut cx);
21798    let navigated = cx
21799        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21800        .await
21801        .expect("Failed to navigate to lookup references");
21802    assert_eq!(
21803        navigated,
21804        Navigated::Yes,
21805        "Should have navigated to references as a fallback after empty GoToDefinition response"
21806    );
21807    // We should not change the selections in the existing file,
21808    // if opening another milti buffer with the references
21809    cx.assert_editor_state(
21810        &r#"fn one() {
21811            let mut a = two();
21812        }
21813
21814        fn «twoˇ»() {}"#
21815            .unindent(),
21816    );
21817    let editors = cx.update_workspace(|workspace, _, cx| {
21818        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21819    });
21820    cx.update_editor(|_, _, test_editor_cx| {
21821        assert_eq!(
21822            editors.len(),
21823            2,
21824            "After falling back to references search, we open a new editor with the results"
21825        );
21826        let references_fallback_text = editors
21827            .into_iter()
21828            .find(|new_editor| *new_editor != test_editor_cx.entity())
21829            .expect("Should have one non-test editor now")
21830            .read(test_editor_cx)
21831            .text(test_editor_cx);
21832        assert_eq!(
21833            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21834            "Should use the range from the references response and not the GoToDefinition one"
21835        );
21836    });
21837}
21838
21839#[gpui::test]
21840async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21841    init_test(cx, |_| {});
21842    cx.update(|cx| {
21843        let mut editor_settings = EditorSettings::get_global(cx).clone();
21844        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21845        EditorSettings::override_global(editor_settings, cx);
21846    });
21847    let mut cx = EditorLspTestContext::new_rust(
21848        lsp::ServerCapabilities {
21849            definition_provider: Some(lsp::OneOf::Left(true)),
21850            references_provider: Some(lsp::OneOf::Left(true)),
21851            ..lsp::ServerCapabilities::default()
21852        },
21853        cx,
21854    )
21855    .await;
21856    let original_state = r#"fn one() {
21857        let mut a = ˇtwo();
21858    }
21859
21860    fn two() {}"#
21861        .unindent();
21862    cx.set_state(&original_state);
21863
21864    let mut go_to_definition = cx
21865        .lsp
21866        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21867            move |_, _| async move { Ok(None) },
21868        );
21869    let _references = cx
21870        .lsp
21871        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21872            panic!("Should not call for references with no go to definition fallback")
21873        });
21874
21875    let navigated = cx
21876        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21877        .await
21878        .expect("Failed to navigate to lookup references");
21879    go_to_definition
21880        .next()
21881        .await
21882        .expect("Should have called the go_to_definition handler");
21883
21884    assert_eq!(
21885        navigated,
21886        Navigated::No,
21887        "Should have navigated to references as a fallback after empty GoToDefinition response"
21888    );
21889    cx.assert_editor_state(&original_state);
21890    let editors = cx.update_workspace(|workspace, _, cx| {
21891        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21892    });
21893    cx.update_editor(|_, _, _| {
21894        assert_eq!(
21895            editors.len(),
21896            1,
21897            "After unsuccessful fallback, no other editor should have been opened"
21898        );
21899    });
21900}
21901
21902#[gpui::test]
21903async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21904    init_test(cx, |_| {});
21905    let mut cx = EditorLspTestContext::new_rust(
21906        lsp::ServerCapabilities {
21907            references_provider: Some(lsp::OneOf::Left(true)),
21908            ..lsp::ServerCapabilities::default()
21909        },
21910        cx,
21911    )
21912    .await;
21913
21914    cx.set_state(
21915        &r#"
21916        fn one() {
21917            let mut a = two();
21918        }
21919
21920        fn ˇtwo() {}"#
21921            .unindent(),
21922    );
21923    cx.lsp
21924        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21925            Ok(Some(vec![
21926                lsp::Location {
21927                    uri: params.text_document_position.text_document.uri.clone(),
21928                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21929                },
21930                lsp::Location {
21931                    uri: params.text_document_position.text_document.uri,
21932                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21933                },
21934            ]))
21935        });
21936    let navigated = cx
21937        .update_editor(|editor, window, cx| {
21938            editor.find_all_references(&FindAllReferences, window, cx)
21939        })
21940        .unwrap()
21941        .await
21942        .expect("Failed to navigate to references");
21943    assert_eq!(
21944        navigated,
21945        Navigated::Yes,
21946        "Should have navigated to references from the FindAllReferences response"
21947    );
21948    cx.assert_editor_state(
21949        &r#"fn one() {
21950            let mut a = two();
21951        }
21952
21953        fn ˇtwo() {}"#
21954            .unindent(),
21955    );
21956
21957    let editors = cx.update_workspace(|workspace, _, cx| {
21958        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21959    });
21960    cx.update_editor(|_, _, _| {
21961        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21962    });
21963
21964    cx.set_state(
21965        &r#"fn one() {
21966            let mut a = ˇtwo();
21967        }
21968
21969        fn two() {}"#
21970            .unindent(),
21971    );
21972    let navigated = cx
21973        .update_editor(|editor, window, cx| {
21974            editor.find_all_references(&FindAllReferences, window, cx)
21975        })
21976        .unwrap()
21977        .await
21978        .expect("Failed to navigate to references");
21979    assert_eq!(
21980        navigated,
21981        Navigated::Yes,
21982        "Should have navigated to references from the FindAllReferences response"
21983    );
21984    cx.assert_editor_state(
21985        &r#"fn one() {
21986            let mut a = ˇtwo();
21987        }
21988
21989        fn two() {}"#
21990            .unindent(),
21991    );
21992    let editors = cx.update_workspace(|workspace, _, cx| {
21993        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21994    });
21995    cx.update_editor(|_, _, _| {
21996        assert_eq!(
21997            editors.len(),
21998            2,
21999            "should have re-used the previous multibuffer"
22000        );
22001    });
22002
22003    cx.set_state(
22004        &r#"fn one() {
22005            let mut a = ˇtwo();
22006        }
22007        fn three() {}
22008        fn two() {}"#
22009            .unindent(),
22010    );
22011    cx.lsp
22012        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22013            Ok(Some(vec![
22014                lsp::Location {
22015                    uri: params.text_document_position.text_document.uri.clone(),
22016                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22017                },
22018                lsp::Location {
22019                    uri: params.text_document_position.text_document.uri,
22020                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22021                },
22022            ]))
22023        });
22024    let navigated = cx
22025        .update_editor(|editor, window, cx| {
22026            editor.find_all_references(&FindAllReferences, window, cx)
22027        })
22028        .unwrap()
22029        .await
22030        .expect("Failed to navigate to references");
22031    assert_eq!(
22032        navigated,
22033        Navigated::Yes,
22034        "Should have navigated to references from the FindAllReferences response"
22035    );
22036    cx.assert_editor_state(
22037        &r#"fn one() {
22038                let mut a = ˇtwo();
22039            }
22040            fn three() {}
22041            fn two() {}"#
22042            .unindent(),
22043    );
22044    let editors = cx.update_workspace(|workspace, _, cx| {
22045        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22046    });
22047    cx.update_editor(|_, _, _| {
22048        assert_eq!(
22049            editors.len(),
22050            3,
22051            "should have used a new multibuffer as offsets changed"
22052        );
22053    });
22054}
22055#[gpui::test]
22056async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22057    init_test(cx, |_| {});
22058
22059    let language = Arc::new(Language::new(
22060        LanguageConfig::default(),
22061        Some(tree_sitter_rust::LANGUAGE.into()),
22062    ));
22063
22064    let text = r#"
22065        #[cfg(test)]
22066        mod tests() {
22067            #[test]
22068            fn runnable_1() {
22069                let a = 1;
22070            }
22071
22072            #[test]
22073            fn runnable_2() {
22074                let a = 1;
22075                let b = 2;
22076            }
22077        }
22078    "#
22079    .unindent();
22080
22081    let fs = FakeFs::new(cx.executor());
22082    fs.insert_file("/file.rs", Default::default()).await;
22083
22084    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22085    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22086    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22087    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22088    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22089
22090    let editor = cx.new_window_entity(|window, cx| {
22091        Editor::new(
22092            EditorMode::full(),
22093            multi_buffer,
22094            Some(project.clone()),
22095            window,
22096            cx,
22097        )
22098    });
22099
22100    editor.update_in(cx, |editor, window, cx| {
22101        let snapshot = editor.buffer().read(cx).snapshot(cx);
22102        editor.tasks.insert(
22103            (buffer.read(cx).remote_id(), 3),
22104            RunnableTasks {
22105                templates: vec![],
22106                offset: snapshot.anchor_before(43),
22107                column: 0,
22108                extra_variables: HashMap::default(),
22109                context_range: BufferOffset(43)..BufferOffset(85),
22110            },
22111        );
22112        editor.tasks.insert(
22113            (buffer.read(cx).remote_id(), 8),
22114            RunnableTasks {
22115                templates: vec![],
22116                offset: snapshot.anchor_before(86),
22117                column: 0,
22118                extra_variables: HashMap::default(),
22119                context_range: BufferOffset(86)..BufferOffset(191),
22120            },
22121        );
22122
22123        // Test finding task when cursor is inside function body
22124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22125            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22126        });
22127        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22128        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22129
22130        // Test finding task when cursor is on function name
22131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22132            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22133        });
22134        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22135        assert_eq!(row, 8, "Should find task when cursor is on function name");
22136    });
22137}
22138
22139#[gpui::test]
22140async fn test_folding_buffers(cx: &mut TestAppContext) {
22141    init_test(cx, |_| {});
22142
22143    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22144    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22145    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22146
22147    let fs = FakeFs::new(cx.executor());
22148    fs.insert_tree(
22149        path!("/a"),
22150        json!({
22151            "first.rs": sample_text_1,
22152            "second.rs": sample_text_2,
22153            "third.rs": sample_text_3,
22154        }),
22155    )
22156    .await;
22157    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22159    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22160    let worktree = project.update(cx, |project, cx| {
22161        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22162        assert_eq!(worktrees.len(), 1);
22163        worktrees.pop().unwrap()
22164    });
22165    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22166
22167    let buffer_1 = project
22168        .update(cx, |project, cx| {
22169            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22170        })
22171        .await
22172        .unwrap();
22173    let buffer_2 = project
22174        .update(cx, |project, cx| {
22175            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22176        })
22177        .await
22178        .unwrap();
22179    let buffer_3 = project
22180        .update(cx, |project, cx| {
22181            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22182        })
22183        .await
22184        .unwrap();
22185
22186    let multi_buffer = cx.new(|cx| {
22187        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22188        multi_buffer.push_excerpts(
22189            buffer_1.clone(),
22190            [
22191                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22192                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22193                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22194            ],
22195            cx,
22196        );
22197        multi_buffer.push_excerpts(
22198            buffer_2.clone(),
22199            [
22200                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22201                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22202                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22203            ],
22204            cx,
22205        );
22206        multi_buffer.push_excerpts(
22207            buffer_3.clone(),
22208            [
22209                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22210                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22211                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22212            ],
22213            cx,
22214        );
22215        multi_buffer
22216    });
22217    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22218        Editor::new(
22219            EditorMode::full(),
22220            multi_buffer.clone(),
22221            Some(project.clone()),
22222            window,
22223            cx,
22224        )
22225    });
22226
22227    assert_eq!(
22228        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22229        "\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",
22230    );
22231
22232    multi_buffer_editor.update(cx, |editor, cx| {
22233        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22234    });
22235    assert_eq!(
22236        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22237        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22238        "After folding the first buffer, its text should not be displayed"
22239    );
22240
22241    multi_buffer_editor.update(cx, |editor, cx| {
22242        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22243    });
22244    assert_eq!(
22245        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22246        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22247        "After folding the second buffer, its text should not be displayed"
22248    );
22249
22250    multi_buffer_editor.update(cx, |editor, cx| {
22251        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22252    });
22253    assert_eq!(
22254        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22255        "\n\n\n\n\n",
22256        "After folding the third buffer, its text should not be displayed"
22257    );
22258
22259    // Emulate selection inside the fold logic, that should work
22260    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22261        editor
22262            .snapshot(window, cx)
22263            .next_line_boundary(Point::new(0, 4));
22264    });
22265
22266    multi_buffer_editor.update(cx, |editor, cx| {
22267        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22268    });
22269    assert_eq!(
22270        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22271        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22272        "After unfolding the second buffer, its text should be displayed"
22273    );
22274
22275    // Typing inside of buffer 1 causes that buffer to be unfolded.
22276    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22277        assert_eq!(
22278            multi_buffer
22279                .read(cx)
22280                .snapshot(cx)
22281                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22282                .collect::<String>(),
22283            "bbbb"
22284        );
22285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22286            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22287        });
22288        editor.handle_input("B", window, cx);
22289    });
22290
22291    assert_eq!(
22292        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22293        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22294        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22295    );
22296
22297    multi_buffer_editor.update(cx, |editor, cx| {
22298        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22299    });
22300    assert_eq!(
22301        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22302        "\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",
22303        "After unfolding the all buffers, all original text should be displayed"
22304    );
22305}
22306
22307#[gpui::test]
22308async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22309    init_test(cx, |_| {});
22310
22311    let sample_text_1 = "1111\n2222\n3333".to_string();
22312    let sample_text_2 = "4444\n5555\n6666".to_string();
22313    let sample_text_3 = "7777\n8888\n9999".to_string();
22314
22315    let fs = FakeFs::new(cx.executor());
22316    fs.insert_tree(
22317        path!("/a"),
22318        json!({
22319            "first.rs": sample_text_1,
22320            "second.rs": sample_text_2,
22321            "third.rs": sample_text_3,
22322        }),
22323    )
22324    .await;
22325    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22326    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22327    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22328    let worktree = project.update(cx, |project, cx| {
22329        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22330        assert_eq!(worktrees.len(), 1);
22331        worktrees.pop().unwrap()
22332    });
22333    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22334
22335    let buffer_1 = project
22336        .update(cx, |project, cx| {
22337            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22338        })
22339        .await
22340        .unwrap();
22341    let buffer_2 = project
22342        .update(cx, |project, cx| {
22343            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22344        })
22345        .await
22346        .unwrap();
22347    let buffer_3 = project
22348        .update(cx, |project, cx| {
22349            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22350        })
22351        .await
22352        .unwrap();
22353
22354    let multi_buffer = cx.new(|cx| {
22355        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22356        multi_buffer.push_excerpts(
22357            buffer_1.clone(),
22358            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22359            cx,
22360        );
22361        multi_buffer.push_excerpts(
22362            buffer_2.clone(),
22363            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22364            cx,
22365        );
22366        multi_buffer.push_excerpts(
22367            buffer_3.clone(),
22368            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22369            cx,
22370        );
22371        multi_buffer
22372    });
22373
22374    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22375        Editor::new(
22376            EditorMode::full(),
22377            multi_buffer,
22378            Some(project.clone()),
22379            window,
22380            cx,
22381        )
22382    });
22383
22384    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22385    assert_eq!(
22386        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22387        full_text,
22388    );
22389
22390    multi_buffer_editor.update(cx, |editor, cx| {
22391        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22392    });
22393    assert_eq!(
22394        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22395        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22396        "After folding the first buffer, its text should not be displayed"
22397    );
22398
22399    multi_buffer_editor.update(cx, |editor, cx| {
22400        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22401    });
22402
22403    assert_eq!(
22404        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22405        "\n\n\n\n\n\n7777\n8888\n9999",
22406        "After folding the second buffer, its text should not be displayed"
22407    );
22408
22409    multi_buffer_editor.update(cx, |editor, cx| {
22410        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22411    });
22412    assert_eq!(
22413        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22414        "\n\n\n\n\n",
22415        "After folding the third buffer, its text should not be displayed"
22416    );
22417
22418    multi_buffer_editor.update(cx, |editor, cx| {
22419        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22420    });
22421    assert_eq!(
22422        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22423        "\n\n\n\n4444\n5555\n6666\n\n",
22424        "After unfolding the second buffer, its text should be displayed"
22425    );
22426
22427    multi_buffer_editor.update(cx, |editor, cx| {
22428        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22429    });
22430    assert_eq!(
22431        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22432        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22433        "After unfolding the first buffer, its text should be displayed"
22434    );
22435
22436    multi_buffer_editor.update(cx, |editor, cx| {
22437        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22438    });
22439    assert_eq!(
22440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22441        full_text,
22442        "After unfolding all buffers, all original text should be displayed"
22443    );
22444}
22445
22446#[gpui::test]
22447async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22448    init_test(cx, |_| {});
22449
22450    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22451
22452    let fs = FakeFs::new(cx.executor());
22453    fs.insert_tree(
22454        path!("/a"),
22455        json!({
22456            "main.rs": sample_text,
22457        }),
22458    )
22459    .await;
22460    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22461    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22462    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22463    let worktree = project.update(cx, |project, cx| {
22464        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22465        assert_eq!(worktrees.len(), 1);
22466        worktrees.pop().unwrap()
22467    });
22468    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22469
22470    let buffer_1 = project
22471        .update(cx, |project, cx| {
22472            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22473        })
22474        .await
22475        .unwrap();
22476
22477    let multi_buffer = cx.new(|cx| {
22478        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22479        multi_buffer.push_excerpts(
22480            buffer_1.clone(),
22481            [ExcerptRange::new(
22482                Point::new(0, 0)
22483                    ..Point::new(
22484                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22485                        0,
22486                    ),
22487            )],
22488            cx,
22489        );
22490        multi_buffer
22491    });
22492    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22493        Editor::new(
22494            EditorMode::full(),
22495            multi_buffer,
22496            Some(project.clone()),
22497            window,
22498            cx,
22499        )
22500    });
22501
22502    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22503    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22504        enum TestHighlight {}
22505        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22506        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22507        editor.highlight_text::<TestHighlight>(
22508            vec![highlight_range.clone()],
22509            HighlightStyle::color(Hsla::green()),
22510            cx,
22511        );
22512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22513            s.select_ranges(Some(highlight_range))
22514        });
22515    });
22516
22517    let full_text = format!("\n\n{sample_text}");
22518    assert_eq!(
22519        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22520        full_text,
22521    );
22522}
22523
22524#[gpui::test]
22525async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22526    init_test(cx, |_| {});
22527    cx.update(|cx| {
22528        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22529            "keymaps/default-linux.json",
22530            cx,
22531        )
22532        .unwrap();
22533        cx.bind_keys(default_key_bindings);
22534    });
22535
22536    let (editor, cx) = cx.add_window_view(|window, cx| {
22537        let multi_buffer = MultiBuffer::build_multi(
22538            [
22539                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22540                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22541                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22542                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22543            ],
22544            cx,
22545        );
22546        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22547
22548        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22549        // fold all but the second buffer, so that we test navigating between two
22550        // adjacent folded buffers, as well as folded buffers at the start and
22551        // end the multibuffer
22552        editor.fold_buffer(buffer_ids[0], cx);
22553        editor.fold_buffer(buffer_ids[2], cx);
22554        editor.fold_buffer(buffer_ids[3], cx);
22555
22556        editor
22557    });
22558    cx.simulate_resize(size(px(1000.), px(1000.)));
22559
22560    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22561    cx.assert_excerpts_with_selections(indoc! {"
22562        [EXCERPT]
22563        ˇ[FOLDED]
22564        [EXCERPT]
22565        a1
22566        b1
22567        [EXCERPT]
22568        [FOLDED]
22569        [EXCERPT]
22570        [FOLDED]
22571        "
22572    });
22573    cx.simulate_keystroke("down");
22574    cx.assert_excerpts_with_selections(indoc! {"
22575        [EXCERPT]
22576        [FOLDED]
22577        [EXCERPT]
22578        ˇa1
22579        b1
22580        [EXCERPT]
22581        [FOLDED]
22582        [EXCERPT]
22583        [FOLDED]
22584        "
22585    });
22586    cx.simulate_keystroke("down");
22587    cx.assert_excerpts_with_selections(indoc! {"
22588        [EXCERPT]
22589        [FOLDED]
22590        [EXCERPT]
22591        a1
22592        ˇb1
22593        [EXCERPT]
22594        [FOLDED]
22595        [EXCERPT]
22596        [FOLDED]
22597        "
22598    });
22599    cx.simulate_keystroke("down");
22600    cx.assert_excerpts_with_selections(indoc! {"
22601        [EXCERPT]
22602        [FOLDED]
22603        [EXCERPT]
22604        a1
22605        b1
22606        ˇ[EXCERPT]
22607        [FOLDED]
22608        [EXCERPT]
22609        [FOLDED]
22610        "
22611    });
22612    cx.simulate_keystroke("down");
22613    cx.assert_excerpts_with_selections(indoc! {"
22614        [EXCERPT]
22615        [FOLDED]
22616        [EXCERPT]
22617        a1
22618        b1
22619        [EXCERPT]
22620        ˇ[FOLDED]
22621        [EXCERPT]
22622        [FOLDED]
22623        "
22624    });
22625    for _ in 0..5 {
22626        cx.simulate_keystroke("down");
22627        cx.assert_excerpts_with_selections(indoc! {"
22628            [EXCERPT]
22629            [FOLDED]
22630            [EXCERPT]
22631            a1
22632            b1
22633            [EXCERPT]
22634            [FOLDED]
22635            [EXCERPT]
22636            ˇ[FOLDED]
22637            "
22638        });
22639    }
22640
22641    cx.simulate_keystroke("up");
22642    cx.assert_excerpts_with_selections(indoc! {"
22643        [EXCERPT]
22644        [FOLDED]
22645        [EXCERPT]
22646        a1
22647        b1
22648        [EXCERPT]
22649        ˇ[FOLDED]
22650        [EXCERPT]
22651        [FOLDED]
22652        "
22653    });
22654    cx.simulate_keystroke("up");
22655    cx.assert_excerpts_with_selections(indoc! {"
22656        [EXCERPT]
22657        [FOLDED]
22658        [EXCERPT]
22659        a1
22660        b1
22661        ˇ[EXCERPT]
22662        [FOLDED]
22663        [EXCERPT]
22664        [FOLDED]
22665        "
22666    });
22667    cx.simulate_keystroke("up");
22668    cx.assert_excerpts_with_selections(indoc! {"
22669        [EXCERPT]
22670        [FOLDED]
22671        [EXCERPT]
22672        a1
22673        ˇb1
22674        [EXCERPT]
22675        [FOLDED]
22676        [EXCERPT]
22677        [FOLDED]
22678        "
22679    });
22680    cx.simulate_keystroke("up");
22681    cx.assert_excerpts_with_selections(indoc! {"
22682        [EXCERPT]
22683        [FOLDED]
22684        [EXCERPT]
22685        ˇa1
22686        b1
22687        [EXCERPT]
22688        [FOLDED]
22689        [EXCERPT]
22690        [FOLDED]
22691        "
22692    });
22693    for _ in 0..5 {
22694        cx.simulate_keystroke("up");
22695        cx.assert_excerpts_with_selections(indoc! {"
22696            [EXCERPT]
22697            ˇ[FOLDED]
22698            [EXCERPT]
22699            a1
22700            b1
22701            [EXCERPT]
22702            [FOLDED]
22703            [EXCERPT]
22704            [FOLDED]
22705            "
22706        });
22707    }
22708}
22709
22710#[gpui::test]
22711async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22712    init_test(cx, |_| {});
22713
22714    // Simple insertion
22715    assert_highlighted_edits(
22716        "Hello, world!",
22717        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22718        true,
22719        cx,
22720        |highlighted_edits, cx| {
22721            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22722            assert_eq!(highlighted_edits.highlights.len(), 1);
22723            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22724            assert_eq!(
22725                highlighted_edits.highlights[0].1.background_color,
22726                Some(cx.theme().status().created_background)
22727            );
22728        },
22729    )
22730    .await;
22731
22732    // Replacement
22733    assert_highlighted_edits(
22734        "This is a test.",
22735        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22736        false,
22737        cx,
22738        |highlighted_edits, cx| {
22739            assert_eq!(highlighted_edits.text, "That is a test.");
22740            assert_eq!(highlighted_edits.highlights.len(), 1);
22741            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22742            assert_eq!(
22743                highlighted_edits.highlights[0].1.background_color,
22744                Some(cx.theme().status().created_background)
22745            );
22746        },
22747    )
22748    .await;
22749
22750    // Multiple edits
22751    assert_highlighted_edits(
22752        "Hello, world!",
22753        vec![
22754            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22755            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22756        ],
22757        false,
22758        cx,
22759        |highlighted_edits, cx| {
22760            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22761            assert_eq!(highlighted_edits.highlights.len(), 2);
22762            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22763            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22764            assert_eq!(
22765                highlighted_edits.highlights[0].1.background_color,
22766                Some(cx.theme().status().created_background)
22767            );
22768            assert_eq!(
22769                highlighted_edits.highlights[1].1.background_color,
22770                Some(cx.theme().status().created_background)
22771            );
22772        },
22773    )
22774    .await;
22775
22776    // Multiple lines with edits
22777    assert_highlighted_edits(
22778        "First line\nSecond line\nThird line\nFourth line",
22779        vec![
22780            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22781            (
22782                Point::new(2, 0)..Point::new(2, 10),
22783                "New third line".to_string(),
22784            ),
22785            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22786        ],
22787        false,
22788        cx,
22789        |highlighted_edits, cx| {
22790            assert_eq!(
22791                highlighted_edits.text,
22792                "Second modified\nNew third line\nFourth updated line"
22793            );
22794            assert_eq!(highlighted_edits.highlights.len(), 3);
22795            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22796            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22797            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22798            for highlight in &highlighted_edits.highlights {
22799                assert_eq!(
22800                    highlight.1.background_color,
22801                    Some(cx.theme().status().created_background)
22802                );
22803            }
22804        },
22805    )
22806    .await;
22807}
22808
22809#[gpui::test]
22810async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22811    init_test(cx, |_| {});
22812
22813    // Deletion
22814    assert_highlighted_edits(
22815        "Hello, world!",
22816        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22817        true,
22818        cx,
22819        |highlighted_edits, cx| {
22820            assert_eq!(highlighted_edits.text, "Hello, world!");
22821            assert_eq!(highlighted_edits.highlights.len(), 1);
22822            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22823            assert_eq!(
22824                highlighted_edits.highlights[0].1.background_color,
22825                Some(cx.theme().status().deleted_background)
22826            );
22827        },
22828    )
22829    .await;
22830
22831    // Insertion
22832    assert_highlighted_edits(
22833        "Hello, world!",
22834        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22835        true,
22836        cx,
22837        |highlighted_edits, cx| {
22838            assert_eq!(highlighted_edits.highlights.len(), 1);
22839            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22840            assert_eq!(
22841                highlighted_edits.highlights[0].1.background_color,
22842                Some(cx.theme().status().created_background)
22843            );
22844        },
22845    )
22846    .await;
22847}
22848
22849async fn assert_highlighted_edits(
22850    text: &str,
22851    edits: Vec<(Range<Point>, String)>,
22852    include_deletions: bool,
22853    cx: &mut TestAppContext,
22854    assertion_fn: impl Fn(HighlightedText, &App),
22855) {
22856    let window = cx.add_window(|window, cx| {
22857        let buffer = MultiBuffer::build_simple(text, cx);
22858        Editor::new(EditorMode::full(), buffer, None, window, cx)
22859    });
22860    let cx = &mut VisualTestContext::from_window(*window, cx);
22861
22862    let (buffer, snapshot) = window
22863        .update(cx, |editor, _window, cx| {
22864            (
22865                editor.buffer().clone(),
22866                editor.buffer().read(cx).snapshot(cx),
22867            )
22868        })
22869        .unwrap();
22870
22871    let edits = edits
22872        .into_iter()
22873        .map(|(range, edit)| {
22874            (
22875                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22876                edit,
22877            )
22878        })
22879        .collect::<Vec<_>>();
22880
22881    let text_anchor_edits = edits
22882        .clone()
22883        .into_iter()
22884        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22885        .collect::<Vec<_>>();
22886
22887    let edit_preview = window
22888        .update(cx, |_, _window, cx| {
22889            buffer
22890                .read(cx)
22891                .as_singleton()
22892                .unwrap()
22893                .read(cx)
22894                .preview_edits(text_anchor_edits.into(), cx)
22895        })
22896        .unwrap()
22897        .await;
22898
22899    cx.update(|_window, cx| {
22900        let highlighted_edits = edit_prediction_edit_text(
22901            snapshot.as_singleton().unwrap().2,
22902            &edits,
22903            &edit_preview,
22904            include_deletions,
22905            cx,
22906        );
22907        assertion_fn(highlighted_edits, cx)
22908    });
22909}
22910
22911#[track_caller]
22912fn assert_breakpoint(
22913    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22914    path: &Arc<Path>,
22915    expected: Vec<(u32, Breakpoint)>,
22916) {
22917    if expected.is_empty() {
22918        assert!(!breakpoints.contains_key(path), "{}", path.display());
22919    } else {
22920        let mut breakpoint = breakpoints
22921            .get(path)
22922            .unwrap()
22923            .iter()
22924            .map(|breakpoint| {
22925                (
22926                    breakpoint.row,
22927                    Breakpoint {
22928                        message: breakpoint.message.clone(),
22929                        state: breakpoint.state,
22930                        condition: breakpoint.condition.clone(),
22931                        hit_condition: breakpoint.hit_condition.clone(),
22932                    },
22933                )
22934            })
22935            .collect::<Vec<_>>();
22936
22937        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22938
22939        assert_eq!(expected, breakpoint);
22940    }
22941}
22942
22943fn add_log_breakpoint_at_cursor(
22944    editor: &mut Editor,
22945    log_message: &str,
22946    window: &mut Window,
22947    cx: &mut Context<Editor>,
22948) {
22949    let (anchor, bp) = editor
22950        .breakpoints_at_cursors(window, cx)
22951        .first()
22952        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22953        .unwrap_or_else(|| {
22954            let snapshot = editor.snapshot(window, cx);
22955            let cursor_position: Point =
22956                editor.selections.newest(&snapshot.display_snapshot).head();
22957
22958            let breakpoint_position = snapshot
22959                .buffer_snapshot()
22960                .anchor_before(Point::new(cursor_position.row, 0));
22961
22962            (breakpoint_position, Breakpoint::new_log(log_message))
22963        });
22964
22965    editor.edit_breakpoint_at_anchor(
22966        anchor,
22967        bp,
22968        BreakpointEditAction::EditLogMessage(log_message.into()),
22969        cx,
22970    );
22971}
22972
22973#[gpui::test]
22974async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22975    init_test(cx, |_| {});
22976
22977    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22978    let fs = FakeFs::new(cx.executor());
22979    fs.insert_tree(
22980        path!("/a"),
22981        json!({
22982            "main.rs": sample_text,
22983        }),
22984    )
22985    .await;
22986    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22987    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22988    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22989
22990    let fs = FakeFs::new(cx.executor());
22991    fs.insert_tree(
22992        path!("/a"),
22993        json!({
22994            "main.rs": sample_text,
22995        }),
22996    )
22997    .await;
22998    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22999    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23000    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23001    let worktree_id = workspace
23002        .update(cx, |workspace, _window, cx| {
23003            workspace.project().update(cx, |project, cx| {
23004                project.worktrees(cx).next().unwrap().read(cx).id()
23005            })
23006        })
23007        .unwrap();
23008
23009    let buffer = project
23010        .update(cx, |project, cx| {
23011            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23012        })
23013        .await
23014        .unwrap();
23015
23016    let (editor, cx) = cx.add_window_view(|window, cx| {
23017        Editor::new(
23018            EditorMode::full(),
23019            MultiBuffer::build_from_buffer(buffer, cx),
23020            Some(project.clone()),
23021            window,
23022            cx,
23023        )
23024    });
23025
23026    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23027    let abs_path = project.read_with(cx, |project, cx| {
23028        project
23029            .absolute_path(&project_path, cx)
23030            .map(Arc::from)
23031            .unwrap()
23032    });
23033
23034    // assert we can add breakpoint on the first line
23035    editor.update_in(cx, |editor, window, cx| {
23036        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23037        editor.move_to_end(&MoveToEnd, window, cx);
23038        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23039    });
23040
23041    let breakpoints = editor.update(cx, |editor, cx| {
23042        editor
23043            .breakpoint_store()
23044            .as_ref()
23045            .unwrap()
23046            .read(cx)
23047            .all_source_breakpoints(cx)
23048    });
23049
23050    assert_eq!(1, breakpoints.len());
23051    assert_breakpoint(
23052        &breakpoints,
23053        &abs_path,
23054        vec![
23055            (0, Breakpoint::new_standard()),
23056            (3, Breakpoint::new_standard()),
23057        ],
23058    );
23059
23060    editor.update_in(cx, |editor, window, cx| {
23061        editor.move_to_beginning(&MoveToBeginning, window, cx);
23062        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23063    });
23064
23065    let breakpoints = editor.update(cx, |editor, cx| {
23066        editor
23067            .breakpoint_store()
23068            .as_ref()
23069            .unwrap()
23070            .read(cx)
23071            .all_source_breakpoints(cx)
23072    });
23073
23074    assert_eq!(1, breakpoints.len());
23075    assert_breakpoint(
23076        &breakpoints,
23077        &abs_path,
23078        vec![(3, Breakpoint::new_standard())],
23079    );
23080
23081    editor.update_in(cx, |editor, window, cx| {
23082        editor.move_to_end(&MoveToEnd, window, cx);
23083        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23084    });
23085
23086    let breakpoints = editor.update(cx, |editor, cx| {
23087        editor
23088            .breakpoint_store()
23089            .as_ref()
23090            .unwrap()
23091            .read(cx)
23092            .all_source_breakpoints(cx)
23093    });
23094
23095    assert_eq!(0, breakpoints.len());
23096    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23097}
23098
23099#[gpui::test]
23100async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23101    init_test(cx, |_| {});
23102
23103    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23104
23105    let fs = FakeFs::new(cx.executor());
23106    fs.insert_tree(
23107        path!("/a"),
23108        json!({
23109            "main.rs": sample_text,
23110        }),
23111    )
23112    .await;
23113    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23114    let (workspace, cx) =
23115        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23116
23117    let worktree_id = workspace.update(cx, |workspace, cx| {
23118        workspace.project().update(cx, |project, cx| {
23119            project.worktrees(cx).next().unwrap().read(cx).id()
23120        })
23121    });
23122
23123    let buffer = project
23124        .update(cx, |project, cx| {
23125            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23126        })
23127        .await
23128        .unwrap();
23129
23130    let (editor, cx) = cx.add_window_view(|window, cx| {
23131        Editor::new(
23132            EditorMode::full(),
23133            MultiBuffer::build_from_buffer(buffer, cx),
23134            Some(project.clone()),
23135            window,
23136            cx,
23137        )
23138    });
23139
23140    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23141    let abs_path = project.read_with(cx, |project, cx| {
23142        project
23143            .absolute_path(&project_path, cx)
23144            .map(Arc::from)
23145            .unwrap()
23146    });
23147
23148    editor.update_in(cx, |editor, window, cx| {
23149        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23150    });
23151
23152    let breakpoints = editor.update(cx, |editor, cx| {
23153        editor
23154            .breakpoint_store()
23155            .as_ref()
23156            .unwrap()
23157            .read(cx)
23158            .all_source_breakpoints(cx)
23159    });
23160
23161    assert_breakpoint(
23162        &breakpoints,
23163        &abs_path,
23164        vec![(0, Breakpoint::new_log("hello world"))],
23165    );
23166
23167    // Removing a log message from a log breakpoint should remove it
23168    editor.update_in(cx, |editor, window, cx| {
23169        add_log_breakpoint_at_cursor(editor, "", window, cx);
23170    });
23171
23172    let breakpoints = editor.update(cx, |editor, cx| {
23173        editor
23174            .breakpoint_store()
23175            .as_ref()
23176            .unwrap()
23177            .read(cx)
23178            .all_source_breakpoints(cx)
23179    });
23180
23181    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23182
23183    editor.update_in(cx, |editor, window, cx| {
23184        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23185        editor.move_to_end(&MoveToEnd, window, cx);
23186        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23187        // Not adding a log message to a standard breakpoint shouldn't remove it
23188        add_log_breakpoint_at_cursor(editor, "", window, cx);
23189    });
23190
23191    let breakpoints = editor.update(cx, |editor, cx| {
23192        editor
23193            .breakpoint_store()
23194            .as_ref()
23195            .unwrap()
23196            .read(cx)
23197            .all_source_breakpoints(cx)
23198    });
23199
23200    assert_breakpoint(
23201        &breakpoints,
23202        &abs_path,
23203        vec![
23204            (0, Breakpoint::new_standard()),
23205            (3, Breakpoint::new_standard()),
23206        ],
23207    );
23208
23209    editor.update_in(cx, |editor, window, cx| {
23210        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23211    });
23212
23213    let breakpoints = editor.update(cx, |editor, cx| {
23214        editor
23215            .breakpoint_store()
23216            .as_ref()
23217            .unwrap()
23218            .read(cx)
23219            .all_source_breakpoints(cx)
23220    });
23221
23222    assert_breakpoint(
23223        &breakpoints,
23224        &abs_path,
23225        vec![
23226            (0, Breakpoint::new_standard()),
23227            (3, Breakpoint::new_log("hello world")),
23228        ],
23229    );
23230
23231    editor.update_in(cx, |editor, window, cx| {
23232        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23233    });
23234
23235    let breakpoints = editor.update(cx, |editor, cx| {
23236        editor
23237            .breakpoint_store()
23238            .as_ref()
23239            .unwrap()
23240            .read(cx)
23241            .all_source_breakpoints(cx)
23242    });
23243
23244    assert_breakpoint(
23245        &breakpoints,
23246        &abs_path,
23247        vec![
23248            (0, Breakpoint::new_standard()),
23249            (3, Breakpoint::new_log("hello Earth!!")),
23250        ],
23251    );
23252}
23253
23254/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23255/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23256/// or when breakpoints were placed out of order. This tests for a regression too
23257#[gpui::test]
23258async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23259    init_test(cx, |_| {});
23260
23261    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23262    let fs = FakeFs::new(cx.executor());
23263    fs.insert_tree(
23264        path!("/a"),
23265        json!({
23266            "main.rs": sample_text,
23267        }),
23268    )
23269    .await;
23270    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23271    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23272    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23273
23274    let fs = FakeFs::new(cx.executor());
23275    fs.insert_tree(
23276        path!("/a"),
23277        json!({
23278            "main.rs": sample_text,
23279        }),
23280    )
23281    .await;
23282    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23283    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23284    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23285    let worktree_id = workspace
23286        .update(cx, |workspace, _window, cx| {
23287            workspace.project().update(cx, |project, cx| {
23288                project.worktrees(cx).next().unwrap().read(cx).id()
23289            })
23290        })
23291        .unwrap();
23292
23293    let buffer = project
23294        .update(cx, |project, cx| {
23295            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23296        })
23297        .await
23298        .unwrap();
23299
23300    let (editor, cx) = cx.add_window_view(|window, cx| {
23301        Editor::new(
23302            EditorMode::full(),
23303            MultiBuffer::build_from_buffer(buffer, cx),
23304            Some(project.clone()),
23305            window,
23306            cx,
23307        )
23308    });
23309
23310    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23311    let abs_path = project.read_with(cx, |project, cx| {
23312        project
23313            .absolute_path(&project_path, cx)
23314            .map(Arc::from)
23315            .unwrap()
23316    });
23317
23318    // assert we can add breakpoint on the first line
23319    editor.update_in(cx, |editor, window, cx| {
23320        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23321        editor.move_to_end(&MoveToEnd, window, cx);
23322        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23323        editor.move_up(&MoveUp, window, cx);
23324        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23325    });
23326
23327    let breakpoints = editor.update(cx, |editor, cx| {
23328        editor
23329            .breakpoint_store()
23330            .as_ref()
23331            .unwrap()
23332            .read(cx)
23333            .all_source_breakpoints(cx)
23334    });
23335
23336    assert_eq!(1, breakpoints.len());
23337    assert_breakpoint(
23338        &breakpoints,
23339        &abs_path,
23340        vec![
23341            (0, Breakpoint::new_standard()),
23342            (2, Breakpoint::new_standard()),
23343            (3, Breakpoint::new_standard()),
23344        ],
23345    );
23346
23347    editor.update_in(cx, |editor, window, cx| {
23348        editor.move_to_beginning(&MoveToBeginning, window, cx);
23349        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23350        editor.move_to_end(&MoveToEnd, window, cx);
23351        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23352        // Disabling a breakpoint that doesn't exist should do nothing
23353        editor.move_up(&MoveUp, window, cx);
23354        editor.move_up(&MoveUp, window, cx);
23355        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23356    });
23357
23358    let breakpoints = editor.update(cx, |editor, cx| {
23359        editor
23360            .breakpoint_store()
23361            .as_ref()
23362            .unwrap()
23363            .read(cx)
23364            .all_source_breakpoints(cx)
23365    });
23366
23367    let disable_breakpoint = {
23368        let mut bp = Breakpoint::new_standard();
23369        bp.state = BreakpointState::Disabled;
23370        bp
23371    };
23372
23373    assert_eq!(1, breakpoints.len());
23374    assert_breakpoint(
23375        &breakpoints,
23376        &abs_path,
23377        vec![
23378            (0, disable_breakpoint.clone()),
23379            (2, Breakpoint::new_standard()),
23380            (3, disable_breakpoint.clone()),
23381        ],
23382    );
23383
23384    editor.update_in(cx, |editor, window, cx| {
23385        editor.move_to_beginning(&MoveToBeginning, window, cx);
23386        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23387        editor.move_to_end(&MoveToEnd, window, cx);
23388        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23389        editor.move_up(&MoveUp, window, cx);
23390        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23391    });
23392
23393    let breakpoints = editor.update(cx, |editor, cx| {
23394        editor
23395            .breakpoint_store()
23396            .as_ref()
23397            .unwrap()
23398            .read(cx)
23399            .all_source_breakpoints(cx)
23400    });
23401
23402    assert_eq!(1, breakpoints.len());
23403    assert_breakpoint(
23404        &breakpoints,
23405        &abs_path,
23406        vec![
23407            (0, Breakpoint::new_standard()),
23408            (2, disable_breakpoint),
23409            (3, Breakpoint::new_standard()),
23410        ],
23411    );
23412}
23413
23414#[gpui::test]
23415async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23416    init_test(cx, |_| {});
23417    let capabilities = lsp::ServerCapabilities {
23418        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23419            prepare_provider: Some(true),
23420            work_done_progress_options: Default::default(),
23421        })),
23422        ..Default::default()
23423    };
23424    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23425
23426    cx.set_state(indoc! {"
23427        struct Fˇoo {}
23428    "});
23429
23430    cx.update_editor(|editor, _, cx| {
23431        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23432        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23433        editor.highlight_background::<DocumentHighlightRead>(
23434            &[highlight_range],
23435            |theme| theme.colors().editor_document_highlight_read_background,
23436            cx,
23437        );
23438    });
23439
23440    let mut prepare_rename_handler = cx
23441        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23442            move |_, _, _| async move {
23443                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23444                    start: lsp::Position {
23445                        line: 0,
23446                        character: 7,
23447                    },
23448                    end: lsp::Position {
23449                        line: 0,
23450                        character: 10,
23451                    },
23452                })))
23453            },
23454        );
23455    let prepare_rename_task = cx
23456        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23457        .expect("Prepare rename was not started");
23458    prepare_rename_handler.next().await.unwrap();
23459    prepare_rename_task.await.expect("Prepare rename failed");
23460
23461    let mut rename_handler =
23462        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23463            let edit = lsp::TextEdit {
23464                range: lsp::Range {
23465                    start: lsp::Position {
23466                        line: 0,
23467                        character: 7,
23468                    },
23469                    end: lsp::Position {
23470                        line: 0,
23471                        character: 10,
23472                    },
23473                },
23474                new_text: "FooRenamed".to_string(),
23475            };
23476            Ok(Some(lsp::WorkspaceEdit::new(
23477                // Specify the same edit twice
23478                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23479            )))
23480        });
23481    let rename_task = cx
23482        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23483        .expect("Confirm rename was not started");
23484    rename_handler.next().await.unwrap();
23485    rename_task.await.expect("Confirm rename failed");
23486    cx.run_until_parked();
23487
23488    // Despite two edits, only one is actually applied as those are identical
23489    cx.assert_editor_state(indoc! {"
23490        struct FooRenamedˇ {}
23491    "});
23492}
23493
23494#[gpui::test]
23495async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23496    init_test(cx, |_| {});
23497    // These capabilities indicate that the server does not support prepare rename.
23498    let capabilities = lsp::ServerCapabilities {
23499        rename_provider: Some(lsp::OneOf::Left(true)),
23500        ..Default::default()
23501    };
23502    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23503
23504    cx.set_state(indoc! {"
23505        struct Fˇoo {}
23506    "});
23507
23508    cx.update_editor(|editor, _window, cx| {
23509        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23510        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23511        editor.highlight_background::<DocumentHighlightRead>(
23512            &[highlight_range],
23513            |theme| theme.colors().editor_document_highlight_read_background,
23514            cx,
23515        );
23516    });
23517
23518    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23519        .expect("Prepare rename was not started")
23520        .await
23521        .expect("Prepare rename failed");
23522
23523    let mut rename_handler =
23524        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23525            let edit = lsp::TextEdit {
23526                range: lsp::Range {
23527                    start: lsp::Position {
23528                        line: 0,
23529                        character: 7,
23530                    },
23531                    end: lsp::Position {
23532                        line: 0,
23533                        character: 10,
23534                    },
23535                },
23536                new_text: "FooRenamed".to_string(),
23537            };
23538            Ok(Some(lsp::WorkspaceEdit::new(
23539                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23540            )))
23541        });
23542    let rename_task = cx
23543        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23544        .expect("Confirm rename was not started");
23545    rename_handler.next().await.unwrap();
23546    rename_task.await.expect("Confirm rename failed");
23547    cx.run_until_parked();
23548
23549    // Correct range is renamed, as `surrounding_word` is used to find it.
23550    cx.assert_editor_state(indoc! {"
23551        struct FooRenamedˇ {}
23552    "});
23553}
23554
23555#[gpui::test]
23556async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23557    init_test(cx, |_| {});
23558    let mut cx = EditorTestContext::new(cx).await;
23559
23560    let language = Arc::new(
23561        Language::new(
23562            LanguageConfig::default(),
23563            Some(tree_sitter_html::LANGUAGE.into()),
23564        )
23565        .with_brackets_query(
23566            r#"
23567            ("<" @open "/>" @close)
23568            ("</" @open ">" @close)
23569            ("<" @open ">" @close)
23570            ("\"" @open "\"" @close)
23571            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23572        "#,
23573        )
23574        .unwrap(),
23575    );
23576    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23577
23578    cx.set_state(indoc! {"
23579        <span>ˇ</span>
23580    "});
23581    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23582    cx.assert_editor_state(indoc! {"
23583        <span>
23584        ˇ
23585        </span>
23586    "});
23587
23588    cx.set_state(indoc! {"
23589        <span><span></span>ˇ</span>
23590    "});
23591    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23592    cx.assert_editor_state(indoc! {"
23593        <span><span></span>
23594        ˇ</span>
23595    "});
23596
23597    cx.set_state(indoc! {"
23598        <span>ˇ
23599        </span>
23600    "});
23601    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23602    cx.assert_editor_state(indoc! {"
23603        <span>
23604        ˇ
23605        </span>
23606    "});
23607}
23608
23609#[gpui::test(iterations = 10)]
23610async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23611    init_test(cx, |_| {});
23612
23613    let fs = FakeFs::new(cx.executor());
23614    fs.insert_tree(
23615        path!("/dir"),
23616        json!({
23617            "a.ts": "a",
23618        }),
23619    )
23620    .await;
23621
23622    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23623    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23624    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23625
23626    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23627    language_registry.add(Arc::new(Language::new(
23628        LanguageConfig {
23629            name: "TypeScript".into(),
23630            matcher: LanguageMatcher {
23631                path_suffixes: vec!["ts".to_string()],
23632                ..Default::default()
23633            },
23634            ..Default::default()
23635        },
23636        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23637    )));
23638    let mut fake_language_servers = language_registry.register_fake_lsp(
23639        "TypeScript",
23640        FakeLspAdapter {
23641            capabilities: lsp::ServerCapabilities {
23642                code_lens_provider: Some(lsp::CodeLensOptions {
23643                    resolve_provider: Some(true),
23644                }),
23645                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23646                    commands: vec!["_the/command".to_string()],
23647                    ..lsp::ExecuteCommandOptions::default()
23648                }),
23649                ..lsp::ServerCapabilities::default()
23650            },
23651            ..FakeLspAdapter::default()
23652        },
23653    );
23654
23655    let editor = workspace
23656        .update(cx, |workspace, window, cx| {
23657            workspace.open_abs_path(
23658                PathBuf::from(path!("/dir/a.ts")),
23659                OpenOptions::default(),
23660                window,
23661                cx,
23662            )
23663        })
23664        .unwrap()
23665        .await
23666        .unwrap()
23667        .downcast::<Editor>()
23668        .unwrap();
23669    cx.executor().run_until_parked();
23670
23671    let fake_server = fake_language_servers.next().await.unwrap();
23672
23673    let buffer = editor.update(cx, |editor, cx| {
23674        editor
23675            .buffer()
23676            .read(cx)
23677            .as_singleton()
23678            .expect("have opened a single file by path")
23679    });
23680
23681    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23682    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23683    drop(buffer_snapshot);
23684    let actions = cx
23685        .update_window(*workspace, |_, window, cx| {
23686            project.code_actions(&buffer, anchor..anchor, window, cx)
23687        })
23688        .unwrap();
23689
23690    fake_server
23691        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23692            Ok(Some(vec![
23693                lsp::CodeLens {
23694                    range: lsp::Range::default(),
23695                    command: Some(lsp::Command {
23696                        title: "Code lens command".to_owned(),
23697                        command: "_the/command".to_owned(),
23698                        arguments: None,
23699                    }),
23700                    data: None,
23701                },
23702                lsp::CodeLens {
23703                    range: lsp::Range::default(),
23704                    command: Some(lsp::Command {
23705                        title: "Command not in capabilities".to_owned(),
23706                        command: "not in capabilities".to_owned(),
23707                        arguments: None,
23708                    }),
23709                    data: None,
23710                },
23711                lsp::CodeLens {
23712                    range: lsp::Range {
23713                        start: lsp::Position {
23714                            line: 1,
23715                            character: 1,
23716                        },
23717                        end: lsp::Position {
23718                            line: 1,
23719                            character: 1,
23720                        },
23721                    },
23722                    command: Some(lsp::Command {
23723                        title: "Command not in range".to_owned(),
23724                        command: "_the/command".to_owned(),
23725                        arguments: None,
23726                    }),
23727                    data: None,
23728                },
23729            ]))
23730        })
23731        .next()
23732        .await;
23733
23734    let actions = actions.await.unwrap();
23735    assert_eq!(
23736        actions.len(),
23737        1,
23738        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23739    );
23740    let action = actions[0].clone();
23741    let apply = project.update(cx, |project, cx| {
23742        project.apply_code_action(buffer.clone(), action, true, cx)
23743    });
23744
23745    // Resolving the code action does not populate its edits. In absence of
23746    // edits, we must execute the given command.
23747    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23748        |mut lens, _| async move {
23749            let lens_command = lens.command.as_mut().expect("should have a command");
23750            assert_eq!(lens_command.title, "Code lens command");
23751            lens_command.arguments = Some(vec![json!("the-argument")]);
23752            Ok(lens)
23753        },
23754    );
23755
23756    // While executing the command, the language server sends the editor
23757    // a `workspaceEdit` request.
23758    fake_server
23759        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23760            let fake = fake_server.clone();
23761            move |params, _| {
23762                assert_eq!(params.command, "_the/command");
23763                let fake = fake.clone();
23764                async move {
23765                    fake.server
23766                        .request::<lsp::request::ApplyWorkspaceEdit>(
23767                            lsp::ApplyWorkspaceEditParams {
23768                                label: None,
23769                                edit: lsp::WorkspaceEdit {
23770                                    changes: Some(
23771                                        [(
23772                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23773                                            vec![lsp::TextEdit {
23774                                                range: lsp::Range::new(
23775                                                    lsp::Position::new(0, 0),
23776                                                    lsp::Position::new(0, 0),
23777                                                ),
23778                                                new_text: "X".into(),
23779                                            }],
23780                                        )]
23781                                        .into_iter()
23782                                        .collect(),
23783                                    ),
23784                                    ..lsp::WorkspaceEdit::default()
23785                                },
23786                            },
23787                        )
23788                        .await
23789                        .into_response()
23790                        .unwrap();
23791                    Ok(Some(json!(null)))
23792                }
23793            }
23794        })
23795        .next()
23796        .await;
23797
23798    // Applying the code lens command returns a project transaction containing the edits
23799    // sent by the language server in its `workspaceEdit` request.
23800    let transaction = apply.await.unwrap();
23801    assert!(transaction.0.contains_key(&buffer));
23802    buffer.update(cx, |buffer, cx| {
23803        assert_eq!(buffer.text(), "Xa");
23804        buffer.undo(cx);
23805        assert_eq!(buffer.text(), "a");
23806    });
23807
23808    let actions_after_edits = cx
23809        .update_window(*workspace, |_, window, cx| {
23810            project.code_actions(&buffer, anchor..anchor, window, cx)
23811        })
23812        .unwrap()
23813        .await
23814        .unwrap();
23815    assert_eq!(
23816        actions, actions_after_edits,
23817        "For the same selection, same code lens actions should be returned"
23818    );
23819
23820    let _responses =
23821        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23822            panic!("No more code lens requests are expected");
23823        });
23824    editor.update_in(cx, |editor, window, cx| {
23825        editor.select_all(&SelectAll, window, cx);
23826    });
23827    cx.executor().run_until_parked();
23828    let new_actions = cx
23829        .update_window(*workspace, |_, window, cx| {
23830            project.code_actions(&buffer, anchor..anchor, window, cx)
23831        })
23832        .unwrap()
23833        .await
23834        .unwrap();
23835    assert_eq!(
23836        actions, new_actions,
23837        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23838    );
23839}
23840
23841#[gpui::test]
23842async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23843    init_test(cx, |_| {});
23844
23845    let fs = FakeFs::new(cx.executor());
23846    let main_text = r#"fn main() {
23847println!("1");
23848println!("2");
23849println!("3");
23850println!("4");
23851println!("5");
23852}"#;
23853    let lib_text = "mod foo {}";
23854    fs.insert_tree(
23855        path!("/a"),
23856        json!({
23857            "lib.rs": lib_text,
23858            "main.rs": main_text,
23859        }),
23860    )
23861    .await;
23862
23863    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23864    let (workspace, cx) =
23865        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23866    let worktree_id = workspace.update(cx, |workspace, cx| {
23867        workspace.project().update(cx, |project, cx| {
23868            project.worktrees(cx).next().unwrap().read(cx).id()
23869        })
23870    });
23871
23872    let expected_ranges = vec![
23873        Point::new(0, 0)..Point::new(0, 0),
23874        Point::new(1, 0)..Point::new(1, 1),
23875        Point::new(2, 0)..Point::new(2, 2),
23876        Point::new(3, 0)..Point::new(3, 3),
23877    ];
23878
23879    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23880    let editor_1 = workspace
23881        .update_in(cx, |workspace, window, cx| {
23882            workspace.open_path(
23883                (worktree_id, rel_path("main.rs")),
23884                Some(pane_1.downgrade()),
23885                true,
23886                window,
23887                cx,
23888            )
23889        })
23890        .unwrap()
23891        .await
23892        .downcast::<Editor>()
23893        .unwrap();
23894    pane_1.update(cx, |pane, cx| {
23895        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23896        open_editor.update(cx, |editor, cx| {
23897            assert_eq!(
23898                editor.display_text(cx),
23899                main_text,
23900                "Original main.rs text on initial open",
23901            );
23902            assert_eq!(
23903                editor
23904                    .selections
23905                    .all::<Point>(&editor.display_snapshot(cx))
23906                    .into_iter()
23907                    .map(|s| s.range())
23908                    .collect::<Vec<_>>(),
23909                vec![Point::zero()..Point::zero()],
23910                "Default selections on initial open",
23911            );
23912        })
23913    });
23914    editor_1.update_in(cx, |editor, window, cx| {
23915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23916            s.select_ranges(expected_ranges.clone());
23917        });
23918    });
23919
23920    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23921        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23922    });
23923    let editor_2 = workspace
23924        .update_in(cx, |workspace, window, cx| {
23925            workspace.open_path(
23926                (worktree_id, rel_path("main.rs")),
23927                Some(pane_2.downgrade()),
23928                true,
23929                window,
23930                cx,
23931            )
23932        })
23933        .unwrap()
23934        .await
23935        .downcast::<Editor>()
23936        .unwrap();
23937    pane_2.update(cx, |pane, cx| {
23938        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23939        open_editor.update(cx, |editor, cx| {
23940            assert_eq!(
23941                editor.display_text(cx),
23942                main_text,
23943                "Original main.rs text on initial open in another panel",
23944            );
23945            assert_eq!(
23946                editor
23947                    .selections
23948                    .all::<Point>(&editor.display_snapshot(cx))
23949                    .into_iter()
23950                    .map(|s| s.range())
23951                    .collect::<Vec<_>>(),
23952                vec![Point::zero()..Point::zero()],
23953                "Default selections on initial open in another panel",
23954            );
23955        })
23956    });
23957
23958    editor_2.update_in(cx, |editor, window, cx| {
23959        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23960    });
23961
23962    let _other_editor_1 = workspace
23963        .update_in(cx, |workspace, window, cx| {
23964            workspace.open_path(
23965                (worktree_id, rel_path("lib.rs")),
23966                Some(pane_1.downgrade()),
23967                true,
23968                window,
23969                cx,
23970            )
23971        })
23972        .unwrap()
23973        .await
23974        .downcast::<Editor>()
23975        .unwrap();
23976    pane_1
23977        .update_in(cx, |pane, window, cx| {
23978            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23979        })
23980        .await
23981        .unwrap();
23982    drop(editor_1);
23983    pane_1.update(cx, |pane, cx| {
23984        pane.active_item()
23985            .unwrap()
23986            .downcast::<Editor>()
23987            .unwrap()
23988            .update(cx, |editor, cx| {
23989                assert_eq!(
23990                    editor.display_text(cx),
23991                    lib_text,
23992                    "Other file should be open and active",
23993                );
23994            });
23995        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23996    });
23997
23998    let _other_editor_2 = workspace
23999        .update_in(cx, |workspace, window, cx| {
24000            workspace.open_path(
24001                (worktree_id, rel_path("lib.rs")),
24002                Some(pane_2.downgrade()),
24003                true,
24004                window,
24005                cx,
24006            )
24007        })
24008        .unwrap()
24009        .await
24010        .downcast::<Editor>()
24011        .unwrap();
24012    pane_2
24013        .update_in(cx, |pane, window, cx| {
24014            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24015        })
24016        .await
24017        .unwrap();
24018    drop(editor_2);
24019    pane_2.update(cx, |pane, cx| {
24020        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24021        open_editor.update(cx, |editor, cx| {
24022            assert_eq!(
24023                editor.display_text(cx),
24024                lib_text,
24025                "Other file should be open and active in another panel too",
24026            );
24027        });
24028        assert_eq!(
24029            pane.items().count(),
24030            1,
24031            "No other editors should be open in another pane",
24032        );
24033    });
24034
24035    let _editor_1_reopened = workspace
24036        .update_in(cx, |workspace, window, cx| {
24037            workspace.open_path(
24038                (worktree_id, rel_path("main.rs")),
24039                Some(pane_1.downgrade()),
24040                true,
24041                window,
24042                cx,
24043            )
24044        })
24045        .unwrap()
24046        .await
24047        .downcast::<Editor>()
24048        .unwrap();
24049    let _editor_2_reopened = workspace
24050        .update_in(cx, |workspace, window, cx| {
24051            workspace.open_path(
24052                (worktree_id, rel_path("main.rs")),
24053                Some(pane_2.downgrade()),
24054                true,
24055                window,
24056                cx,
24057            )
24058        })
24059        .unwrap()
24060        .await
24061        .downcast::<Editor>()
24062        .unwrap();
24063    pane_1.update(cx, |pane, cx| {
24064        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24065        open_editor.update(cx, |editor, cx| {
24066            assert_eq!(
24067                editor.display_text(cx),
24068                main_text,
24069                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24070            );
24071            assert_eq!(
24072                editor
24073                    .selections
24074                    .all::<Point>(&editor.display_snapshot(cx))
24075                    .into_iter()
24076                    .map(|s| s.range())
24077                    .collect::<Vec<_>>(),
24078                expected_ranges,
24079                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24080            );
24081        })
24082    });
24083    pane_2.update(cx, |pane, cx| {
24084        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24085        open_editor.update(cx, |editor, cx| {
24086            assert_eq!(
24087                editor.display_text(cx),
24088                r#"fn main() {
24089⋯rintln!("1");
24090⋯intln!("2");
24091⋯ntln!("3");
24092println!("4");
24093println!("5");
24094}"#,
24095                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24096            );
24097            assert_eq!(
24098                editor
24099                    .selections
24100                    .all::<Point>(&editor.display_snapshot(cx))
24101                    .into_iter()
24102                    .map(|s| s.range())
24103                    .collect::<Vec<_>>(),
24104                vec![Point::zero()..Point::zero()],
24105                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24106            );
24107        })
24108    });
24109}
24110
24111#[gpui::test]
24112async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24113    init_test(cx, |_| {});
24114
24115    let fs = FakeFs::new(cx.executor());
24116    let main_text = r#"fn main() {
24117println!("1");
24118println!("2");
24119println!("3");
24120println!("4");
24121println!("5");
24122}"#;
24123    let lib_text = "mod foo {}";
24124    fs.insert_tree(
24125        path!("/a"),
24126        json!({
24127            "lib.rs": lib_text,
24128            "main.rs": main_text,
24129        }),
24130    )
24131    .await;
24132
24133    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24134    let (workspace, cx) =
24135        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24136    let worktree_id = workspace.update(cx, |workspace, cx| {
24137        workspace.project().update(cx, |project, cx| {
24138            project.worktrees(cx).next().unwrap().read(cx).id()
24139        })
24140    });
24141
24142    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24143    let editor = workspace
24144        .update_in(cx, |workspace, window, cx| {
24145            workspace.open_path(
24146                (worktree_id, rel_path("main.rs")),
24147                Some(pane.downgrade()),
24148                true,
24149                window,
24150                cx,
24151            )
24152        })
24153        .unwrap()
24154        .await
24155        .downcast::<Editor>()
24156        .unwrap();
24157    pane.update(cx, |pane, cx| {
24158        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24159        open_editor.update(cx, |editor, cx| {
24160            assert_eq!(
24161                editor.display_text(cx),
24162                main_text,
24163                "Original main.rs text on initial open",
24164            );
24165        })
24166    });
24167    editor.update_in(cx, |editor, window, cx| {
24168        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24169    });
24170
24171    cx.update_global(|store: &mut SettingsStore, cx| {
24172        store.update_user_settings(cx, |s| {
24173            s.workspace.restore_on_file_reopen = Some(false);
24174        });
24175    });
24176    editor.update_in(cx, |editor, window, cx| {
24177        editor.fold_ranges(
24178            vec![
24179                Point::new(1, 0)..Point::new(1, 1),
24180                Point::new(2, 0)..Point::new(2, 2),
24181                Point::new(3, 0)..Point::new(3, 3),
24182            ],
24183            false,
24184            window,
24185            cx,
24186        );
24187    });
24188    pane.update_in(cx, |pane, window, cx| {
24189        pane.close_all_items(&CloseAllItems::default(), window, cx)
24190    })
24191    .await
24192    .unwrap();
24193    pane.update(cx, |pane, _| {
24194        assert!(pane.active_item().is_none());
24195    });
24196    cx.update_global(|store: &mut SettingsStore, cx| {
24197        store.update_user_settings(cx, |s| {
24198            s.workspace.restore_on_file_reopen = Some(true);
24199        });
24200    });
24201
24202    let _editor_reopened = workspace
24203        .update_in(cx, |workspace, window, cx| {
24204            workspace.open_path(
24205                (worktree_id, rel_path("main.rs")),
24206                Some(pane.downgrade()),
24207                true,
24208                window,
24209                cx,
24210            )
24211        })
24212        .unwrap()
24213        .await
24214        .downcast::<Editor>()
24215        .unwrap();
24216    pane.update(cx, |pane, cx| {
24217        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24218        open_editor.update(cx, |editor, cx| {
24219            assert_eq!(
24220                editor.display_text(cx),
24221                main_text,
24222                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24223            );
24224        })
24225    });
24226}
24227
24228#[gpui::test]
24229async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24230    struct EmptyModalView {
24231        focus_handle: gpui::FocusHandle,
24232    }
24233    impl EventEmitter<DismissEvent> for EmptyModalView {}
24234    impl Render for EmptyModalView {
24235        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24236            div()
24237        }
24238    }
24239    impl Focusable for EmptyModalView {
24240        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24241            self.focus_handle.clone()
24242        }
24243    }
24244    impl workspace::ModalView for EmptyModalView {}
24245    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24246        EmptyModalView {
24247            focus_handle: cx.focus_handle(),
24248        }
24249    }
24250
24251    init_test(cx, |_| {});
24252
24253    let fs = FakeFs::new(cx.executor());
24254    let project = Project::test(fs, [], cx).await;
24255    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24256    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24257    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24258    let editor = cx.new_window_entity(|window, cx| {
24259        Editor::new(
24260            EditorMode::full(),
24261            buffer,
24262            Some(project.clone()),
24263            window,
24264            cx,
24265        )
24266    });
24267    workspace
24268        .update(cx, |workspace, window, cx| {
24269            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24270        })
24271        .unwrap();
24272    editor.update_in(cx, |editor, window, cx| {
24273        editor.open_context_menu(&OpenContextMenu, window, cx);
24274        assert!(editor.mouse_context_menu.is_some());
24275    });
24276    workspace
24277        .update(cx, |workspace, window, cx| {
24278            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24279        })
24280        .unwrap();
24281    cx.read(|cx| {
24282        assert!(editor.read(cx).mouse_context_menu.is_none());
24283    });
24284}
24285
24286fn set_linked_edit_ranges(
24287    opening: (Point, Point),
24288    closing: (Point, Point),
24289    editor: &mut Editor,
24290    cx: &mut Context<Editor>,
24291) {
24292    let Some((buffer, _)) = editor
24293        .buffer
24294        .read(cx)
24295        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24296    else {
24297        panic!("Failed to get buffer for selection position");
24298    };
24299    let buffer = buffer.read(cx);
24300    let buffer_id = buffer.remote_id();
24301    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24302    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24303    let mut linked_ranges = HashMap::default();
24304    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24305    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24306}
24307
24308#[gpui::test]
24309async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24310    init_test(cx, |_| {});
24311
24312    let fs = FakeFs::new(cx.executor());
24313    fs.insert_file(path!("/file.html"), Default::default())
24314        .await;
24315
24316    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24317
24318    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24319    let html_language = Arc::new(Language::new(
24320        LanguageConfig {
24321            name: "HTML".into(),
24322            matcher: LanguageMatcher {
24323                path_suffixes: vec!["html".to_string()],
24324                ..LanguageMatcher::default()
24325            },
24326            brackets: BracketPairConfig {
24327                pairs: vec![BracketPair {
24328                    start: "<".into(),
24329                    end: ">".into(),
24330                    close: true,
24331                    ..Default::default()
24332                }],
24333                ..Default::default()
24334            },
24335            ..Default::default()
24336        },
24337        Some(tree_sitter_html::LANGUAGE.into()),
24338    ));
24339    language_registry.add(html_language);
24340    let mut fake_servers = language_registry.register_fake_lsp(
24341        "HTML",
24342        FakeLspAdapter {
24343            capabilities: lsp::ServerCapabilities {
24344                completion_provider: Some(lsp::CompletionOptions {
24345                    resolve_provider: Some(true),
24346                    ..Default::default()
24347                }),
24348                ..Default::default()
24349            },
24350            ..Default::default()
24351        },
24352    );
24353
24354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24355    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24356
24357    let worktree_id = workspace
24358        .update(cx, |workspace, _window, cx| {
24359            workspace.project().update(cx, |project, cx| {
24360                project.worktrees(cx).next().unwrap().read(cx).id()
24361            })
24362        })
24363        .unwrap();
24364    project
24365        .update(cx, |project, cx| {
24366            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24367        })
24368        .await
24369        .unwrap();
24370    let editor = workspace
24371        .update(cx, |workspace, window, cx| {
24372            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24373        })
24374        .unwrap()
24375        .await
24376        .unwrap()
24377        .downcast::<Editor>()
24378        .unwrap();
24379
24380    let fake_server = fake_servers.next().await.unwrap();
24381    editor.update_in(cx, |editor, window, cx| {
24382        editor.set_text("<ad></ad>", window, cx);
24383        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24384            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24385        });
24386        set_linked_edit_ranges(
24387            (Point::new(0, 1), Point::new(0, 3)),
24388            (Point::new(0, 6), Point::new(0, 8)),
24389            editor,
24390            cx,
24391        );
24392    });
24393    let mut completion_handle =
24394        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24395            Ok(Some(lsp::CompletionResponse::Array(vec![
24396                lsp::CompletionItem {
24397                    label: "head".to_string(),
24398                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24399                        lsp::InsertReplaceEdit {
24400                            new_text: "head".to_string(),
24401                            insert: lsp::Range::new(
24402                                lsp::Position::new(0, 1),
24403                                lsp::Position::new(0, 3),
24404                            ),
24405                            replace: lsp::Range::new(
24406                                lsp::Position::new(0, 1),
24407                                lsp::Position::new(0, 3),
24408                            ),
24409                        },
24410                    )),
24411                    ..Default::default()
24412                },
24413            ])))
24414        });
24415    editor.update_in(cx, |editor, window, cx| {
24416        editor.show_completions(
24417            &ShowCompletions {
24418                trigger: None,
24419                snippets_only: false,
24420            },
24421            window,
24422            cx,
24423        );
24424    });
24425    cx.run_until_parked();
24426    completion_handle.next().await.unwrap();
24427    editor.update(cx, |editor, _| {
24428        assert!(
24429            editor.context_menu_visible(),
24430            "Completion menu should be visible"
24431        );
24432    });
24433    editor.update_in(cx, |editor, window, cx| {
24434        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24435    });
24436    cx.executor().run_until_parked();
24437    editor.update(cx, |editor, cx| {
24438        assert_eq!(editor.text(cx), "<head></head>");
24439    });
24440}
24441
24442#[gpui::test]
24443async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24444    init_test(cx, |_| {});
24445
24446    let mut cx = EditorTestContext::new(cx).await;
24447    let language = Arc::new(Language::new(
24448        LanguageConfig {
24449            name: "TSX".into(),
24450            matcher: LanguageMatcher {
24451                path_suffixes: vec!["tsx".to_string()],
24452                ..LanguageMatcher::default()
24453            },
24454            brackets: BracketPairConfig {
24455                pairs: vec![BracketPair {
24456                    start: "<".into(),
24457                    end: ">".into(),
24458                    close: true,
24459                    ..Default::default()
24460                }],
24461                ..Default::default()
24462            },
24463            linked_edit_characters: HashSet::from_iter(['.']),
24464            ..Default::default()
24465        },
24466        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24467    ));
24468    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24469
24470    // Test typing > does not extend linked pair
24471    cx.set_state("<divˇ<div></div>");
24472    cx.update_editor(|editor, _, cx| {
24473        set_linked_edit_ranges(
24474            (Point::new(0, 1), Point::new(0, 4)),
24475            (Point::new(0, 11), Point::new(0, 14)),
24476            editor,
24477            cx,
24478        );
24479    });
24480    cx.update_editor(|editor, window, cx| {
24481        editor.handle_input(">", window, cx);
24482    });
24483    cx.assert_editor_state("<div>ˇ<div></div>");
24484
24485    // Test typing . do extend linked pair
24486    cx.set_state("<Animatedˇ></Animated>");
24487    cx.update_editor(|editor, _, cx| {
24488        set_linked_edit_ranges(
24489            (Point::new(0, 1), Point::new(0, 9)),
24490            (Point::new(0, 12), Point::new(0, 20)),
24491            editor,
24492            cx,
24493        );
24494    });
24495    cx.update_editor(|editor, window, cx| {
24496        editor.handle_input(".", window, cx);
24497    });
24498    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24499    cx.update_editor(|editor, _, cx| {
24500        set_linked_edit_ranges(
24501            (Point::new(0, 1), Point::new(0, 10)),
24502            (Point::new(0, 13), Point::new(0, 21)),
24503            editor,
24504            cx,
24505        );
24506    });
24507    cx.update_editor(|editor, window, cx| {
24508        editor.handle_input("V", window, cx);
24509    });
24510    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24511}
24512
24513#[gpui::test]
24514async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24515    init_test(cx, |_| {});
24516
24517    let fs = FakeFs::new(cx.executor());
24518    fs.insert_tree(
24519        path!("/root"),
24520        json!({
24521            "a": {
24522                "main.rs": "fn main() {}",
24523            },
24524            "foo": {
24525                "bar": {
24526                    "external_file.rs": "pub mod external {}",
24527                }
24528            }
24529        }),
24530    )
24531    .await;
24532
24533    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24534    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24535    language_registry.add(rust_lang());
24536    let _fake_servers = language_registry.register_fake_lsp(
24537        "Rust",
24538        FakeLspAdapter {
24539            ..FakeLspAdapter::default()
24540        },
24541    );
24542    let (workspace, cx) =
24543        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24544    let worktree_id = workspace.update(cx, |workspace, cx| {
24545        workspace.project().update(cx, |project, cx| {
24546            project.worktrees(cx).next().unwrap().read(cx).id()
24547        })
24548    });
24549
24550    let assert_language_servers_count =
24551        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24552            project.update(cx, |project, cx| {
24553                let current = project
24554                    .lsp_store()
24555                    .read(cx)
24556                    .as_local()
24557                    .unwrap()
24558                    .language_servers
24559                    .len();
24560                assert_eq!(expected, current, "{context}");
24561            });
24562        };
24563
24564    assert_language_servers_count(
24565        0,
24566        "No servers should be running before any file is open",
24567        cx,
24568    );
24569    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24570    let main_editor = workspace
24571        .update_in(cx, |workspace, window, cx| {
24572            workspace.open_path(
24573                (worktree_id, rel_path("main.rs")),
24574                Some(pane.downgrade()),
24575                true,
24576                window,
24577                cx,
24578            )
24579        })
24580        .unwrap()
24581        .await
24582        .downcast::<Editor>()
24583        .unwrap();
24584    pane.update(cx, |pane, cx| {
24585        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24586        open_editor.update(cx, |editor, cx| {
24587            assert_eq!(
24588                editor.display_text(cx),
24589                "fn main() {}",
24590                "Original main.rs text on initial open",
24591            );
24592        });
24593        assert_eq!(open_editor, main_editor);
24594    });
24595    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24596
24597    let external_editor = workspace
24598        .update_in(cx, |workspace, window, cx| {
24599            workspace.open_abs_path(
24600                PathBuf::from("/root/foo/bar/external_file.rs"),
24601                OpenOptions::default(),
24602                window,
24603                cx,
24604            )
24605        })
24606        .await
24607        .expect("opening external file")
24608        .downcast::<Editor>()
24609        .expect("downcasted external file's open element to editor");
24610    pane.update(cx, |pane, cx| {
24611        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24612        open_editor.update(cx, |editor, cx| {
24613            assert_eq!(
24614                editor.display_text(cx),
24615                "pub mod external {}",
24616                "External file is open now",
24617            );
24618        });
24619        assert_eq!(open_editor, external_editor);
24620    });
24621    assert_language_servers_count(
24622        1,
24623        "Second, external, *.rs file should join the existing server",
24624        cx,
24625    );
24626
24627    pane.update_in(cx, |pane, window, cx| {
24628        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24629    })
24630    .await
24631    .unwrap();
24632    pane.update_in(cx, |pane, window, cx| {
24633        pane.navigate_backward(&Default::default(), window, cx);
24634    });
24635    cx.run_until_parked();
24636    pane.update(cx, |pane, cx| {
24637        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24638        open_editor.update(cx, |editor, cx| {
24639            assert_eq!(
24640                editor.display_text(cx),
24641                "pub mod external {}",
24642                "External file is open now",
24643            );
24644        });
24645    });
24646    assert_language_servers_count(
24647        1,
24648        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24649        cx,
24650    );
24651
24652    cx.update(|_, cx| {
24653        workspace::reload(cx);
24654    });
24655    assert_language_servers_count(
24656        1,
24657        "After reloading the worktree with local and external files opened, only one project should be started",
24658        cx,
24659    );
24660}
24661
24662#[gpui::test]
24663async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24664    init_test(cx, |_| {});
24665
24666    let mut cx = EditorTestContext::new(cx).await;
24667    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24668    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24669
24670    // test cursor move to start of each line on tab
24671    // for `if`, `elif`, `else`, `while`, `with` and `for`
24672    cx.set_state(indoc! {"
24673        def main():
24674        ˇ    for item in items:
24675        ˇ        while item.active:
24676        ˇ            if item.value > 10:
24677        ˇ                continue
24678        ˇ            elif item.value < 0:
24679        ˇ                break
24680        ˇ            else:
24681        ˇ                with item.context() as ctx:
24682        ˇ                    yield count
24683        ˇ        else:
24684        ˇ            log('while else')
24685        ˇ    else:
24686        ˇ        log('for else')
24687    "});
24688    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24689    cx.assert_editor_state(indoc! {"
24690        def main():
24691            ˇfor item in items:
24692                ˇwhile item.active:
24693                    ˇif item.value > 10:
24694                        ˇcontinue
24695                    ˇelif item.value < 0:
24696                        ˇbreak
24697                    ˇelse:
24698                        ˇwith item.context() as ctx:
24699                            ˇyield count
24700                ˇelse:
24701                    ˇlog('while else')
24702            ˇelse:
24703                ˇlog('for else')
24704    "});
24705    // test relative indent is preserved when tab
24706    // for `if`, `elif`, `else`, `while`, `with` and `for`
24707    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24708    cx.assert_editor_state(indoc! {"
24709        def main():
24710                ˇfor item in items:
24711                    ˇwhile item.active:
24712                        ˇif item.value > 10:
24713                            ˇcontinue
24714                        ˇelif item.value < 0:
24715                            ˇbreak
24716                        ˇelse:
24717                            ˇwith item.context() as ctx:
24718                                ˇyield count
24719                    ˇelse:
24720                        ˇlog('while else')
24721                ˇelse:
24722                    ˇlog('for else')
24723    "});
24724
24725    // test cursor move to start of each line on tab
24726    // for `try`, `except`, `else`, `finally`, `match` and `def`
24727    cx.set_state(indoc! {"
24728        def main():
24729        ˇ    try:
24730        ˇ        fetch()
24731        ˇ    except ValueError:
24732        ˇ        handle_error()
24733        ˇ    else:
24734        ˇ        match value:
24735        ˇ            case _:
24736        ˇ    finally:
24737        ˇ        def status():
24738        ˇ            return 0
24739    "});
24740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24741    cx.assert_editor_state(indoc! {"
24742        def main():
24743            ˇtry:
24744                ˇfetch()
24745            ˇexcept ValueError:
24746                ˇhandle_error()
24747            ˇelse:
24748                ˇmatch value:
24749                    ˇcase _:
24750            ˇfinally:
24751                ˇdef status():
24752                    ˇreturn 0
24753    "});
24754    // test relative indent is preserved when tab
24755    // for `try`, `except`, `else`, `finally`, `match` and `def`
24756    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24757    cx.assert_editor_state(indoc! {"
24758        def main():
24759                ˇtry:
24760                    ˇfetch()
24761                ˇexcept ValueError:
24762                    ˇhandle_error()
24763                ˇelse:
24764                    ˇmatch value:
24765                        ˇcase _:
24766                ˇfinally:
24767                    ˇdef status():
24768                        ˇreturn 0
24769    "});
24770}
24771
24772#[gpui::test]
24773async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24774    init_test(cx, |_| {});
24775
24776    let mut cx = EditorTestContext::new(cx).await;
24777    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24778    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24779
24780    // test `else` auto outdents when typed inside `if` block
24781    cx.set_state(indoc! {"
24782        def main():
24783            if i == 2:
24784                return
24785                ˇ
24786    "});
24787    cx.update_editor(|editor, window, cx| {
24788        editor.handle_input("else:", window, cx);
24789    });
24790    cx.assert_editor_state(indoc! {"
24791        def main():
24792            if i == 2:
24793                return
24794            else:ˇ
24795    "});
24796
24797    // test `except` auto outdents when typed inside `try` block
24798    cx.set_state(indoc! {"
24799        def main():
24800            try:
24801                i = 2
24802                ˇ
24803    "});
24804    cx.update_editor(|editor, window, cx| {
24805        editor.handle_input("except:", window, cx);
24806    });
24807    cx.assert_editor_state(indoc! {"
24808        def main():
24809            try:
24810                i = 2
24811            except:ˇ
24812    "});
24813
24814    // test `else` auto outdents when typed inside `except` block
24815    cx.set_state(indoc! {"
24816        def main():
24817            try:
24818                i = 2
24819            except:
24820                j = 2
24821                ˇ
24822    "});
24823    cx.update_editor(|editor, window, cx| {
24824        editor.handle_input("else:", window, cx);
24825    });
24826    cx.assert_editor_state(indoc! {"
24827        def main():
24828            try:
24829                i = 2
24830            except:
24831                j = 2
24832            else:ˇ
24833    "});
24834
24835    // test `finally` auto outdents when typed inside `else` block
24836    cx.set_state(indoc! {"
24837        def main():
24838            try:
24839                i = 2
24840            except:
24841                j = 2
24842            else:
24843                k = 2
24844                ˇ
24845    "});
24846    cx.update_editor(|editor, window, cx| {
24847        editor.handle_input("finally:", window, cx);
24848    });
24849    cx.assert_editor_state(indoc! {"
24850        def main():
24851            try:
24852                i = 2
24853            except:
24854                j = 2
24855            else:
24856                k = 2
24857            finally:ˇ
24858    "});
24859
24860    // test `else` does not outdents when typed inside `except` block right after for block
24861    cx.set_state(indoc! {"
24862        def main():
24863            try:
24864                i = 2
24865            except:
24866                for i in range(n):
24867                    pass
24868                ˇ
24869    "});
24870    cx.update_editor(|editor, window, cx| {
24871        editor.handle_input("else:", window, cx);
24872    });
24873    cx.assert_editor_state(indoc! {"
24874        def main():
24875            try:
24876                i = 2
24877            except:
24878                for i in range(n):
24879                    pass
24880                else:ˇ
24881    "});
24882
24883    // test `finally` auto outdents when typed inside `else` block right after for block
24884    cx.set_state(indoc! {"
24885        def main():
24886            try:
24887                i = 2
24888            except:
24889                j = 2
24890            else:
24891                for i in range(n):
24892                    pass
24893                ˇ
24894    "});
24895    cx.update_editor(|editor, window, cx| {
24896        editor.handle_input("finally:", window, cx);
24897    });
24898    cx.assert_editor_state(indoc! {"
24899        def main():
24900            try:
24901                i = 2
24902            except:
24903                j = 2
24904            else:
24905                for i in range(n):
24906                    pass
24907            finally:ˇ
24908    "});
24909
24910    // test `except` outdents to inner "try" block
24911    cx.set_state(indoc! {"
24912        def main():
24913            try:
24914                i = 2
24915                if i == 2:
24916                    try:
24917                        i = 3
24918                        ˇ
24919    "});
24920    cx.update_editor(|editor, window, cx| {
24921        editor.handle_input("except:", window, cx);
24922    });
24923    cx.assert_editor_state(indoc! {"
24924        def main():
24925            try:
24926                i = 2
24927                if i == 2:
24928                    try:
24929                        i = 3
24930                    except:ˇ
24931    "});
24932
24933    // test `except` outdents to outer "try" block
24934    cx.set_state(indoc! {"
24935        def main():
24936            try:
24937                i = 2
24938                if i == 2:
24939                    try:
24940                        i = 3
24941                ˇ
24942    "});
24943    cx.update_editor(|editor, window, cx| {
24944        editor.handle_input("except:", window, cx);
24945    });
24946    cx.assert_editor_state(indoc! {"
24947        def main():
24948            try:
24949                i = 2
24950                if i == 2:
24951                    try:
24952                        i = 3
24953            except:ˇ
24954    "});
24955
24956    // test `else` stays at correct indent when typed after `for` block
24957    cx.set_state(indoc! {"
24958        def main():
24959            for i in range(10):
24960                if i == 3:
24961                    break
24962            ˇ
24963    "});
24964    cx.update_editor(|editor, window, cx| {
24965        editor.handle_input("else:", window, cx);
24966    });
24967    cx.assert_editor_state(indoc! {"
24968        def main():
24969            for i in range(10):
24970                if i == 3:
24971                    break
24972            else:ˇ
24973    "});
24974
24975    // test does not outdent on typing after line with square brackets
24976    cx.set_state(indoc! {"
24977        def f() -> list[str]:
24978            ˇ
24979    "});
24980    cx.update_editor(|editor, window, cx| {
24981        editor.handle_input("a", window, cx);
24982    });
24983    cx.assert_editor_state(indoc! {"
24984        def f() -> list[str]:
2498524986    "});
24987
24988    // test does not outdent on typing : after case keyword
24989    cx.set_state(indoc! {"
24990        match 1:
24991            caseˇ
24992    "});
24993    cx.update_editor(|editor, window, cx| {
24994        editor.handle_input(":", window, cx);
24995    });
24996    cx.assert_editor_state(indoc! {"
24997        match 1:
24998            case:ˇ
24999    "});
25000}
25001
25002#[gpui::test]
25003async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25004    init_test(cx, |_| {});
25005    update_test_language_settings(cx, |settings| {
25006        settings.defaults.extend_comment_on_newline = Some(false);
25007    });
25008    let mut cx = EditorTestContext::new(cx).await;
25009    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25010    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25011
25012    // test correct indent after newline on comment
25013    cx.set_state(indoc! {"
25014        # COMMENT:ˇ
25015    "});
25016    cx.update_editor(|editor, window, cx| {
25017        editor.newline(&Newline, window, cx);
25018    });
25019    cx.assert_editor_state(indoc! {"
25020        # COMMENT:
25021        ˇ
25022    "});
25023
25024    // test correct indent after newline in brackets
25025    cx.set_state(indoc! {"
25026        {ˇ}
25027    "});
25028    cx.update_editor(|editor, window, cx| {
25029        editor.newline(&Newline, window, cx);
25030    });
25031    cx.run_until_parked();
25032    cx.assert_editor_state(indoc! {"
25033        {
25034            ˇ
25035        }
25036    "});
25037
25038    cx.set_state(indoc! {"
25039        (ˇ)
25040    "});
25041    cx.update_editor(|editor, window, cx| {
25042        editor.newline(&Newline, window, cx);
25043    });
25044    cx.run_until_parked();
25045    cx.assert_editor_state(indoc! {"
25046        (
25047            ˇ
25048        )
25049    "});
25050
25051    // do not indent after empty lists or dictionaries
25052    cx.set_state(indoc! {"
25053        a = []ˇ
25054    "});
25055    cx.update_editor(|editor, window, cx| {
25056        editor.newline(&Newline, window, cx);
25057    });
25058    cx.run_until_parked();
25059    cx.assert_editor_state(indoc! {"
25060        a = []
25061        ˇ
25062    "});
25063}
25064
25065#[gpui::test]
25066async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25067    init_test(cx, |_| {});
25068
25069    let mut cx = EditorTestContext::new(cx).await;
25070    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25071    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25072
25073    // test cursor move to start of each line on tab
25074    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25075    cx.set_state(indoc! {"
25076        function main() {
25077        ˇ    for item in $items; do
25078        ˇ        while [ -n \"$item\" ]; do
25079        ˇ            if [ \"$value\" -gt 10 ]; then
25080        ˇ                continue
25081        ˇ            elif [ \"$value\" -lt 0 ]; then
25082        ˇ                break
25083        ˇ            else
25084        ˇ                echo \"$item\"
25085        ˇ            fi
25086        ˇ        done
25087        ˇ    done
25088        ˇ}
25089    "});
25090    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25091    cx.assert_editor_state(indoc! {"
25092        function main() {
25093            ˇfor item in $items; do
25094                ˇwhile [ -n \"$item\" ]; do
25095                    ˇif [ \"$value\" -gt 10 ]; then
25096                        ˇcontinue
25097                    ˇelif [ \"$value\" -lt 0 ]; then
25098                        ˇbreak
25099                    ˇelse
25100                        ˇecho \"$item\"
25101                    ˇfi
25102                ˇdone
25103            ˇdone
25104        ˇ}
25105    "});
25106    // test relative indent is preserved when tab
25107    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25108    cx.assert_editor_state(indoc! {"
25109        function main() {
25110                ˇfor item in $items; do
25111                    ˇwhile [ -n \"$item\" ]; do
25112                        ˇif [ \"$value\" -gt 10 ]; then
25113                            ˇcontinue
25114                        ˇelif [ \"$value\" -lt 0 ]; then
25115                            ˇbreak
25116                        ˇelse
25117                            ˇecho \"$item\"
25118                        ˇfi
25119                    ˇdone
25120                ˇdone
25121            ˇ}
25122    "});
25123
25124    // test cursor move to start of each line on tab
25125    // for `case` statement with patterns
25126    cx.set_state(indoc! {"
25127        function handle() {
25128        ˇ    case \"$1\" in
25129        ˇ        start)
25130        ˇ            echo \"a\"
25131        ˇ            ;;
25132        ˇ        stop)
25133        ˇ            echo \"b\"
25134        ˇ            ;;
25135        ˇ        *)
25136        ˇ            echo \"c\"
25137        ˇ            ;;
25138        ˇ    esac
25139        ˇ}
25140    "});
25141    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25142    cx.assert_editor_state(indoc! {"
25143        function handle() {
25144            ˇcase \"$1\" in
25145                ˇstart)
25146                    ˇecho \"a\"
25147                    ˇ;;
25148                ˇstop)
25149                    ˇecho \"b\"
25150                    ˇ;;
25151                ˇ*)
25152                    ˇecho \"c\"
25153                    ˇ;;
25154            ˇesac
25155        ˇ}
25156    "});
25157}
25158
25159#[gpui::test]
25160async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25161    init_test(cx, |_| {});
25162
25163    let mut cx = EditorTestContext::new(cx).await;
25164    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25165    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25166
25167    // test indents on comment insert
25168    cx.set_state(indoc! {"
25169        function main() {
25170        ˇ    for item in $items; do
25171        ˇ        while [ -n \"$item\" ]; do
25172        ˇ            if [ \"$value\" -gt 10 ]; then
25173        ˇ                continue
25174        ˇ            elif [ \"$value\" -lt 0 ]; then
25175        ˇ                break
25176        ˇ            else
25177        ˇ                echo \"$item\"
25178        ˇ            fi
25179        ˇ        done
25180        ˇ    done
25181        ˇ}
25182    "});
25183    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25184    cx.assert_editor_state(indoc! {"
25185        function main() {
25186        #ˇ    for item in $items; do
25187        #ˇ        while [ -n \"$item\" ]; do
25188        #ˇ            if [ \"$value\" -gt 10 ]; then
25189        #ˇ                continue
25190        #ˇ            elif [ \"$value\" -lt 0 ]; then
25191        #ˇ                break
25192        #ˇ            else
25193        #ˇ                echo \"$item\"
25194        #ˇ            fi
25195        #ˇ        done
25196        #ˇ    done
25197        #ˇ}
25198    "});
25199}
25200
25201#[gpui::test]
25202async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25203    init_test(cx, |_| {});
25204
25205    let mut cx = EditorTestContext::new(cx).await;
25206    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25207    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25208
25209    // test `else` auto outdents when typed inside `if` block
25210    cx.set_state(indoc! {"
25211        if [ \"$1\" = \"test\" ]; then
25212            echo \"foo bar\"
25213            ˇ
25214    "});
25215    cx.update_editor(|editor, window, cx| {
25216        editor.handle_input("else", window, cx);
25217    });
25218    cx.assert_editor_state(indoc! {"
25219        if [ \"$1\" = \"test\" ]; then
25220            echo \"foo bar\"
25221        elseˇ
25222    "});
25223
25224    // test `elif` auto outdents when typed inside `if` block
25225    cx.set_state(indoc! {"
25226        if [ \"$1\" = \"test\" ]; then
25227            echo \"foo bar\"
25228            ˇ
25229    "});
25230    cx.update_editor(|editor, window, cx| {
25231        editor.handle_input("elif", window, cx);
25232    });
25233    cx.assert_editor_state(indoc! {"
25234        if [ \"$1\" = \"test\" ]; then
25235            echo \"foo bar\"
25236        elifˇ
25237    "});
25238
25239    // test `fi` auto outdents when typed inside `else` block
25240    cx.set_state(indoc! {"
25241        if [ \"$1\" = \"test\" ]; then
25242            echo \"foo bar\"
25243        else
25244            echo \"bar baz\"
25245            ˇ
25246    "});
25247    cx.update_editor(|editor, window, cx| {
25248        editor.handle_input("fi", window, cx);
25249    });
25250    cx.assert_editor_state(indoc! {"
25251        if [ \"$1\" = \"test\" ]; then
25252            echo \"foo bar\"
25253        else
25254            echo \"bar baz\"
25255        fiˇ
25256    "});
25257
25258    // test `done` auto outdents when typed inside `while` block
25259    cx.set_state(indoc! {"
25260        while read line; do
25261            echo \"$line\"
25262            ˇ
25263    "});
25264    cx.update_editor(|editor, window, cx| {
25265        editor.handle_input("done", window, cx);
25266    });
25267    cx.assert_editor_state(indoc! {"
25268        while read line; do
25269            echo \"$line\"
25270        doneˇ
25271    "});
25272
25273    // test `done` auto outdents when typed inside `for` block
25274    cx.set_state(indoc! {"
25275        for file in *.txt; do
25276            cat \"$file\"
25277            ˇ
25278    "});
25279    cx.update_editor(|editor, window, cx| {
25280        editor.handle_input("done", window, cx);
25281    });
25282    cx.assert_editor_state(indoc! {"
25283        for file in *.txt; do
25284            cat \"$file\"
25285        doneˇ
25286    "});
25287
25288    // test `esac` auto outdents when typed inside `case` block
25289    cx.set_state(indoc! {"
25290        case \"$1\" in
25291            start)
25292                echo \"foo bar\"
25293                ;;
25294            stop)
25295                echo \"bar baz\"
25296                ;;
25297            ˇ
25298    "});
25299    cx.update_editor(|editor, window, cx| {
25300        editor.handle_input("esac", window, cx);
25301    });
25302    cx.assert_editor_state(indoc! {"
25303        case \"$1\" in
25304            start)
25305                echo \"foo bar\"
25306                ;;
25307            stop)
25308                echo \"bar baz\"
25309                ;;
25310        esacˇ
25311    "});
25312
25313    // test `*)` auto outdents when typed inside `case` block
25314    cx.set_state(indoc! {"
25315        case \"$1\" in
25316            start)
25317                echo \"foo bar\"
25318                ;;
25319                ˇ
25320    "});
25321    cx.update_editor(|editor, window, cx| {
25322        editor.handle_input("*)", window, cx);
25323    });
25324    cx.assert_editor_state(indoc! {"
25325        case \"$1\" in
25326            start)
25327                echo \"foo bar\"
25328                ;;
25329            *)ˇ
25330    "});
25331
25332    // test `fi` outdents to correct level with nested if blocks
25333    cx.set_state(indoc! {"
25334        if [ \"$1\" = \"test\" ]; then
25335            echo \"outer if\"
25336            if [ \"$2\" = \"debug\" ]; then
25337                echo \"inner if\"
25338                ˇ
25339    "});
25340    cx.update_editor(|editor, window, cx| {
25341        editor.handle_input("fi", window, cx);
25342    });
25343    cx.assert_editor_state(indoc! {"
25344        if [ \"$1\" = \"test\" ]; then
25345            echo \"outer if\"
25346            if [ \"$2\" = \"debug\" ]; then
25347                echo \"inner if\"
25348            fiˇ
25349    "});
25350}
25351
25352#[gpui::test]
25353async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25354    init_test(cx, |_| {});
25355    update_test_language_settings(cx, |settings| {
25356        settings.defaults.extend_comment_on_newline = Some(false);
25357    });
25358    let mut cx = EditorTestContext::new(cx).await;
25359    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25360    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25361
25362    // test correct indent after newline on comment
25363    cx.set_state(indoc! {"
25364        # COMMENT:ˇ
25365    "});
25366    cx.update_editor(|editor, window, cx| {
25367        editor.newline(&Newline, window, cx);
25368    });
25369    cx.assert_editor_state(indoc! {"
25370        # COMMENT:
25371        ˇ
25372    "});
25373
25374    // test correct indent after newline after `then`
25375    cx.set_state(indoc! {"
25376
25377        if [ \"$1\" = \"test\" ]; thenˇ
25378    "});
25379    cx.update_editor(|editor, window, cx| {
25380        editor.newline(&Newline, window, cx);
25381    });
25382    cx.run_until_parked();
25383    cx.assert_editor_state(indoc! {"
25384
25385        if [ \"$1\" = \"test\" ]; then
25386            ˇ
25387    "});
25388
25389    // test correct indent after newline after `else`
25390    cx.set_state(indoc! {"
25391        if [ \"$1\" = \"test\" ]; then
25392        elseˇ
25393    "});
25394    cx.update_editor(|editor, window, cx| {
25395        editor.newline(&Newline, window, cx);
25396    });
25397    cx.run_until_parked();
25398    cx.assert_editor_state(indoc! {"
25399        if [ \"$1\" = \"test\" ]; then
25400        else
25401            ˇ
25402    "});
25403
25404    // test correct indent after newline after `elif`
25405    cx.set_state(indoc! {"
25406        if [ \"$1\" = \"test\" ]; then
25407        elifˇ
25408    "});
25409    cx.update_editor(|editor, window, cx| {
25410        editor.newline(&Newline, window, cx);
25411    });
25412    cx.run_until_parked();
25413    cx.assert_editor_state(indoc! {"
25414        if [ \"$1\" = \"test\" ]; then
25415        elif
25416            ˇ
25417    "});
25418
25419    // test correct indent after newline after `do`
25420    cx.set_state(indoc! {"
25421        for file in *.txt; doˇ
25422    "});
25423    cx.update_editor(|editor, window, cx| {
25424        editor.newline(&Newline, window, cx);
25425    });
25426    cx.run_until_parked();
25427    cx.assert_editor_state(indoc! {"
25428        for file in *.txt; do
25429            ˇ
25430    "});
25431
25432    // test correct indent after newline after case pattern
25433    cx.set_state(indoc! {"
25434        case \"$1\" in
25435            start)ˇ
25436    "});
25437    cx.update_editor(|editor, window, cx| {
25438        editor.newline(&Newline, window, cx);
25439    });
25440    cx.run_until_parked();
25441    cx.assert_editor_state(indoc! {"
25442        case \"$1\" in
25443            start)
25444                ˇ
25445    "});
25446
25447    // test correct indent after newline after case pattern
25448    cx.set_state(indoc! {"
25449        case \"$1\" in
25450            start)
25451                ;;
25452            *)ˇ
25453    "});
25454    cx.update_editor(|editor, window, cx| {
25455        editor.newline(&Newline, window, cx);
25456    });
25457    cx.run_until_parked();
25458    cx.assert_editor_state(indoc! {"
25459        case \"$1\" in
25460            start)
25461                ;;
25462            *)
25463                ˇ
25464    "});
25465
25466    // test correct indent after newline after function opening brace
25467    cx.set_state(indoc! {"
25468        function test() {ˇ}
25469    "});
25470    cx.update_editor(|editor, window, cx| {
25471        editor.newline(&Newline, window, cx);
25472    });
25473    cx.run_until_parked();
25474    cx.assert_editor_state(indoc! {"
25475        function test() {
25476            ˇ
25477        }
25478    "});
25479
25480    // test no extra indent after semicolon on same line
25481    cx.set_state(indoc! {"
25482        echo \"test\"25483    "});
25484    cx.update_editor(|editor, window, cx| {
25485        editor.newline(&Newline, window, cx);
25486    });
25487    cx.run_until_parked();
25488    cx.assert_editor_state(indoc! {"
25489        echo \"test\";
25490        ˇ
25491    "});
25492}
25493
25494fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25495    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25496    point..point
25497}
25498
25499#[track_caller]
25500fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25501    let (text, ranges) = marked_text_ranges(marked_text, true);
25502    assert_eq!(editor.text(cx), text);
25503    assert_eq!(
25504        editor.selections.ranges(&editor.display_snapshot(cx)),
25505        ranges,
25506        "Assert selections are {}",
25507        marked_text
25508    );
25509}
25510
25511pub fn handle_signature_help_request(
25512    cx: &mut EditorLspTestContext,
25513    mocked_response: lsp::SignatureHelp,
25514) -> impl Future<Output = ()> + use<> {
25515    let mut request =
25516        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25517            let mocked_response = mocked_response.clone();
25518            async move { Ok(Some(mocked_response)) }
25519        });
25520
25521    async move {
25522        request.next().await;
25523    }
25524}
25525
25526#[track_caller]
25527pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25528    cx.update_editor(|editor, _, _| {
25529        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25530            let entries = menu.entries.borrow();
25531            let entries = entries
25532                .iter()
25533                .map(|entry| entry.string.as_str())
25534                .collect::<Vec<_>>();
25535            assert_eq!(entries, expected);
25536        } else {
25537            panic!("Expected completions menu");
25538        }
25539    });
25540}
25541
25542/// Handle completion request passing a marked string specifying where the completion
25543/// should be triggered from using '|' character, what range should be replaced, and what completions
25544/// should be returned using '<' and '>' to delimit the range.
25545///
25546/// Also see `handle_completion_request_with_insert_and_replace`.
25547#[track_caller]
25548pub fn handle_completion_request(
25549    marked_string: &str,
25550    completions: Vec<&'static str>,
25551    is_incomplete: bool,
25552    counter: Arc<AtomicUsize>,
25553    cx: &mut EditorLspTestContext,
25554) -> impl Future<Output = ()> {
25555    let complete_from_marker: TextRangeMarker = '|'.into();
25556    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25557    let (_, mut marked_ranges) = marked_text_ranges_by(
25558        marked_string,
25559        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25560    );
25561
25562    let complete_from_position =
25563        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25564    let replace_range =
25565        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25566
25567    let mut request =
25568        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25569            let completions = completions.clone();
25570            counter.fetch_add(1, atomic::Ordering::Release);
25571            async move {
25572                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25573                assert_eq!(
25574                    params.text_document_position.position,
25575                    complete_from_position
25576                );
25577                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25578                    is_incomplete,
25579                    item_defaults: None,
25580                    items: completions
25581                        .iter()
25582                        .map(|completion_text| lsp::CompletionItem {
25583                            label: completion_text.to_string(),
25584                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25585                                range: replace_range,
25586                                new_text: completion_text.to_string(),
25587                            })),
25588                            ..Default::default()
25589                        })
25590                        .collect(),
25591                })))
25592            }
25593        });
25594
25595    async move {
25596        request.next().await;
25597    }
25598}
25599
25600/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25601/// given instead, which also contains an `insert` range.
25602///
25603/// This function uses markers to define ranges:
25604/// - `|` marks the cursor position
25605/// - `<>` marks the replace range
25606/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25607pub fn handle_completion_request_with_insert_and_replace(
25608    cx: &mut EditorLspTestContext,
25609    marked_string: &str,
25610    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25611    counter: Arc<AtomicUsize>,
25612) -> impl Future<Output = ()> {
25613    let complete_from_marker: TextRangeMarker = '|'.into();
25614    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25615    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25616
25617    let (_, mut marked_ranges) = marked_text_ranges_by(
25618        marked_string,
25619        vec![
25620            complete_from_marker.clone(),
25621            replace_range_marker.clone(),
25622            insert_range_marker.clone(),
25623        ],
25624    );
25625
25626    let complete_from_position =
25627        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25628    let replace_range =
25629        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25630
25631    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25632        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25633        _ => lsp::Range {
25634            start: replace_range.start,
25635            end: complete_from_position,
25636        },
25637    };
25638
25639    let mut request =
25640        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25641            let completions = completions.clone();
25642            counter.fetch_add(1, atomic::Ordering::Release);
25643            async move {
25644                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25645                assert_eq!(
25646                    params.text_document_position.position, complete_from_position,
25647                    "marker `|` position doesn't match",
25648                );
25649                Ok(Some(lsp::CompletionResponse::Array(
25650                    completions
25651                        .iter()
25652                        .map(|(label, new_text)| lsp::CompletionItem {
25653                            label: label.to_string(),
25654                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25655                                lsp::InsertReplaceEdit {
25656                                    insert: insert_range,
25657                                    replace: replace_range,
25658                                    new_text: new_text.to_string(),
25659                                },
25660                            )),
25661                            ..Default::default()
25662                        })
25663                        .collect(),
25664                )))
25665            }
25666        });
25667
25668    async move {
25669        request.next().await;
25670    }
25671}
25672
25673fn handle_resolve_completion_request(
25674    cx: &mut EditorLspTestContext,
25675    edits: Option<Vec<(&'static str, &'static str)>>,
25676) -> impl Future<Output = ()> {
25677    let edits = edits.map(|edits| {
25678        edits
25679            .iter()
25680            .map(|(marked_string, new_text)| {
25681                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25682                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25683                lsp::TextEdit::new(replace_range, new_text.to_string())
25684            })
25685            .collect::<Vec<_>>()
25686    });
25687
25688    let mut request =
25689        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25690            let edits = edits.clone();
25691            async move {
25692                Ok(lsp::CompletionItem {
25693                    additional_text_edits: edits,
25694                    ..Default::default()
25695                })
25696            }
25697        });
25698
25699    async move {
25700        request.next().await;
25701    }
25702}
25703
25704pub(crate) fn update_test_language_settings(
25705    cx: &mut TestAppContext,
25706    f: impl Fn(&mut AllLanguageSettingsContent),
25707) {
25708    cx.update(|cx| {
25709        SettingsStore::update_global(cx, |store, cx| {
25710            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25711        });
25712    });
25713}
25714
25715pub(crate) fn update_test_project_settings(
25716    cx: &mut TestAppContext,
25717    f: impl Fn(&mut ProjectSettingsContent),
25718) {
25719    cx.update(|cx| {
25720        SettingsStore::update_global(cx, |store, cx| {
25721            store.update_user_settings(cx, |settings| f(&mut settings.project));
25722        });
25723    });
25724}
25725
25726pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25727    cx.update(|cx| {
25728        assets::Assets.load_test_fonts(cx);
25729        let store = SettingsStore::test(cx);
25730        cx.set_global(store);
25731        theme::init(theme::LoadThemes::JustBase, cx);
25732        release_channel::init(SemanticVersion::default(), cx);
25733        client::init_settings(cx);
25734        language::init(cx);
25735        Project::init_settings(cx);
25736        workspace::init_settings(cx);
25737        crate::init(cx);
25738    });
25739    zlog::init_test();
25740    update_test_language_settings(cx, f);
25741}
25742
25743#[track_caller]
25744fn assert_hunk_revert(
25745    not_reverted_text_with_selections: &str,
25746    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25747    expected_reverted_text_with_selections: &str,
25748    base_text: &str,
25749    cx: &mut EditorLspTestContext,
25750) {
25751    cx.set_state(not_reverted_text_with_selections);
25752    cx.set_head_text(base_text);
25753    cx.executor().run_until_parked();
25754
25755    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25756        let snapshot = editor.snapshot(window, cx);
25757        let reverted_hunk_statuses = snapshot
25758            .buffer_snapshot()
25759            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25760            .map(|hunk| hunk.status().kind)
25761            .collect::<Vec<_>>();
25762
25763        editor.git_restore(&Default::default(), window, cx);
25764        reverted_hunk_statuses
25765    });
25766    cx.executor().run_until_parked();
25767    cx.assert_editor_state(expected_reverted_text_with_selections);
25768    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25769}
25770
25771#[gpui::test(iterations = 10)]
25772async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25773    init_test(cx, |_| {});
25774
25775    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25776    let counter = diagnostic_requests.clone();
25777
25778    let fs = FakeFs::new(cx.executor());
25779    fs.insert_tree(
25780        path!("/a"),
25781        json!({
25782            "first.rs": "fn main() { let a = 5; }",
25783            "second.rs": "// Test file",
25784        }),
25785    )
25786    .await;
25787
25788    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25789    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25790    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25791
25792    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25793    language_registry.add(rust_lang());
25794    let mut fake_servers = language_registry.register_fake_lsp(
25795        "Rust",
25796        FakeLspAdapter {
25797            capabilities: lsp::ServerCapabilities {
25798                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25799                    lsp::DiagnosticOptions {
25800                        identifier: None,
25801                        inter_file_dependencies: true,
25802                        workspace_diagnostics: true,
25803                        work_done_progress_options: Default::default(),
25804                    },
25805                )),
25806                ..Default::default()
25807            },
25808            ..Default::default()
25809        },
25810    );
25811
25812    let editor = workspace
25813        .update(cx, |workspace, window, cx| {
25814            workspace.open_abs_path(
25815                PathBuf::from(path!("/a/first.rs")),
25816                OpenOptions::default(),
25817                window,
25818                cx,
25819            )
25820        })
25821        .unwrap()
25822        .await
25823        .unwrap()
25824        .downcast::<Editor>()
25825        .unwrap();
25826    let fake_server = fake_servers.next().await.unwrap();
25827    let server_id = fake_server.server.server_id();
25828    let mut first_request = fake_server
25829        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25830            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25831            let result_id = Some(new_result_id.to_string());
25832            assert_eq!(
25833                params.text_document.uri,
25834                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25835            );
25836            async move {
25837                Ok(lsp::DocumentDiagnosticReportResult::Report(
25838                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25839                        related_documents: None,
25840                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25841                            items: Vec::new(),
25842                            result_id,
25843                        },
25844                    }),
25845                ))
25846            }
25847        });
25848
25849    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25850        project.update(cx, |project, cx| {
25851            let buffer_id = editor
25852                .read(cx)
25853                .buffer()
25854                .read(cx)
25855                .as_singleton()
25856                .expect("created a singleton buffer")
25857                .read(cx)
25858                .remote_id();
25859            let buffer_result_id = project
25860                .lsp_store()
25861                .read(cx)
25862                .result_id(server_id, buffer_id, cx);
25863            assert_eq!(expected, buffer_result_id);
25864        });
25865    };
25866
25867    ensure_result_id(None, cx);
25868    cx.executor().advance_clock(Duration::from_millis(60));
25869    cx.executor().run_until_parked();
25870    assert_eq!(
25871        diagnostic_requests.load(atomic::Ordering::Acquire),
25872        1,
25873        "Opening file should trigger diagnostic request"
25874    );
25875    first_request
25876        .next()
25877        .await
25878        .expect("should have sent the first diagnostics pull request");
25879    ensure_result_id(Some("1".to_string()), cx);
25880
25881    // Editing should trigger diagnostics
25882    editor.update_in(cx, |editor, window, cx| {
25883        editor.handle_input("2", window, cx)
25884    });
25885    cx.executor().advance_clock(Duration::from_millis(60));
25886    cx.executor().run_until_parked();
25887    assert_eq!(
25888        diagnostic_requests.load(atomic::Ordering::Acquire),
25889        2,
25890        "Editing should trigger diagnostic request"
25891    );
25892    ensure_result_id(Some("2".to_string()), cx);
25893
25894    // Moving cursor should not trigger diagnostic request
25895    editor.update_in(cx, |editor, window, cx| {
25896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25897            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25898        });
25899    });
25900    cx.executor().advance_clock(Duration::from_millis(60));
25901    cx.executor().run_until_parked();
25902    assert_eq!(
25903        diagnostic_requests.load(atomic::Ordering::Acquire),
25904        2,
25905        "Cursor movement should not trigger diagnostic request"
25906    );
25907    ensure_result_id(Some("2".to_string()), cx);
25908    // Multiple rapid edits should be debounced
25909    for _ in 0..5 {
25910        editor.update_in(cx, |editor, window, cx| {
25911            editor.handle_input("x", window, cx)
25912        });
25913    }
25914    cx.executor().advance_clock(Duration::from_millis(60));
25915    cx.executor().run_until_parked();
25916
25917    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25918    assert!(
25919        final_requests <= 4,
25920        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25921    );
25922    ensure_result_id(Some(final_requests.to_string()), cx);
25923}
25924
25925#[gpui::test]
25926async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25927    // Regression test for issue #11671
25928    // Previously, adding a cursor after moving multiple cursors would reset
25929    // the cursor count instead of adding to the existing cursors.
25930    init_test(cx, |_| {});
25931    let mut cx = EditorTestContext::new(cx).await;
25932
25933    // Create a simple buffer with cursor at start
25934    cx.set_state(indoc! {"
25935        ˇaaaa
25936        bbbb
25937        cccc
25938        dddd
25939        eeee
25940        ffff
25941        gggg
25942        hhhh"});
25943
25944    // Add 2 cursors below (so we have 3 total)
25945    cx.update_editor(|editor, window, cx| {
25946        editor.add_selection_below(&Default::default(), window, cx);
25947        editor.add_selection_below(&Default::default(), window, cx);
25948    });
25949
25950    // Verify we have 3 cursors
25951    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25952    assert_eq!(
25953        initial_count, 3,
25954        "Should have 3 cursors after adding 2 below"
25955    );
25956
25957    // Move down one line
25958    cx.update_editor(|editor, window, cx| {
25959        editor.move_down(&MoveDown, window, cx);
25960    });
25961
25962    // Add another cursor below
25963    cx.update_editor(|editor, window, cx| {
25964        editor.add_selection_below(&Default::default(), window, cx);
25965    });
25966
25967    // Should now have 4 cursors (3 original + 1 new)
25968    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25969    assert_eq!(
25970        final_count, 4,
25971        "Should have 4 cursors after moving and adding another"
25972    );
25973}
25974
25975#[gpui::test]
25976async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25977    init_test(cx, |_| {});
25978
25979    let mut cx = EditorTestContext::new(cx).await;
25980
25981    cx.set_state(indoc!(
25982        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25983           Second line here"#
25984    ));
25985
25986    cx.update_editor(|editor, window, cx| {
25987        // Enable soft wrapping with a narrow width to force soft wrapping and
25988        // confirm that more than 2 rows are being displayed.
25989        editor.set_wrap_width(Some(100.0.into()), cx);
25990        assert!(editor.display_text(cx).lines().count() > 2);
25991
25992        editor.add_selection_below(
25993            &AddSelectionBelow {
25994                skip_soft_wrap: true,
25995            },
25996            window,
25997            cx,
25998        );
25999
26000        assert_eq!(
26001            editor.selections.display_ranges(cx),
26002            &[
26003                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26004                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26005            ]
26006        );
26007
26008        editor.add_selection_above(
26009            &AddSelectionAbove {
26010                skip_soft_wrap: true,
26011            },
26012            window,
26013            cx,
26014        );
26015
26016        assert_eq!(
26017            editor.selections.display_ranges(cx),
26018            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26019        );
26020
26021        editor.add_selection_below(
26022            &AddSelectionBelow {
26023                skip_soft_wrap: false,
26024            },
26025            window,
26026            cx,
26027        );
26028
26029        assert_eq!(
26030            editor.selections.display_ranges(cx),
26031            &[
26032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26034            ]
26035        );
26036
26037        editor.add_selection_above(
26038            &AddSelectionAbove {
26039                skip_soft_wrap: false,
26040            },
26041            window,
26042            cx,
26043        );
26044
26045        assert_eq!(
26046            editor.selections.display_ranges(cx),
26047            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26048        );
26049    });
26050}
26051
26052#[gpui::test(iterations = 10)]
26053async fn test_document_colors(cx: &mut TestAppContext) {
26054    let expected_color = Rgba {
26055        r: 0.33,
26056        g: 0.33,
26057        b: 0.33,
26058        a: 0.33,
26059    };
26060
26061    init_test(cx, |_| {});
26062
26063    let fs = FakeFs::new(cx.executor());
26064    fs.insert_tree(
26065        path!("/a"),
26066        json!({
26067            "first.rs": "fn main() { let a = 5; }",
26068        }),
26069    )
26070    .await;
26071
26072    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26073    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26074    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26075
26076    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26077    language_registry.add(rust_lang());
26078    let mut fake_servers = language_registry.register_fake_lsp(
26079        "Rust",
26080        FakeLspAdapter {
26081            capabilities: lsp::ServerCapabilities {
26082                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26083                ..lsp::ServerCapabilities::default()
26084            },
26085            name: "rust-analyzer",
26086            ..FakeLspAdapter::default()
26087        },
26088    );
26089    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26090        "Rust",
26091        FakeLspAdapter {
26092            capabilities: lsp::ServerCapabilities {
26093                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26094                ..lsp::ServerCapabilities::default()
26095            },
26096            name: "not-rust-analyzer",
26097            ..FakeLspAdapter::default()
26098        },
26099    );
26100
26101    let editor = workspace
26102        .update(cx, |workspace, window, cx| {
26103            workspace.open_abs_path(
26104                PathBuf::from(path!("/a/first.rs")),
26105                OpenOptions::default(),
26106                window,
26107                cx,
26108            )
26109        })
26110        .unwrap()
26111        .await
26112        .unwrap()
26113        .downcast::<Editor>()
26114        .unwrap();
26115    let fake_language_server = fake_servers.next().await.unwrap();
26116    let fake_language_server_without_capabilities =
26117        fake_servers_without_capabilities.next().await.unwrap();
26118    let requests_made = Arc::new(AtomicUsize::new(0));
26119    let closure_requests_made = Arc::clone(&requests_made);
26120    let mut color_request_handle = fake_language_server
26121        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26122            let requests_made = Arc::clone(&closure_requests_made);
26123            async move {
26124                assert_eq!(
26125                    params.text_document.uri,
26126                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26127                );
26128                requests_made.fetch_add(1, atomic::Ordering::Release);
26129                Ok(vec![
26130                    lsp::ColorInformation {
26131                        range: lsp::Range {
26132                            start: lsp::Position {
26133                                line: 0,
26134                                character: 0,
26135                            },
26136                            end: lsp::Position {
26137                                line: 0,
26138                                character: 1,
26139                            },
26140                        },
26141                        color: lsp::Color {
26142                            red: 0.33,
26143                            green: 0.33,
26144                            blue: 0.33,
26145                            alpha: 0.33,
26146                        },
26147                    },
26148                    lsp::ColorInformation {
26149                        range: lsp::Range {
26150                            start: lsp::Position {
26151                                line: 0,
26152                                character: 0,
26153                            },
26154                            end: lsp::Position {
26155                                line: 0,
26156                                character: 1,
26157                            },
26158                        },
26159                        color: lsp::Color {
26160                            red: 0.33,
26161                            green: 0.33,
26162                            blue: 0.33,
26163                            alpha: 0.33,
26164                        },
26165                    },
26166                ])
26167            }
26168        });
26169
26170    let _handle = fake_language_server_without_capabilities
26171        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26172            panic!("Should not be called");
26173        });
26174    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26175    color_request_handle.next().await.unwrap();
26176    cx.run_until_parked();
26177    assert_eq!(
26178        1,
26179        requests_made.load(atomic::Ordering::Acquire),
26180        "Should query for colors once per editor open"
26181    );
26182    editor.update_in(cx, |editor, _, cx| {
26183        assert_eq!(
26184            vec![expected_color],
26185            extract_color_inlays(editor, cx),
26186            "Should have an initial inlay"
26187        );
26188    });
26189
26190    // opening another file in a split should not influence the LSP query counter
26191    workspace
26192        .update(cx, |workspace, window, cx| {
26193            assert_eq!(
26194                workspace.panes().len(),
26195                1,
26196                "Should have one pane with one editor"
26197            );
26198            workspace.move_item_to_pane_in_direction(
26199                &MoveItemToPaneInDirection {
26200                    direction: SplitDirection::Right,
26201                    focus: false,
26202                    clone: true,
26203                },
26204                window,
26205                cx,
26206            );
26207        })
26208        .unwrap();
26209    cx.run_until_parked();
26210    workspace
26211        .update(cx, |workspace, _, cx| {
26212            let panes = workspace.panes();
26213            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26214            for pane in panes {
26215                let editor = pane
26216                    .read(cx)
26217                    .active_item()
26218                    .and_then(|item| item.downcast::<Editor>())
26219                    .expect("Should have opened an editor in each split");
26220                let editor_file = editor
26221                    .read(cx)
26222                    .buffer()
26223                    .read(cx)
26224                    .as_singleton()
26225                    .expect("test deals with singleton buffers")
26226                    .read(cx)
26227                    .file()
26228                    .expect("test buffese should have a file")
26229                    .path();
26230                assert_eq!(
26231                    editor_file.as_ref(),
26232                    rel_path("first.rs"),
26233                    "Both editors should be opened for the same file"
26234                )
26235            }
26236        })
26237        .unwrap();
26238
26239    cx.executor().advance_clock(Duration::from_millis(500));
26240    let save = editor.update_in(cx, |editor, window, cx| {
26241        editor.move_to_end(&MoveToEnd, window, cx);
26242        editor.handle_input("dirty", window, cx);
26243        editor.save(
26244            SaveOptions {
26245                format: true,
26246                autosave: true,
26247            },
26248            project.clone(),
26249            window,
26250            cx,
26251        )
26252    });
26253    save.await.unwrap();
26254
26255    color_request_handle.next().await.unwrap();
26256    cx.run_until_parked();
26257    assert_eq!(
26258        2,
26259        requests_made.load(atomic::Ordering::Acquire),
26260        "Should query for colors once per save (deduplicated) and once per formatting after save"
26261    );
26262
26263    drop(editor);
26264    let close = workspace
26265        .update(cx, |workspace, window, cx| {
26266            workspace.active_pane().update(cx, |pane, cx| {
26267                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26268            })
26269        })
26270        .unwrap();
26271    close.await.unwrap();
26272    let close = workspace
26273        .update(cx, |workspace, window, cx| {
26274            workspace.active_pane().update(cx, |pane, cx| {
26275                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26276            })
26277        })
26278        .unwrap();
26279    close.await.unwrap();
26280    assert_eq!(
26281        2,
26282        requests_made.load(atomic::Ordering::Acquire),
26283        "After saving and closing all editors, no extra requests should be made"
26284    );
26285    workspace
26286        .update(cx, |workspace, _, cx| {
26287            assert!(
26288                workspace.active_item(cx).is_none(),
26289                "Should close all editors"
26290            )
26291        })
26292        .unwrap();
26293
26294    workspace
26295        .update(cx, |workspace, window, cx| {
26296            workspace.active_pane().update(cx, |pane, cx| {
26297                pane.navigate_backward(&workspace::GoBack, window, cx);
26298            })
26299        })
26300        .unwrap();
26301    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26302    cx.run_until_parked();
26303    let editor = workspace
26304        .update(cx, |workspace, _, cx| {
26305            workspace
26306                .active_item(cx)
26307                .expect("Should have reopened the editor again after navigating back")
26308                .downcast::<Editor>()
26309                .expect("Should be an editor")
26310        })
26311        .unwrap();
26312
26313    assert_eq!(
26314        2,
26315        requests_made.load(atomic::Ordering::Acquire),
26316        "Cache should be reused on buffer close and reopen"
26317    );
26318    editor.update(cx, |editor, cx| {
26319        assert_eq!(
26320            vec![expected_color],
26321            extract_color_inlays(editor, cx),
26322            "Should have an initial inlay"
26323        );
26324    });
26325
26326    drop(color_request_handle);
26327    let closure_requests_made = Arc::clone(&requests_made);
26328    let mut empty_color_request_handle = fake_language_server
26329        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26330            let requests_made = Arc::clone(&closure_requests_made);
26331            async move {
26332                assert_eq!(
26333                    params.text_document.uri,
26334                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26335                );
26336                requests_made.fetch_add(1, atomic::Ordering::Release);
26337                Ok(Vec::new())
26338            }
26339        });
26340    let save = editor.update_in(cx, |editor, window, cx| {
26341        editor.move_to_end(&MoveToEnd, window, cx);
26342        editor.handle_input("dirty_again", window, cx);
26343        editor.save(
26344            SaveOptions {
26345                format: false,
26346                autosave: true,
26347            },
26348            project.clone(),
26349            window,
26350            cx,
26351        )
26352    });
26353    save.await.unwrap();
26354
26355    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26356    empty_color_request_handle.next().await.unwrap();
26357    cx.run_until_parked();
26358    assert_eq!(
26359        3,
26360        requests_made.load(atomic::Ordering::Acquire),
26361        "Should query for colors once per save only, as formatting was not requested"
26362    );
26363    editor.update(cx, |editor, cx| {
26364        assert_eq!(
26365            Vec::<Rgba>::new(),
26366            extract_color_inlays(editor, cx),
26367            "Should clear all colors when the server returns an empty response"
26368        );
26369    });
26370}
26371
26372#[gpui::test]
26373async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26374    init_test(cx, |_| {});
26375    let (editor, cx) = cx.add_window_view(Editor::single_line);
26376    editor.update_in(cx, |editor, window, cx| {
26377        editor.set_text("oops\n\nwow\n", window, cx)
26378    });
26379    cx.run_until_parked();
26380    editor.update(cx, |editor, cx| {
26381        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26382    });
26383    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26384    cx.run_until_parked();
26385    editor.update(cx, |editor, cx| {
26386        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26387    });
26388}
26389
26390#[gpui::test]
26391async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26392    init_test(cx, |_| {});
26393
26394    cx.update(|cx| {
26395        register_project_item::<Editor>(cx);
26396    });
26397
26398    let fs = FakeFs::new(cx.executor());
26399    fs.insert_tree("/root1", json!({})).await;
26400    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26401        .await;
26402
26403    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26404    let (workspace, cx) =
26405        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26406
26407    let worktree_id = project.update(cx, |project, cx| {
26408        project.worktrees(cx).next().unwrap().read(cx).id()
26409    });
26410
26411    let handle = workspace
26412        .update_in(cx, |workspace, window, cx| {
26413            let project_path = (worktree_id, rel_path("one.pdf"));
26414            workspace.open_path(project_path, None, true, window, cx)
26415        })
26416        .await
26417        .unwrap();
26418
26419    assert_eq!(
26420        handle.to_any().entity_type(),
26421        TypeId::of::<InvalidItemView>()
26422    );
26423}
26424
26425#[gpui::test]
26426async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26427    init_test(cx, |_| {});
26428
26429    let language = Arc::new(Language::new(
26430        LanguageConfig::default(),
26431        Some(tree_sitter_rust::LANGUAGE.into()),
26432    ));
26433
26434    // Test hierarchical sibling navigation
26435    let text = r#"
26436        fn outer() {
26437            if condition {
26438                let a = 1;
26439            }
26440            let b = 2;
26441        }
26442
26443        fn another() {
26444            let c = 3;
26445        }
26446    "#;
26447
26448    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26449    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26450    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26451
26452    // Wait for parsing to complete
26453    editor
26454        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26455        .await;
26456
26457    editor.update_in(cx, |editor, window, cx| {
26458        // Start by selecting "let a = 1;" inside the if block
26459        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26460            s.select_display_ranges([
26461                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26462            ]);
26463        });
26464
26465        let initial_selection = editor.selections.display_ranges(cx);
26466        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26467
26468        // Test select next sibling - should move up levels to find the next sibling
26469        // Since "let a = 1;" has no siblings in the if block, it should move up
26470        // to find "let b = 2;" which is a sibling of the if block
26471        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26472        let next_selection = editor.selections.display_ranges(cx);
26473
26474        // Should have a selection and it should be different from the initial
26475        assert_eq!(
26476            next_selection.len(),
26477            1,
26478            "Should have one selection after next"
26479        );
26480        assert_ne!(
26481            next_selection[0], initial_selection[0],
26482            "Next sibling selection should be different"
26483        );
26484
26485        // Test hierarchical navigation by going to the end of the current function
26486        // and trying to navigate to the next function
26487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26488            s.select_display_ranges([
26489                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26490            ]);
26491        });
26492
26493        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26494        let function_next_selection = editor.selections.display_ranges(cx);
26495
26496        // Should move to the next function
26497        assert_eq!(
26498            function_next_selection.len(),
26499            1,
26500            "Should have one selection after function next"
26501        );
26502
26503        // Test select previous sibling navigation
26504        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26505        let prev_selection = editor.selections.display_ranges(cx);
26506
26507        // Should have a selection and it should be different
26508        assert_eq!(
26509            prev_selection.len(),
26510            1,
26511            "Should have one selection after prev"
26512        );
26513        assert_ne!(
26514            prev_selection[0], function_next_selection[0],
26515            "Previous sibling selection should be different from next"
26516        );
26517    });
26518}
26519
26520#[gpui::test]
26521async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26522    init_test(cx, |_| {});
26523
26524    let mut cx = EditorTestContext::new(cx).await;
26525    cx.set_state(
26526        "let ˇvariable = 42;
26527let another = variable + 1;
26528let result = variable * 2;",
26529    );
26530
26531    // Set up document highlights manually (simulating LSP response)
26532    cx.update_editor(|editor, _window, cx| {
26533        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26534
26535        // Create highlights for "variable" occurrences
26536        let highlight_ranges = [
26537            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26538            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26539            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26540        ];
26541
26542        let anchor_ranges: Vec<_> = highlight_ranges
26543            .iter()
26544            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26545            .collect();
26546
26547        editor.highlight_background::<DocumentHighlightRead>(
26548            &anchor_ranges,
26549            |theme| theme.colors().editor_document_highlight_read_background,
26550            cx,
26551        );
26552    });
26553
26554    // Go to next highlight - should move to second "variable"
26555    cx.update_editor(|editor, window, cx| {
26556        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26557    });
26558    cx.assert_editor_state(
26559        "let variable = 42;
26560let another = ˇvariable + 1;
26561let result = variable * 2;",
26562    );
26563
26564    // Go to next highlight - should move to third "variable"
26565    cx.update_editor(|editor, window, cx| {
26566        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26567    });
26568    cx.assert_editor_state(
26569        "let variable = 42;
26570let another = variable + 1;
26571let result = ˇvariable * 2;",
26572    );
26573
26574    // Go to next highlight - should stay at third "variable" (no wrap-around)
26575    cx.update_editor(|editor, window, cx| {
26576        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26577    });
26578    cx.assert_editor_state(
26579        "let variable = 42;
26580let another = variable + 1;
26581let result = ˇvariable * 2;",
26582    );
26583
26584    // Now test going backwards from third position
26585    cx.update_editor(|editor, window, cx| {
26586        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26587    });
26588    cx.assert_editor_state(
26589        "let variable = 42;
26590let another = ˇvariable + 1;
26591let result = variable * 2;",
26592    );
26593
26594    // Go to previous highlight - should move to first "variable"
26595    cx.update_editor(|editor, window, cx| {
26596        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26597    });
26598    cx.assert_editor_state(
26599        "let ˇvariable = 42;
26600let another = variable + 1;
26601let result = variable * 2;",
26602    );
26603
26604    // Go to previous highlight - should stay on first "variable"
26605    cx.update_editor(|editor, window, cx| {
26606        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26607    });
26608    cx.assert_editor_state(
26609        "let ˇvariable = 42;
26610let another = variable + 1;
26611let result = variable * 2;",
26612    );
26613}
26614
26615#[gpui::test]
26616async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26617    cx: &mut gpui::TestAppContext,
26618) {
26619    init_test(cx, |_| {});
26620
26621    let url = "https://zed.dev";
26622
26623    let markdown_language = Arc::new(Language::new(
26624        LanguageConfig {
26625            name: "Markdown".into(),
26626            ..LanguageConfig::default()
26627        },
26628        None,
26629    ));
26630
26631    let mut cx = EditorTestContext::new(cx).await;
26632    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26633    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26634
26635    cx.update_editor(|editor, window, cx| {
26636        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26637        editor.paste(&Paste, window, cx);
26638    });
26639
26640    cx.assert_editor_state(&format!(
26641        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26642    ));
26643}
26644
26645#[gpui::test]
26646async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26647    cx: &mut gpui::TestAppContext,
26648) {
26649    init_test(cx, |_| {});
26650
26651    let url = "https://zed.dev";
26652
26653    let markdown_language = Arc::new(Language::new(
26654        LanguageConfig {
26655            name: "Markdown".into(),
26656            ..LanguageConfig::default()
26657        },
26658        None,
26659    ));
26660
26661    let mut cx = EditorTestContext::new(cx).await;
26662    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26663    cx.set_state(&format!(
26664        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26665    ));
26666
26667    cx.update_editor(|editor, window, cx| {
26668        editor.copy(&Copy, window, cx);
26669    });
26670
26671    cx.set_state(&format!(
26672        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26673    ));
26674
26675    cx.update_editor(|editor, window, cx| {
26676        editor.paste(&Paste, window, cx);
26677    });
26678
26679    cx.assert_editor_state(&format!(
26680        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26681    ));
26682}
26683
26684#[gpui::test]
26685async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26686    cx: &mut gpui::TestAppContext,
26687) {
26688    init_test(cx, |_| {});
26689
26690    let url = "https://zed.dev";
26691
26692    let markdown_language = Arc::new(Language::new(
26693        LanguageConfig {
26694            name: "Markdown".into(),
26695            ..LanguageConfig::default()
26696        },
26697        None,
26698    ));
26699
26700    let mut cx = EditorTestContext::new(cx).await;
26701    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26702    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26703
26704    cx.update_editor(|editor, window, cx| {
26705        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26706        editor.paste(&Paste, window, cx);
26707    });
26708
26709    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26710}
26711
26712#[gpui::test]
26713async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26714    cx: &mut gpui::TestAppContext,
26715) {
26716    init_test(cx, |_| {});
26717
26718    let text = "Awesome";
26719
26720    let markdown_language = Arc::new(Language::new(
26721        LanguageConfig {
26722            name: "Markdown".into(),
26723            ..LanguageConfig::default()
26724        },
26725        None,
26726    ));
26727
26728    let mut cx = EditorTestContext::new(cx).await;
26729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26730    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26731
26732    cx.update_editor(|editor, window, cx| {
26733        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26734        editor.paste(&Paste, window, cx);
26735    });
26736
26737    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26738}
26739
26740#[gpui::test]
26741async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26742    cx: &mut gpui::TestAppContext,
26743) {
26744    init_test(cx, |_| {});
26745
26746    let url = "https://zed.dev";
26747
26748    let markdown_language = Arc::new(Language::new(
26749        LanguageConfig {
26750            name: "Rust".into(),
26751            ..LanguageConfig::default()
26752        },
26753        None,
26754    ));
26755
26756    let mut cx = EditorTestContext::new(cx).await;
26757    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26758    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26759
26760    cx.update_editor(|editor, window, cx| {
26761        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26762        editor.paste(&Paste, window, cx);
26763    });
26764
26765    cx.assert_editor_state(&format!(
26766        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26767    ));
26768}
26769
26770#[gpui::test]
26771async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26772    cx: &mut TestAppContext,
26773) {
26774    init_test(cx, |_| {});
26775
26776    let url = "https://zed.dev";
26777
26778    let markdown_language = Arc::new(Language::new(
26779        LanguageConfig {
26780            name: "Markdown".into(),
26781            ..LanguageConfig::default()
26782        },
26783        None,
26784    ));
26785
26786    let (editor, cx) = cx.add_window_view(|window, cx| {
26787        let multi_buffer = MultiBuffer::build_multi(
26788            [
26789                ("this will embed -> link", vec![Point::row_range(0..1)]),
26790                ("this will replace -> link", vec![Point::row_range(0..1)]),
26791            ],
26792            cx,
26793        );
26794        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26796            s.select_ranges(vec![
26797                Point::new(0, 19)..Point::new(0, 23),
26798                Point::new(1, 21)..Point::new(1, 25),
26799            ])
26800        });
26801        let first_buffer_id = multi_buffer
26802            .read(cx)
26803            .excerpt_buffer_ids()
26804            .into_iter()
26805            .next()
26806            .unwrap();
26807        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26808        first_buffer.update(cx, |buffer, cx| {
26809            buffer.set_language(Some(markdown_language.clone()), cx);
26810        });
26811
26812        editor
26813    });
26814    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26815
26816    cx.update_editor(|editor, window, cx| {
26817        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26818        editor.paste(&Paste, window, cx);
26819    });
26820
26821    cx.assert_editor_state(&format!(
26822        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26823    ));
26824}
26825
26826#[gpui::test]
26827async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26828    init_test(cx, |_| {});
26829
26830    let fs = FakeFs::new(cx.executor());
26831    fs.insert_tree(
26832        path!("/project"),
26833        json!({
26834            "first.rs": "# First Document\nSome content here.",
26835            "second.rs": "Plain text content for second file.",
26836        }),
26837    )
26838    .await;
26839
26840    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26841    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26842    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26843
26844    let language = rust_lang();
26845    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26846    language_registry.add(language.clone());
26847    let mut fake_servers = language_registry.register_fake_lsp(
26848        "Rust",
26849        FakeLspAdapter {
26850            ..FakeLspAdapter::default()
26851        },
26852    );
26853
26854    let buffer1 = project
26855        .update(cx, |project, cx| {
26856            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26857        })
26858        .await
26859        .unwrap();
26860    let buffer2 = project
26861        .update(cx, |project, cx| {
26862            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26863        })
26864        .await
26865        .unwrap();
26866
26867    let multi_buffer = cx.new(|cx| {
26868        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26869        multi_buffer.set_excerpts_for_path(
26870            PathKey::for_buffer(&buffer1, cx),
26871            buffer1.clone(),
26872            [Point::zero()..buffer1.read(cx).max_point()],
26873            3,
26874            cx,
26875        );
26876        multi_buffer.set_excerpts_for_path(
26877            PathKey::for_buffer(&buffer2, cx),
26878            buffer2.clone(),
26879            [Point::zero()..buffer1.read(cx).max_point()],
26880            3,
26881            cx,
26882        );
26883        multi_buffer
26884    });
26885
26886    let (editor, cx) = cx.add_window_view(|window, cx| {
26887        Editor::new(
26888            EditorMode::full(),
26889            multi_buffer,
26890            Some(project.clone()),
26891            window,
26892            cx,
26893        )
26894    });
26895
26896    let fake_language_server = fake_servers.next().await.unwrap();
26897
26898    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26899
26900    let save = editor.update_in(cx, |editor, window, cx| {
26901        assert!(editor.is_dirty(cx));
26902
26903        editor.save(
26904            SaveOptions {
26905                format: true,
26906                autosave: true,
26907            },
26908            project,
26909            window,
26910            cx,
26911        )
26912    });
26913    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26914    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26915    let mut done_edit_rx = Some(done_edit_rx);
26916    let mut start_edit_tx = Some(start_edit_tx);
26917
26918    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26919        start_edit_tx.take().unwrap().send(()).unwrap();
26920        let done_edit_rx = done_edit_rx.take().unwrap();
26921        async move {
26922            done_edit_rx.await.unwrap();
26923            Ok(None)
26924        }
26925    });
26926
26927    start_edit_rx.await.unwrap();
26928    buffer2
26929        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26930        .unwrap();
26931
26932    done_edit_tx.send(()).unwrap();
26933
26934    save.await.unwrap();
26935    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26936}
26937
26938#[track_caller]
26939fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26940    editor
26941        .all_inlays(cx)
26942        .into_iter()
26943        .filter_map(|inlay| inlay.get_color())
26944        .map(Rgba::from)
26945        .collect()
26946}
26947
26948#[gpui::test]
26949fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26950    init_test(cx, |_| {});
26951
26952    let editor = cx.add_window(|window, cx| {
26953        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26954        build_editor(buffer, window, cx)
26955    });
26956
26957    editor
26958        .update(cx, |editor, window, cx| {
26959            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26960                s.select_display_ranges([
26961                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26962                ])
26963            });
26964
26965            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26966
26967            assert_eq!(
26968                editor.display_text(cx),
26969                "line1\nline2\nline2",
26970                "Duplicating last line upward should create duplicate above, not on same line"
26971            );
26972
26973            assert_eq!(
26974                editor.selections.display_ranges(cx),
26975                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26976                "Selection should remain on the original line"
26977            );
26978        })
26979        .unwrap();
26980}
26981
26982#[gpui::test]
26983async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26984    init_test(cx, |_| {});
26985
26986    let mut cx = EditorTestContext::new(cx).await;
26987
26988    cx.set_state("line1\nline2ˇ");
26989
26990    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26991
26992    let clipboard_text = cx
26993        .read_from_clipboard()
26994        .and_then(|item| item.text().as_deref().map(str::to_string));
26995
26996    assert_eq!(
26997        clipboard_text,
26998        Some("line2\n".to_string()),
26999        "Copying a line without trailing newline should include a newline"
27000    );
27001
27002    cx.set_state("line1\nˇ");
27003
27004    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27005
27006    cx.assert_editor_state("line1\nline2\nˇ");
27007}
27008
27009#[gpui::test]
27010async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27011    init_test(cx, |_| {});
27012
27013    let mut cx = EditorTestContext::new(cx).await;
27014
27015    cx.set_state("line1\nline2ˇ");
27016    cx.update_editor(|e, window, cx| {
27017        e.set_mode(EditorMode::SingleLine);
27018        assert!(e.key_context(window, cx).contains("end_of_input"));
27019    });
27020    cx.set_state("ˇline1\nline2");
27021    cx.update_editor(|e, window, cx| {
27022        assert!(!e.key_context(window, cx).contains("end_of_input"));
27023    });
27024    cx.set_state("line1ˇ\nline2");
27025    cx.update_editor(|e, window, cx| {
27026        assert!(!e.key_context(window, cx).contains("end_of_input"));
27027    });
27028}