editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::build_editor_with_project;
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_item_view::InvalidItemView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(
  224            editor.selections.ranges(&editor.display_snapshot(cx)),
  225            vec![4..4]
  226        );
  227
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([4..5])
  231        });
  232        editor.insert("e", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![5..5]
  238        );
  239
  240        now += group_interval + Duration::from_millis(1);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([2..2])
  243        });
  244
  245        // Simulate an edit in another editor
  246        buffer.update(cx, |buffer, cx| {
  247            buffer.start_transaction_at(now, cx);
  248            buffer.edit([(0..1, "a")], None, cx);
  249            buffer.edit([(1..1, "b")], None, cx);
  250            buffer.end_transaction_at(now, cx);
  251        });
  252
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(
  255            editor.selections.ranges(&editor.display_snapshot(cx)),
  256            vec![3..3]
  257        );
  258
  259        // Last transaction happened past the group interval in a different editor.
  260        // Undo it individually and don't restore selections.
  261        editor.undo(&Undo, window, cx);
  262        assert_eq!(editor.text(cx), "12cde6");
  263        assert_eq!(
  264            editor.selections.ranges(&editor.display_snapshot(cx)),
  265            vec![2..2]
  266        );
  267
  268        // First two transactions happened within the group interval in this editor.
  269        // Undo them together and restore selections.
  270        editor.undo(&Undo, window, cx);
  271        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  272        assert_eq!(editor.text(cx), "123456");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![0..0]
  276        );
  277
  278        // Redo the first two transactions together.
  279        editor.redo(&Redo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![5..5]
  284        );
  285
  286        // Redo the last transaction on its own.
  287        editor.redo(&Redo, window, cx);
  288        assert_eq!(editor.text(cx), "ab2cde6");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![6..6]
  292        );
  293
  294        // Test empty transactions.
  295        editor.start_transaction_at(now, window, cx);
  296        editor.end_transaction_at(now, cx);
  297        editor.undo(&Undo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299    });
  300}
  301
  302#[gpui::test]
  303fn test_ime_composition(cx: &mut TestAppContext) {
  304    init_test(cx, |_| {});
  305
  306    let buffer = cx.new(|cx| {
  307        let mut buffer = language::Buffer::local("abcde", cx);
  308        // Ensure automatic grouping doesn't occur.
  309        buffer.set_group_interval(Duration::ZERO);
  310        buffer
  311    });
  312
  313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  314    cx.add_window(|window, cx| {
  315        let mut editor = build_editor(buffer.clone(), window, cx);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  320        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  321        assert_eq!(editor.text(cx), "äbcde");
  322        assert_eq!(
  323            editor.marked_text_ranges(cx),
  324            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  325        );
  326
  327        // Finalize IME composition.
  328        editor.replace_text_in_range(None, "ā", window, cx);
  329        assert_eq!(editor.text(cx), "ābcde");
  330        assert_eq!(editor.marked_text_ranges(cx), None);
  331
  332        // IME composition edits are grouped and are undone/redone at once.
  333        editor.undo(&Default::default(), window, cx);
  334        assert_eq!(editor.text(cx), "abcde");
  335        assert_eq!(editor.marked_text_ranges(cx), None);
  336        editor.redo(&Default::default(), window, cx);
  337        assert_eq!(editor.text(cx), "ābcde");
  338        assert_eq!(editor.marked_text_ranges(cx), None);
  339
  340        // Start a new IME composition.
  341        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  345        );
  346
  347        // Undoing during an IME composition cancels it.
  348        editor.undo(&Default::default(), window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  353        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  354        assert_eq!(editor.text(cx), "ābcdè");
  355        assert_eq!(
  356            editor.marked_text_ranges(cx),
  357            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  358        );
  359
  360        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  361        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  362        assert_eq!(editor.text(cx), "ābcdę");
  363        assert_eq!(editor.marked_text_ranges(cx), None);
  364
  365        // Start a new IME composition with multiple cursors.
  366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  367            s.select_ranges([
  368                OffsetUtf16(1)..OffsetUtf16(1),
  369                OffsetUtf16(3)..OffsetUtf16(3),
  370                OffsetUtf16(5)..OffsetUtf16(5),
  371            ])
  372        });
  373        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  374        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                OffsetUtf16(0)..OffsetUtf16(3),
  379                OffsetUtf16(4)..OffsetUtf16(7),
  380                OffsetUtf16(8)..OffsetUtf16(11)
  381            ])
  382        );
  383
  384        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  385        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  386        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  387        assert_eq!(
  388            editor.marked_text_ranges(cx),
  389            Some(vec![
  390                OffsetUtf16(1)..OffsetUtf16(2),
  391                OffsetUtf16(5)..OffsetUtf16(6),
  392                OffsetUtf16(9)..OffsetUtf16(10)
  393            ])
  394        );
  395
  396        // Finalize IME composition with multiple cursors.
  397        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  398        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  399        assert_eq!(editor.marked_text_ranges(cx), None);
  400
  401        editor
  402    });
  403}
  404
  405#[gpui::test]
  406fn test_selection_with_mouse(cx: &mut TestAppContext) {
  407    init_test(cx, |_| {});
  408
  409    let editor = cx.add_window(|window, cx| {
  410        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  411        build_editor(buffer, window, cx)
  412    });
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  416    });
  417    assert_eq!(
  418        editor
  419            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  420            .unwrap(),
  421        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  422    );
  423
  424    _ = editor.update(cx, |editor, window, cx| {
  425        editor.update_selection(
  426            DisplayPoint::new(DisplayRow(3), 3),
  427            0,
  428            gpui::Point::<f32>::default(),
  429            window,
  430            cx,
  431        );
  432    });
  433
  434    assert_eq!(
  435        editor
  436            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  437            .unwrap(),
  438        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  439    );
  440
  441    _ = editor.update(cx, |editor, window, cx| {
  442        editor.update_selection(
  443            DisplayPoint::new(DisplayRow(1), 1),
  444            0,
  445            gpui::Point::<f32>::default(),
  446            window,
  447            cx,
  448        );
  449    });
  450
  451    assert_eq!(
  452        editor
  453            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  454            .unwrap(),
  455        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460        editor.update_selection(
  461            DisplayPoint::new(DisplayRow(3), 3),
  462            0,
  463            gpui::Point::<f32>::default(),
  464            window,
  465            cx,
  466        );
  467    });
  468
  469    assert_eq!(
  470        editor
  471            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  472            .unwrap(),
  473        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  478        editor.update_selection(
  479            DisplayPoint::new(DisplayRow(0), 0),
  480            0,
  481            gpui::Point::<f32>::default(),
  482            window,
  483            cx,
  484        );
  485    });
  486
  487    assert_eq!(
  488        editor
  489            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  490            .unwrap(),
  491        [
  492            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  493            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  494        ]
  495    );
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.end_selection(window, cx);
  499    });
  500
  501    assert_eq!(
  502        editor
  503            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  504            .unwrap(),
  505        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  506    );
  507}
  508
  509#[gpui::test]
  510fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  511    init_test(cx, |_| {});
  512
  513    let editor = cx.add_window(|window, cx| {
  514        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  515        build_editor(buffer, window, cx)
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.end_selection(window, cx);
  532    });
  533
  534    assert_eq!(
  535        editor
  536            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  537            .unwrap(),
  538        [
  539            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  540            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  541        ]
  542    );
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    assert_eq!(
  553        editor
  554            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  555            .unwrap(),
  556        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  557    );
  558}
  559
  560#[gpui::test]
  561fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  562    init_test(cx, |_| {});
  563
  564    let editor = cx.add_window(|window, cx| {
  565        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  566        build_editor(buffer, window, cx)
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  574        );
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.update_selection(
  579            DisplayPoint::new(DisplayRow(3), 3),
  580            0,
  581            gpui::Point::<f32>::default(),
  582            window,
  583            cx,
  584        );
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  588        );
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.cancel(&Cancel, window, cx);
  593        editor.update_selection(
  594            DisplayPoint::new(DisplayRow(1), 1),
  595            0,
  596            gpui::Point::<f32>::default(),
  597            window,
  598            cx,
  599        );
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  603        );
  604    });
  605}
  606
  607#[gpui::test]
  608fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  609    init_test(cx, |_| {});
  610
  611    let editor = cx.add_window(|window, cx| {
  612        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  613        build_editor(buffer, window, cx)
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  618        assert_eq!(
  619            editor.selections.display_ranges(cx),
  620            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  621        );
  622
  623        editor.move_down(&Default::default(), window, cx);
  624        assert_eq!(
  625            editor.selections.display_ranges(cx),
  626            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  627        );
  628
  629        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  630        assert_eq!(
  631            editor.selections.display_ranges(cx),
  632            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  633        );
  634
  635        editor.move_up(&Default::default(), window, cx);
  636        assert_eq!(
  637            editor.selections.display_ranges(cx),
  638            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  639        );
  640    });
  641}
  642
  643#[gpui::test]
  644fn test_extending_selection(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    let editor = cx.add_window(|window, cx| {
  648        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  649        build_editor(buffer, window, cx)
  650    });
  651
  652    _ = editor.update(cx, |editor, window, cx| {
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  654        editor.end_selection(window, cx);
  655        assert_eq!(
  656            editor.selections.display_ranges(cx),
  657            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  658        );
  659
  660        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            editor.selections.display_ranges(cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  673        );
  674
  675        editor.update_selection(
  676            DisplayPoint::new(DisplayRow(0), 1),
  677            0,
  678            gpui::Point::<f32>::default(),
  679            window,
  680            cx,
  681        );
  682        editor.end_selection(window, cx);
  683        assert_eq!(
  684            editor.selections.display_ranges(cx),
  685            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  686        );
  687
  688        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  689        editor.end_selection(window, cx);
  690        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  691        editor.end_selection(window, cx);
  692        assert_eq!(
  693            editor.selections.display_ranges(cx),
  694            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  695        );
  696
  697        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  698        assert_eq!(
  699            editor.selections.display_ranges(cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  701        );
  702
  703        editor.update_selection(
  704            DisplayPoint::new(DisplayRow(0), 6),
  705            0,
  706            gpui::Point::<f32>::default(),
  707            window,
  708            cx,
  709        );
  710        assert_eq!(
  711            editor.selections.display_ranges(cx),
  712            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  713        );
  714
  715        editor.update_selection(
  716            DisplayPoint::new(DisplayRow(0), 1),
  717            0,
  718            gpui::Point::<f32>::default(),
  719            window,
  720            cx,
  721        );
  722        editor.end_selection(window, cx);
  723        assert_eq!(
  724            editor.selections.display_ranges(cx),
  725            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  726        );
  727    });
  728}
  729
  730#[gpui::test]
  731fn test_clone(cx: &mut TestAppContext) {
  732    init_test(cx, |_| {});
  733
  734    let (text, selection_ranges) = marked_text_ranges(
  735        indoc! {"
  736            one
  737            two
  738            threeˇ
  739            four
  740            fiveˇ
  741        "},
  742        true,
  743    );
  744
  745    let editor = cx.add_window(|window, cx| {
  746        let buffer = MultiBuffer::build_simple(&text, cx);
  747        build_editor(buffer, window, cx)
  748    });
  749
  750    _ = editor.update(cx, |editor, window, cx| {
  751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  752            s.select_ranges(selection_ranges.clone())
  753        });
  754        editor.fold_creases(
  755            vec![
  756                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  757                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  758            ],
  759            true,
  760            window,
  761            cx,
  762        );
  763    });
  764
  765    let cloned_editor = editor
  766        .update(cx, |editor, _, cx| {
  767            cx.open_window(Default::default(), |window, cx| {
  768                cx.new(|cx| editor.clone(window, cx))
  769            })
  770        })
  771        .unwrap()
  772        .unwrap();
  773
  774    let snapshot = editor
  775        .update(cx, |e, window, cx| e.snapshot(window, cx))
  776        .unwrap();
  777    let cloned_snapshot = cloned_editor
  778        .update(cx, |e, window, cx| e.snapshot(window, cx))
  779        .unwrap();
  780
  781    assert_eq!(
  782        cloned_editor
  783            .update(cx, |e, _, cx| e.display_text(cx))
  784            .unwrap(),
  785        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  786    );
  787    assert_eq!(
  788        cloned_snapshot
  789            .folds_in_range(0..text.len())
  790            .collect::<Vec<_>>(),
  791        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  792    );
  793    assert_set_eq!(
  794        cloned_editor
  795            .update(cx, |editor, _, cx| editor
  796                .selections
  797                .ranges::<Point>(&editor.display_snapshot(cx)))
  798            .unwrap(),
  799        editor
  800            .update(cx, |editor, _, cx| editor
  801                .selections
  802                .ranges(&editor.display_snapshot(cx)))
  803            .unwrap()
  804    );
  805    assert_set_eq!(
  806        cloned_editor
  807            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  808            .unwrap(),
  809        editor
  810            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  811            .unwrap()
  812    );
  813}
  814
  815#[gpui::test]
  816async fn test_navigation_history(cx: &mut TestAppContext) {
  817    init_test(cx, |_| {});
  818
  819    use workspace::item::Item;
  820
  821    let fs = FakeFs::new(cx.executor());
  822    let project = Project::test(fs, [], cx).await;
  823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  824    let pane = workspace
  825        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  826        .unwrap();
  827
  828    _ = workspace.update(cx, |_v, window, cx| {
  829        cx.new(|cx| {
  830            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  831            let mut editor = build_editor(buffer, window, cx);
  832            let handle = cx.entity();
  833            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  834
  835            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  836                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  837            }
  838
  839            // Move the cursor a small distance.
  840            // Nothing is added to the navigation history.
  841            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  842                s.select_display_ranges([
  843                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  844                ])
  845            });
  846            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  847                s.select_display_ranges([
  848                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  849                ])
  850            });
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance.
  854            // The history can jump back to the previous position.
  855            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  856                s.select_display_ranges([
  857                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  858                ])
  859            });
  860            let nav_entry = pop_history(&mut editor, cx).unwrap();
  861            editor.navigate(nav_entry.data.unwrap(), window, cx);
  862            assert_eq!(nav_entry.item.id(), cx.entity_id());
  863            assert_eq!(
  864                editor.selections.display_ranges(cx),
  865                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  866            );
  867            assert!(pop_history(&mut editor, cx).is_none());
  868
  869            // Move the cursor a small distance via the mouse.
  870            // Nothing is added to the navigation history.
  871            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  872            editor.end_selection(window, cx);
  873            assert_eq!(
  874                editor.selections.display_ranges(cx),
  875                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  876            );
  877            assert!(pop_history(&mut editor, cx).is_none());
  878
  879            // Move the cursor a large distance via the mouse.
  880            // The history can jump back to the previous position.
  881            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  882            editor.end_selection(window, cx);
  883            assert_eq!(
  884                editor.selections.display_ranges(cx),
  885                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  886            );
  887            let nav_entry = pop_history(&mut editor, cx).unwrap();
  888            editor.navigate(nav_entry.data.unwrap(), window, cx);
  889            assert_eq!(nav_entry.item.id(), cx.entity_id());
  890            assert_eq!(
  891                editor.selections.display_ranges(cx),
  892                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  893            );
  894            assert!(pop_history(&mut editor, cx).is_none());
  895
  896            // Set scroll position to check later
  897            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  898            let original_scroll_position = editor.scroll_manager.anchor();
  899
  900            // Jump to the end of the document and adjust scroll
  901            editor.move_to_end(&MoveToEnd, window, cx);
  902            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  903            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  904
  905            let nav_entry = pop_history(&mut editor, cx).unwrap();
  906            editor.navigate(nav_entry.data.unwrap(), window, cx);
  907            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  908
  909            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  910            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  911            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  912            let invalid_point = Point::new(9999, 0);
  913            editor.navigate(
  914                Box::new(NavigationData {
  915                    cursor_anchor: invalid_anchor,
  916                    cursor_position: invalid_point,
  917                    scroll_anchor: ScrollAnchor {
  918                        anchor: invalid_anchor,
  919                        offset: Default::default(),
  920                    },
  921                    scroll_top_row: invalid_point.row,
  922                }),
  923                window,
  924                cx,
  925            );
  926            assert_eq!(
  927                editor.selections.display_ranges(cx),
  928                &[editor.max_point(cx)..editor.max_point(cx)]
  929            );
  930            assert_eq!(
  931                editor.scroll_position(cx),
  932                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  933            );
  934
  935            editor
  936        })
  937    });
  938}
  939
  940#[gpui::test]
  941fn test_cancel(cx: &mut TestAppContext) {
  942    init_test(cx, |_| {});
  943
  944    let editor = cx.add_window(|window, cx| {
  945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  946        build_editor(buffer, window, cx)
  947    });
  948
  949    _ = editor.update(cx, |editor, window, cx| {
  950        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  951        editor.update_selection(
  952            DisplayPoint::new(DisplayRow(1), 1),
  953            0,
  954            gpui::Point::<f32>::default(),
  955            window,
  956            cx,
  957        );
  958        editor.end_selection(window, cx);
  959
  960        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  961        editor.update_selection(
  962            DisplayPoint::new(DisplayRow(0), 3),
  963            0,
  964            gpui::Point::<f32>::default(),
  965            window,
  966            cx,
  967        );
  968        editor.end_selection(window, cx);
  969        assert_eq!(
  970            editor.selections.display_ranges(cx),
  971            [
  972                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  973                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  974            ]
  975        );
  976    });
  977
  978    _ = editor.update(cx, |editor, window, cx| {
  979        editor.cancel(&Cancel, window, cx);
  980        assert_eq!(
  981            editor.selections.display_ranges(cx),
  982            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  983        );
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.cancel(&Cancel, window, cx);
  988        assert_eq!(
  989            editor.selections.display_ranges(cx),
  990            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_fold_action(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let editor = cx.add_window(|window, cx| {
 1000        let buffer = MultiBuffer::build_simple(
 1001            &"
 1002                impl Foo {
 1003                    // Hello!
 1004
 1005                    fn a() {
 1006                        1
 1007                    }
 1008
 1009                    fn b() {
 1010                        2
 1011                    }
 1012
 1013                    fn c() {
 1014                        3
 1015                    }
 1016                }
 1017            "
 1018            .unindent(),
 1019            cx,
 1020        );
 1021        build_editor(buffer, window, cx)
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1026            s.select_display_ranges([
 1027                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1028            ]);
 1029        });
 1030        editor.fold(&Fold, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                impl Foo {
 1035                    // Hello!
 1036
 1037                    fn a() {
 1038                        1
 1039                    }
 1040
 1041                    fn b() {⋯
 1042                    }
 1043
 1044                    fn c() {⋯
 1045                    }
 1046                }
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {⋯
 1056                }
 1057            "
 1058            .unindent(),
 1059        );
 1060
 1061        editor.unfold_lines(&UnfoldLines, window, cx);
 1062        assert_eq!(
 1063            editor.display_text(cx),
 1064            "
 1065                impl Foo {
 1066                    // Hello!
 1067
 1068                    fn a() {
 1069                        1
 1070                    }
 1071
 1072                    fn b() {⋯
 1073                    }
 1074
 1075                    fn c() {⋯
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            editor.buffer.read(cx).read(cx).text()
 1086        );
 1087    });
 1088}
 1089
 1090#[gpui::test]
 1091fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1092    init_test(cx, |_| {});
 1093
 1094    let editor = cx.add_window(|window, cx| {
 1095        let buffer = MultiBuffer::build_simple(
 1096            &"
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():
 1104                        print(2)
 1105
 1106                    def c():
 1107                        print(3)
 1108            "
 1109            .unindent(),
 1110            cx,
 1111        );
 1112        build_editor(buffer, window, cx)
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1117            s.select_display_ranges([
 1118                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1119            ]);
 1120        });
 1121        editor.fold(&Fold, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.fold(&Fold, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            "
 1142                class Foo:⋯
 1143            "
 1144            .unindent(),
 1145        );
 1146
 1147        editor.unfold_lines(&UnfoldLines, window, cx);
 1148        assert_eq!(
 1149            editor.display_text(cx),
 1150            "
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():⋯
 1158
 1159                    def c():⋯
 1160            "
 1161            .unindent(),
 1162        );
 1163
 1164        editor.unfold_lines(&UnfoldLines, window, cx);
 1165        assert_eq!(
 1166            editor.display_text(cx),
 1167            editor.buffer.read(cx).read(cx).text()
 1168        );
 1169    });
 1170}
 1171
 1172#[gpui::test]
 1173fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1174    init_test(cx, |_| {});
 1175
 1176    let editor = cx.add_window(|window, cx| {
 1177        let buffer = MultiBuffer::build_simple(
 1178            &"
 1179                class Foo:
 1180                    # Hello!
 1181
 1182                    def a():
 1183                        print(1)
 1184
 1185                    def b():
 1186                        print(2)
 1187
 1188
 1189                    def c():
 1190                        print(3)
 1191
 1192
 1193            "
 1194            .unindent(),
 1195            cx,
 1196        );
 1197        build_editor(buffer, window, cx)
 1198    });
 1199
 1200    _ = editor.update(cx, |editor, window, cx| {
 1201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1202            s.select_display_ranges([
 1203                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1204            ]);
 1205        });
 1206        editor.fold(&Fold, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218
 1219                    def c():⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:⋯
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        editor.unfold_lines(&UnfoldLines, window, cx);
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            "
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():⋯
 1248
 1249
 1250                    def c():⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            editor.buffer.read(cx).read(cx).text()
 1261        );
 1262    });
 1263}
 1264
 1265#[gpui::test]
 1266fn test_fold_at_level(cx: &mut TestAppContext) {
 1267    init_test(cx, |_| {});
 1268
 1269    let editor = cx.add_window(|window, cx| {
 1270        let buffer = MultiBuffer::build_simple(
 1271            &"
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():
 1279                        print(2)
 1280
 1281
 1282                class Bar:
 1283                    # World!
 1284
 1285                    def a():
 1286                        print(1)
 1287
 1288                    def b():
 1289                        print(2)
 1290
 1291
 1292            "
 1293            .unindent(),
 1294            cx,
 1295        );
 1296        build_editor(buffer, window, cx)
 1297    });
 1298
 1299    _ = editor.update(cx, |editor, window, cx| {
 1300        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1301        assert_eq!(
 1302            editor.display_text(cx),
 1303            "
 1304                class Foo:
 1305                    # Hello!
 1306
 1307                    def a():⋯
 1308
 1309                    def b():⋯
 1310
 1311
 1312                class Bar:
 1313                    # World!
 1314
 1315                    def a():⋯
 1316
 1317                    def b():⋯
 1318
 1319
 1320            "
 1321            .unindent(),
 1322        );
 1323
 1324        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1325        assert_eq!(
 1326            editor.display_text(cx),
 1327            "
 1328                class Foo:⋯
 1329
 1330
 1331                class Bar:⋯
 1332
 1333
 1334            "
 1335            .unindent(),
 1336        );
 1337
 1338        editor.unfold_all(&UnfoldAll, window, cx);
 1339        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1340        assert_eq!(
 1341            editor.display_text(cx),
 1342            "
 1343                class Foo:
 1344                    # Hello!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353                class Bar:
 1354                    # World!
 1355
 1356                    def a():
 1357                        print(1)
 1358
 1359                    def b():
 1360                        print(2)
 1361
 1362
 1363            "
 1364            .unindent(),
 1365        );
 1366
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            editor.buffer.read(cx).read(cx).text()
 1370        );
 1371        let (_, positions) = marked_text_ranges(
 1372            &"
 1373                       class Foo:
 1374                           # Hello!
 1375
 1376                           def a():
 1377                              print(1)
 1378
 1379                           def b():
 1380                               p«riˇ»nt(2)
 1381
 1382
 1383                       class Bar:
 1384                           # World!
 1385
 1386                           def a():
 1387                               «ˇprint(1)
 1388
 1389                           def b():
 1390                               print(2)»
 1391
 1392
 1393                   "
 1394            .unindent(),
 1395            true,
 1396        );
 1397
 1398        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1399            s.select_ranges(positions)
 1400        });
 1401
 1402        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1403        assert_eq!(
 1404            editor.display_text(cx),
 1405            "
 1406                class Foo:
 1407                    # Hello!
 1408
 1409                    def a():⋯
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428    });
 1429}
 1430
 1431#[gpui::test]
 1432fn test_move_cursor(cx: &mut TestAppContext) {
 1433    init_test(cx, |_| {});
 1434
 1435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1436    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1437
 1438    buffer.update(cx, |buffer, cx| {
 1439        buffer.edit(
 1440            vec![
 1441                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1442                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1443            ],
 1444            None,
 1445            cx,
 1446        );
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1452        );
 1453
 1454        editor.move_down(&MoveDown, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1458        );
 1459
 1460        editor.move_right(&MoveRight, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1464        );
 1465
 1466        editor.move_left(&MoveLeft, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1470        );
 1471
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1476        );
 1477
 1478        editor.move_to_end(&MoveToEnd, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1482        );
 1483
 1484        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1488        );
 1489
 1490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1491            s.select_display_ranges([
 1492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1493            ]);
 1494        });
 1495        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1499        );
 1500
 1501        editor.select_to_end(&SelectToEnd, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1505        );
 1506    });
 1507}
 1508
 1509#[gpui::test]
 1510fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1511    init_test(cx, |_| {});
 1512
 1513    let editor = cx.add_window(|window, cx| {
 1514        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1515        build_editor(buffer, window, cx)
 1516    });
 1517
 1518    assert_eq!('🟥'.len_utf8(), 4);
 1519    assert_eq!('α'.len_utf8(), 2);
 1520
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.fold_creases(
 1523            vec![
 1524                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1525                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1526                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1527            ],
 1528            true,
 1529            window,
 1530            cx,
 1531        );
 1532        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1533
 1534        editor.move_right(&MoveRight, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(0, "🟥".len())]
 1538        );
 1539        editor.move_right(&MoveRight, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(0, "🟥🟧".len())]
 1543        );
 1544        editor.move_right(&MoveRight, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[empty_range(0, "🟥🟧⋯".len())]
 1548        );
 1549
 1550        editor.move_down(&MoveDown, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(1, "ab⋯e".len())]
 1554        );
 1555        editor.move_left(&MoveLeft, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(1, "ab⋯".len())]
 1559        );
 1560        editor.move_left(&MoveLeft, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(1, "ab".len())]
 1564        );
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(1, "a".len())]
 1569        );
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "α".len())]
 1575        );
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(2, "αβ".len())]
 1580        );
 1581        editor.move_right(&MoveRight, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[empty_range(2, "αβ⋯".len())]
 1585        );
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[empty_range(2, "αβ⋯ε".len())]
 1590        );
 1591
 1592        editor.move_up(&MoveUp, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(1, "ab⋯e".len())]
 1596        );
 1597        editor.move_down(&MoveDown, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[empty_range(2, "αβ⋯ε".len())]
 1601        );
 1602        editor.move_up(&MoveUp, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[empty_range(1, "ab⋯e".len())]
 1606        );
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[empty_range(0, "🟥🟧".len())]
 1612        );
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[empty_range(0, "🟥".len())]
 1617        );
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(
 1620            editor.selections.display_ranges(cx),
 1621            &[empty_range(0, "".len())]
 1622        );
 1623    });
 1624}
 1625
 1626#[gpui::test]
 1627fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1628    init_test(cx, |_| {});
 1629
 1630    let editor = cx.add_window(|window, cx| {
 1631        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1632        build_editor(buffer, window, cx)
 1633    });
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1636            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1637        });
 1638
 1639        // moving above start of document should move selection to start of document,
 1640        // but the next move down should still be at the original goal_x
 1641        editor.move_up(&MoveUp, window, cx);
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[empty_range(0, "".len())]
 1645        );
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(1, "abcd".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(2, "αβγ".len())]
 1657        );
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(3, "abcd".len())]
 1663        );
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1669        );
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(
 1674            editor.selections.display_ranges(cx),
 1675            &[empty_range(5, "".len())]
 1676        );
 1677
 1678        editor.move_down(&MoveDown, window, cx);
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[empty_range(5, "".len())]
 1682        );
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(
 1686            editor.selections.display_ranges(cx),
 1687            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1688        );
 1689
 1690        editor.move_up(&MoveUp, window, cx);
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[empty_range(3, "abcd".len())]
 1694        );
 1695
 1696        editor.move_up(&MoveUp, window, cx);
 1697        assert_eq!(
 1698            editor.selections.display_ranges(cx),
 1699            &[empty_range(2, "αβγ".len())]
 1700        );
 1701    });
 1702}
 1703
 1704#[gpui::test]
 1705fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1706    init_test(cx, |_| {});
 1707    let move_to_beg = MoveToBeginningOfLine {
 1708        stop_at_soft_wraps: true,
 1709        stop_at_indent: true,
 1710    };
 1711
 1712    let delete_to_beg = DeleteToBeginningOfLine {
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: true,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1726            s.select_display_ranges([
 1727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1728                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1729            ]);
 1730        });
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1735        assert_eq!(
 1736            editor.selections.display_ranges(cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1740            ]
 1741        );
 1742    });
 1743
 1744    _ = editor.update(cx, |editor, window, cx| {
 1745        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1746        assert_eq!(
 1747            editor.selections.display_ranges(cx),
 1748            &[
 1749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1751            ]
 1752        );
 1753    });
 1754
 1755    _ = editor.update(cx, |editor, window, cx| {
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            editor.selections.display_ranges(cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.move_to_end_of_line(&move_to_end, window, cx);
 1768        assert_eq!(
 1769            editor.selections.display_ranges(cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1772                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1773            ]
 1774        );
 1775    });
 1776
 1777    // Moving to the end of line again is a no-op.
 1778    _ = editor.update(cx, |editor, window, cx| {
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            editor.selections.display_ranges(cx),
 1782            &[
 1783                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1784                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1785            ]
 1786        );
 1787    });
 1788
 1789    _ = editor.update(cx, |editor, window, cx| {
 1790        editor.move_left(&MoveLeft, window, cx);
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            editor.selections.display_ranges(cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            editor.selections.display_ranges(cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_beginning_of_line(
 1828            &SelectToBeginningOfLine {
 1829                stop_at_soft_wraps: true,
 1830                stop_at_indent: true,
 1831            },
 1832            window,
 1833            cx,
 1834        );
 1835        assert_eq!(
 1836            editor.selections.display_ranges(cx),
 1837            &[
 1838                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1839                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1840            ]
 1841        );
 1842    });
 1843
 1844    _ = editor.update(cx, |editor, window, cx| {
 1845        editor.select_to_end_of_line(
 1846            &SelectToEndOfLine {
 1847                stop_at_soft_wraps: true,
 1848            },
 1849            window,
 1850            cx,
 1851        );
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1856                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1857            ]
 1858        );
 1859    });
 1860
 1861    _ = editor.update(cx, |editor, window, cx| {
 1862        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1863        assert_eq!(editor.display_text(cx), "ab\n  de");
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1869            ]
 1870        );
 1871    });
 1872
 1873    _ = editor.update(cx, |editor, window, cx| {
 1874        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1875        assert_eq!(editor.display_text(cx), "\n");
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889    let move_to_beg = MoveToBeginningOfLine {
 1890        stop_at_soft_wraps: false,
 1891        stop_at_indent: false,
 1892    };
 1893
 1894    let move_to_end = MoveToEndOfLine {
 1895        stop_at_soft_wraps: false,
 1896    };
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.set_wrap_width(Some(140.0.into()), cx);
 1905
 1906        // We expect the following lines after wrapping
 1907        // ```
 1908        // thequickbrownfox
 1909        // jumpedoverthelazydo
 1910        // gs
 1911        // ```
 1912        // The final `gs` was soft-wrapped onto a new line.
 1913        assert_eq!(
 1914            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1915            editor.display_text(cx),
 1916        );
 1917
 1918        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1919        // Start the cursor at the `k` on the first line
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1923            ]);
 1924        });
 1925
 1926        // Moving to the beginning of the line should put us at the beginning of the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1930            editor.selections.display_ranges(cx)
 1931        );
 1932
 1933        // Moving to the end of the line should put us at the end of the line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1937            editor.selections.display_ranges(cx)
 1938        );
 1939
 1940        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1941        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1943            s.select_display_ranges([
 1944                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1945            ]);
 1946        });
 1947
 1948        // Moving to the beginning of the line should put us at the start of the second line of
 1949        // display text, i.e., the `j`.
 1950        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1951        assert_eq!(
 1952            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1953            editor.selections.display_ranges(cx)
 1954        );
 1955
 1956        // Moving to the beginning of the line again should be a no-op.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1960            editor.selections.display_ranges(cx)
 1961        );
 1962
 1963        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1964        // next display line.
 1965        editor.move_to_end_of_line(&move_to_end, window, cx);
 1966        assert_eq!(
 1967            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1968            editor.selections.display_ranges(cx)
 1969        );
 1970
 1971        // Moving to the end of the line again should be a no-op.
 1972        editor.move_to_end_of_line(&move_to_end, window, cx);
 1973        assert_eq!(
 1974            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1975            editor.selections.display_ranges(cx)
 1976        );
 1977    });
 1978}
 1979
 1980#[gpui::test]
 1981fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1982    init_test(cx, |_| {});
 1983
 1984    let move_to_beg = MoveToBeginningOfLine {
 1985        stop_at_soft_wraps: true,
 1986        stop_at_indent: true,
 1987    };
 1988
 1989    let select_to_beg = SelectToBeginningOfLine {
 1990        stop_at_soft_wraps: true,
 1991        stop_at_indent: true,
 1992    };
 1993
 1994    let delete_to_beg = DeleteToBeginningOfLine {
 1995        stop_at_indent: true,
 1996    };
 1997
 1998    let move_to_end = MoveToEndOfLine {
 1999        stop_at_soft_wraps: false,
 2000    };
 2001
 2002    let editor = cx.add_window(|window, cx| {
 2003        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2004        build_editor(buffer, window, cx)
 2005    });
 2006
 2007    _ = editor.update(cx, |editor, window, cx| {
 2008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2009            s.select_display_ranges([
 2010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2011                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2012            ]);
 2013        });
 2014
 2015        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2016        // and the second cursor at the first non-whitespace character in the line.
 2017        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2018        assert_eq!(
 2019            editor.selections.display_ranges(cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should move the second cursor to the beginning of the line.
 2028        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2029        assert_eq!(
 2030            editor.selections.display_ranges(cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2038        // and should move the second cursor back to the first non-whitespace character in the line.
 2039        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2040        assert_eq!(
 2041            editor.selections.display_ranges(cx),
 2042            &[
 2043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2044                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2045            ]
 2046        );
 2047
 2048        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2049        // and to the first non-whitespace character in the line for the second cursor.
 2050        editor.move_to_end_of_line(&move_to_end, window, cx);
 2051        editor.move_left(&MoveLeft, window, cx);
 2052        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should select to the beginning of the line for the second cursor.
 2063        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2064        assert_eq!(
 2065            editor.selections.display_ranges(cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2073        // and should delete to the first non-whitespace character in the line for the second cursor.
 2074        editor.move_to_end_of_line(&move_to_end, window, cx);
 2075        editor.move_left(&MoveLeft, window, cx);
 2076        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2077        assert_eq!(editor.text(cx), "c\n  f");
 2078    });
 2079}
 2080
 2081#[gpui::test]
 2082fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2083    init_test(cx, |_| {});
 2084
 2085    let move_to_beg = MoveToBeginningOfLine {
 2086        stop_at_soft_wraps: true,
 2087        stop_at_indent: true,
 2088    };
 2089
 2090    let editor = cx.add_window(|window, cx| {
 2091        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2092        build_editor(buffer, window, cx)
 2093    });
 2094
 2095    _ = editor.update(cx, |editor, window, cx| {
 2096        // test cursor between line_start and indent_start
 2097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2098            s.select_display_ranges([
 2099                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2100            ]);
 2101        });
 2102
 2103        // cursor should move to line_start
 2104        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2108        );
 2109
 2110        // cursor should move to indent_start
 2111        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2115        );
 2116
 2117        // cursor should move to back to line_start
 2118        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2119        assert_eq!(
 2120            editor.selections.display_ranges(cx),
 2121            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2122        );
 2123    });
 2124}
 2125
 2126#[gpui::test]
 2127fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2128    init_test(cx, |_| {});
 2129
 2130    let editor = cx.add_window(|window, cx| {
 2131        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2132        build_editor(buffer, window, cx)
 2133    });
 2134    _ = editor.update(cx, |editor, window, cx| {
 2135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2136            s.select_display_ranges([
 2137                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2138                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2139            ])
 2140        });
 2141        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2142        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2143
 2144        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2145        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2148        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2149
 2150        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2151        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2152
 2153        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2154        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2158
 2159        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2160        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2164
 2165        editor.move_right(&MoveRight, window, cx);
 2166        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2167        assert_selection_ranges(
 2168            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2169            editor,
 2170            cx,
 2171        );
 2172
 2173        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2174        assert_selection_ranges(
 2175            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2176            editor,
 2177            cx,
 2178        );
 2179
 2180        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2181        assert_selection_ranges(
 2182            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2183            editor,
 2184            cx,
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192
 2193    let editor = cx.add_window(|window, cx| {
 2194        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2195        build_editor(buffer, window, cx)
 2196    });
 2197
 2198    _ = editor.update(cx, |editor, window, cx| {
 2199        editor.set_wrap_width(Some(140.0.into()), cx);
 2200        assert_eq!(
 2201            editor.display_text(cx),
 2202            "use one::{\n    two::three::\n    four::five\n};"
 2203        );
 2204
 2205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2206            s.select_display_ranges([
 2207                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2208            ]);
 2209        });
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            editor.selections.display_ranges(cx),
 2214            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2215        );
 2216
 2217        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2218        assert_eq!(
 2219            editor.selections.display_ranges(cx),
 2220            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2221        );
 2222
 2223        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2224        assert_eq!(
 2225            editor.selections.display_ranges(cx),
 2226            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2227        );
 2228
 2229        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2230        assert_eq!(
 2231            editor.selections.display_ranges(cx),
 2232            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2233        );
 2234
 2235        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2236        assert_eq!(
 2237            editor.selections.display_ranges(cx),
 2238            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2239        );
 2240
 2241        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2242        assert_eq!(
 2243            editor.selections.display_ranges(cx),
 2244            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2245        );
 2246    });
 2247}
 2248
 2249#[gpui::test]
 2250async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2251    init_test(cx, |_| {});
 2252    let mut cx = EditorTestContext::new(cx).await;
 2253
 2254    let line_height = cx.editor(|editor, window, _| {
 2255        editor
 2256            .style()
 2257            .unwrap()
 2258            .text
 2259            .line_height_in_pixels(window.rem_size())
 2260    });
 2261    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2262
 2263    cx.set_state(
 2264        &r#"ˇone
 2265        two
 2266
 2267        three
 2268        fourˇ
 2269        five
 2270
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285        ˇ
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"one
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300        ˇ
 2301        sixˇ"#
 2302            .unindent(),
 2303    );
 2304
 2305    cx.update_editor(|editor, window, cx| {
 2306        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2307    });
 2308    cx.assert_editor_state(
 2309        &r#"one
 2310        two
 2311
 2312        three
 2313        four
 2314        five
 2315
 2316        sixˇ"#
 2317            .unindent(),
 2318    );
 2319
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2322    });
 2323    cx.assert_editor_state(
 2324        &r#"one
 2325        two
 2326
 2327        three
 2328        four
 2329        five
 2330        ˇ
 2331        six"#
 2332            .unindent(),
 2333    );
 2334
 2335    cx.update_editor(|editor, window, cx| {
 2336        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2337    });
 2338    cx.assert_editor_state(
 2339        &r#"one
 2340        two
 2341        ˇ
 2342        three
 2343        four
 2344        five
 2345
 2346        six"#
 2347            .unindent(),
 2348    );
 2349
 2350    cx.update_editor(|editor, window, cx| {
 2351        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2352    });
 2353    cx.assert_editor_state(
 2354        &r#"ˇone
 2355        two
 2356
 2357        three
 2358        four
 2359        five
 2360
 2361        six"#
 2362            .unindent(),
 2363    );
 2364}
 2365
 2366#[gpui::test]
 2367async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2368    init_test(cx, |_| {});
 2369    let mut cx = EditorTestContext::new(cx).await;
 2370    let line_height = cx.editor(|editor, window, _| {
 2371        editor
 2372            .style()
 2373            .unwrap()
 2374            .text
 2375            .line_height_in_pixels(window.rem_size())
 2376    });
 2377    let window = cx.window;
 2378    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2379
 2380    cx.set_state(
 2381        r#"ˇone
 2382        two
 2383        three
 2384        four
 2385        five
 2386        six
 2387        seven
 2388        eight
 2389        nine
 2390        ten
 2391        "#,
 2392    );
 2393
 2394    cx.update_editor(|editor, window, cx| {
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 0.)
 2398        );
 2399        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2400        assert_eq!(
 2401            editor.snapshot(window, cx).scroll_position(),
 2402            gpui::Point::new(0., 3.)
 2403        );
 2404        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2405        assert_eq!(
 2406            editor.snapshot(window, cx).scroll_position(),
 2407            gpui::Point::new(0., 6.)
 2408        );
 2409        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2410        assert_eq!(
 2411            editor.snapshot(window, cx).scroll_position(),
 2412            gpui::Point::new(0., 3.)
 2413        );
 2414
 2415        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 1.)
 2419        );
 2420        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2421        assert_eq!(
 2422            editor.snapshot(window, cx).scroll_position(),
 2423            gpui::Point::new(0., 3.)
 2424        );
 2425    });
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_autoscroll(cx: &mut TestAppContext) {
 2430    init_test(cx, |_| {});
 2431    let mut cx = EditorTestContext::new(cx).await;
 2432
 2433    let line_height = cx.update_editor(|editor, window, cx| {
 2434        editor.set_vertical_scroll_margin(2, cx);
 2435        editor
 2436            .style()
 2437            .unwrap()
 2438            .text
 2439            .line_height_in_pixels(window.rem_size())
 2440    });
 2441    let window = cx.window;
 2442    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2443
 2444    cx.set_state(
 2445        r#"ˇone
 2446            two
 2447            three
 2448            four
 2449            five
 2450            six
 2451            seven
 2452            eight
 2453            nine
 2454            ten
 2455        "#,
 2456    );
 2457    cx.update_editor(|editor, window, cx| {
 2458        assert_eq!(
 2459            editor.snapshot(window, cx).scroll_position(),
 2460            gpui::Point::new(0., 0.0)
 2461        );
 2462    });
 2463
 2464    // Add a cursor below the visible area. Since both cursors cannot fit
 2465    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2466    // allows the vertical scroll margin below that cursor.
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.change_selections(Default::default(), window, cx, |selections| {
 2469            selections.select_ranges([
 2470                Point::new(0, 0)..Point::new(0, 0),
 2471                Point::new(6, 0)..Point::new(6, 0),
 2472            ]);
 2473        })
 2474    });
 2475    cx.update_editor(|editor, window, cx| {
 2476        assert_eq!(
 2477            editor.snapshot(window, cx).scroll_position(),
 2478            gpui::Point::new(0., 3.0)
 2479        );
 2480    });
 2481
 2482    // Move down. The editor cursor scrolls down to track the newest cursor.
 2483    cx.update_editor(|editor, window, cx| {
 2484        editor.move_down(&Default::default(), window, cx);
 2485    });
 2486    cx.update_editor(|editor, window, cx| {
 2487        assert_eq!(
 2488            editor.snapshot(window, cx).scroll_position(),
 2489            gpui::Point::new(0., 4.0)
 2490        );
 2491    });
 2492
 2493    // Add a cursor above the visible area. Since both cursors fit on screen,
 2494    // the editor scrolls to show both.
 2495    cx.update_editor(|editor, window, cx| {
 2496        editor.change_selections(Default::default(), window, cx, |selections| {
 2497            selections.select_ranges([
 2498                Point::new(1, 0)..Point::new(1, 0),
 2499                Point::new(6, 0)..Point::new(6, 0),
 2500            ]);
 2501        })
 2502    });
 2503    cx.update_editor(|editor, window, cx| {
 2504        assert_eq!(
 2505            editor.snapshot(window, cx).scroll_position(),
 2506            gpui::Point::new(0., 1.0)
 2507        );
 2508    });
 2509}
 2510
 2511#[gpui::test]
 2512async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2513    init_test(cx, |_| {});
 2514    let mut cx = EditorTestContext::new(cx).await;
 2515
 2516    let line_height = cx.editor(|editor, window, _cx| {
 2517        editor
 2518            .style()
 2519            .unwrap()
 2520            .text
 2521            .line_height_in_pixels(window.rem_size())
 2522    });
 2523    let window = cx.window;
 2524    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2525    cx.set_state(
 2526        &r#"
 2527        ˇone
 2528        two
 2529        threeˇ
 2530        four
 2531        five
 2532        six
 2533        seven
 2534        eight
 2535        nine
 2536        ten
 2537        "#
 2538        .unindent(),
 2539    );
 2540
 2541    cx.update_editor(|editor, window, cx| {
 2542        editor.move_page_down(&MovePageDown::default(), window, cx)
 2543    });
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| {
 2561        editor.move_page_down(&MovePageDown::default(), window, cx)
 2562    });
 2563    cx.assert_editor_state(
 2564        &r#"
 2565        one
 2566        two
 2567        three
 2568        four
 2569        five
 2570        six
 2571        ˇseven
 2572        eight
 2573        nineˇ
 2574        ten
 2575        "#
 2576        .unindent(),
 2577    );
 2578
 2579    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2580    cx.assert_editor_state(
 2581        &r#"
 2582        one
 2583        two
 2584        three
 2585        ˇfour
 2586        five
 2587        sixˇ
 2588        seven
 2589        eight
 2590        nine
 2591        ten
 2592        "#
 2593        .unindent(),
 2594    );
 2595
 2596    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        ˇone
 2600        two
 2601        threeˇ
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ten
 2609        "#
 2610        .unindent(),
 2611    );
 2612
 2613    // Test select collapsing
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.move_page_down(&MovePageDown::default(), window, cx);
 2616        editor.move_page_down(&MovePageDown::default(), window, cx);
 2617        editor.move_page_down(&MovePageDown::default(), window, cx);
 2618    });
 2619    cx.assert_editor_state(
 2620        &r#"
 2621        one
 2622        two
 2623        three
 2624        four
 2625        five
 2626        six
 2627        seven
 2628        eight
 2629        nine
 2630        ˇten
 2631        ˇ"#
 2632        .unindent(),
 2633    );
 2634}
 2635
 2636#[gpui::test]
 2637async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2638    init_test(cx, |_| {});
 2639    let mut cx = EditorTestContext::new(cx).await;
 2640    cx.set_state("one «two threeˇ» four");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_beginning_of_line(
 2643            &DeleteToBeginningOfLine {
 2644                stop_at_indent: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649        assert_eq!(editor.text(cx), " four");
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    // For an empty selection, the preceding word fragment is deleted.
 2660    // For non-empty selections, only selected characters are deleted.
 2661    cx.set_state("onˇe two t«hreˇ»e four");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: false,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    cx.assert_editor_state("ˇe two tˇe four");
 2673
 2674    cx.set_state("e tˇwo te «fˇ»our");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("e tˇ te ˇour");
 2686}
 2687
 2688#[gpui::test]
 2689async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2690    init_test(cx, |_| {});
 2691
 2692    let mut cx = EditorTestContext::new(cx).await;
 2693
 2694    cx.set_state("here is some text    ˇwith a space");
 2695    cx.update_editor(|editor, window, cx| {
 2696        editor.delete_to_previous_word_start(
 2697            &DeleteToPreviousWordStart {
 2698                ignore_newlines: false,
 2699                ignore_brackets: true,
 2700            },
 2701            window,
 2702            cx,
 2703        );
 2704    });
 2705    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2706    cx.assert_editor_state("here is some textˇwith a space");
 2707
 2708    cx.set_state("here is some text    ˇwith a space");
 2709    cx.update_editor(|editor, window, cx| {
 2710        editor.delete_to_previous_word_start(
 2711            &DeleteToPreviousWordStart {
 2712                ignore_newlines: false,
 2713                ignore_brackets: false,
 2714            },
 2715            window,
 2716            cx,
 2717        );
 2718    });
 2719    cx.assert_editor_state("here is some textˇwith a space");
 2720
 2721    cx.set_state("here is some textˇ    with a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_next_word_end(
 2724            &DeleteToNextWordEnd {
 2725                ignore_newlines: false,
 2726                ignore_brackets: true,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    // Same happens in the other direction.
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734
 2735    cx.set_state("here is some textˇ    with a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_next_word_end(
 2738            &DeleteToNextWordEnd {
 2739                ignore_newlines: false,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    cx.assert_editor_state("here is some textˇwith a space");
 2747
 2748    cx.set_state("here is some textˇ    with a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_next_word_end(
 2751            &DeleteToNextWordEnd {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("here is some textˇwith a space");
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    cx.assert_editor_state("here is some ˇwith a space");
 2771    cx.update_editor(|editor, window, cx| {
 2772        editor.delete_to_previous_word_start(
 2773            &DeleteToPreviousWordStart {
 2774                ignore_newlines: true,
 2775                ignore_brackets: false,
 2776            },
 2777            window,
 2778            cx,
 2779        );
 2780    });
 2781    // Single whitespaces are removed with the word behind them.
 2782    cx.assert_editor_state("here is ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("here ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_previous_word_start(
 2796            &DeleteToPreviousWordStart {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    cx.assert_editor_state("ˇwith a space");
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    cx.assert_editor_state("ˇwith a space");
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Same happens in the other direction.
 2827    cx.assert_editor_state("ˇ a space");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ space");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_next_word_end(
 2841            &DeleteToNextWordEnd {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850    cx.update_editor(|editor, window, cx| {
 2851        editor.delete_to_next_word_end(
 2852            &DeleteToNextWordEnd {
 2853                ignore_newlines: true,
 2854                ignore_brackets: false,
 2855            },
 2856            window,
 2857            cx,
 2858        );
 2859    });
 2860    cx.assert_editor_state("ˇ");
 2861    cx.update_editor(|editor, window, cx| {
 2862        editor.delete_to_previous_word_start(
 2863            &DeleteToPreviousWordStart {
 2864                ignore_newlines: true,
 2865                ignore_brackets: false,
 2866            },
 2867            window,
 2868            cx,
 2869        );
 2870    });
 2871    cx.assert_editor_state("ˇ");
 2872}
 2873
 2874#[gpui::test]
 2875async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2876    init_test(cx, |_| {});
 2877
 2878    let language = Arc::new(
 2879        Language::new(
 2880            LanguageConfig {
 2881                brackets: BracketPairConfig {
 2882                    pairs: vec![
 2883                        BracketPair {
 2884                            start: "\"".to_string(),
 2885                            end: "\"".to_string(),
 2886                            close: true,
 2887                            surround: true,
 2888                            newline: false,
 2889                        },
 2890                        BracketPair {
 2891                            start: "(".to_string(),
 2892                            end: ")".to_string(),
 2893                            close: true,
 2894                            surround: true,
 2895                            newline: true,
 2896                        },
 2897                    ],
 2898                    ..BracketPairConfig::default()
 2899                },
 2900                ..LanguageConfig::default()
 2901            },
 2902            Some(tree_sitter_rust::LANGUAGE.into()),
 2903        )
 2904        .with_brackets_query(
 2905            r#"
 2906                ("(" @open ")" @close)
 2907                ("\"" @open "\"" @close)
 2908            "#,
 2909        )
 2910        .unwrap(),
 2911    );
 2912
 2913    let mut cx = EditorTestContext::new(cx).await;
 2914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2915
 2916    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Deletion stops before brackets if asked to not ignore them.
 2928    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    // Deletion has to remove a single bracket and then stop again.
 2940    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_previous_word_start(
 2944            &DeleteToPreviousWordStart {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_previous_word_start(
 2956            &DeleteToPreviousWordStart {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_previous_word_start(
 2968            &DeleteToPreviousWordStart {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2977
 2978    cx.update_editor(|editor, window, cx| {
 2979        editor.delete_to_next_word_end(
 2980            &DeleteToNextWordEnd {
 2981                ignore_newlines: true,
 2982                ignore_brackets: false,
 2983            },
 2984            window,
 2985            cx,
 2986        );
 2987    });
 2988    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2989    cx.assert_editor_state(r#"ˇ");"#);
 2990
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_next_word_end(
 2993            &DeleteToNextWordEnd {
 2994                ignore_newlines: true,
 2995                ignore_brackets: false,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"ˇ"#);
 3002
 3003    cx.update_editor(|editor, window, cx| {
 3004        editor.delete_to_next_word_end(
 3005            &DeleteToNextWordEnd {
 3006                ignore_newlines: true,
 3007                ignore_brackets: false,
 3008            },
 3009            window,
 3010            cx,
 3011        );
 3012    });
 3013    cx.assert_editor_state(r#"ˇ"#);
 3014
 3015    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3016    cx.update_editor(|editor, window, cx| {
 3017        editor.delete_to_previous_word_start(
 3018            &DeleteToPreviousWordStart {
 3019                ignore_newlines: true,
 3020                ignore_brackets: true,
 3021            },
 3022            window,
 3023            cx,
 3024        );
 3025    });
 3026    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3027}
 3028
 3029#[gpui::test]
 3030fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    let editor = cx.add_window(|window, cx| {
 3034        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3035        build_editor(buffer, window, cx)
 3036    });
 3037    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3038        ignore_newlines: false,
 3039        ignore_brackets: false,
 3040    };
 3041    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3042        ignore_newlines: true,
 3043        ignore_brackets: false,
 3044    };
 3045
 3046    _ = editor.update(cx, |editor, window, cx| {
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_display_ranges([
 3049                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3050            ])
 3051        });
 3052        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3053        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3054        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3056        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3058        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3060        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3062        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3064    });
 3065}
 3066
 3067#[gpui::test]
 3068fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3069    init_test(cx, |_| {});
 3070
 3071    let editor = cx.add_window(|window, cx| {
 3072        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3073        build_editor(buffer, window, cx)
 3074    });
 3075    let del_to_next_word_end = DeleteToNextWordEnd {
 3076        ignore_newlines: false,
 3077        ignore_brackets: false,
 3078    };
 3079    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3080        ignore_newlines: true,
 3081        ignore_brackets: false,
 3082    };
 3083
 3084    _ = editor.update(cx, |editor, window, cx| {
 3085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3086            s.select_display_ranges([
 3087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3088            ])
 3089        });
 3090        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3091        assert_eq!(
 3092            editor.buffer.read(cx).read(cx).text(),
 3093            "one\n   two\nthree\n   four"
 3094        );
 3095        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3096        assert_eq!(
 3097            editor.buffer.read(cx).read(cx).text(),
 3098            "\n   two\nthree\n   four"
 3099        );
 3100        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3101        assert_eq!(
 3102            editor.buffer.read(cx).read(cx).text(),
 3103            "two\nthree\n   four"
 3104        );
 3105        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3107        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3108        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3109        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3110        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3111        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140async fn test_newline_yaml(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let mut cx = EditorTestContext::new(cx).await;
 3144    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3145    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3146
 3147    // Object (between 2 fields)
 3148    cx.set_state(indoc! {"
 3149    test:ˇ
 3150    hello: bye"});
 3151    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3152    cx.assert_editor_state(indoc! {"
 3153    test:
 3154        ˇ
 3155    hello: bye"});
 3156
 3157    // Object (first and single line)
 3158    cx.set_state(indoc! {"
 3159    test:ˇ"});
 3160    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3161    cx.assert_editor_state(indoc! {"
 3162    test:
 3163        ˇ"});
 3164
 3165    // Array with objects (after first element)
 3166    cx.set_state(indoc! {"
 3167    test:
 3168        - foo: barˇ"});
 3169    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3170    cx.assert_editor_state(indoc! {"
 3171    test:
 3172        - foo: bar
 3173        ˇ"});
 3174
 3175    // Array with objects and comment
 3176    cx.set_state(indoc! {"
 3177    test:
 3178        - foo: bar
 3179        - bar: # testˇ"});
 3180    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182    test:
 3183        - foo: bar
 3184        - bar: # test
 3185            ˇ"});
 3186
 3187    // Array with objects (after second element)
 3188    cx.set_state(indoc! {"
 3189    test:
 3190        - foo: bar
 3191        - bar: fooˇ"});
 3192    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194    test:
 3195        - foo: bar
 3196        - bar: foo
 3197        ˇ"});
 3198
 3199    // Array with strings (after first element)
 3200    cx.set_state(indoc! {"
 3201    test:
 3202        - fooˇ"});
 3203    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3204    cx.assert_editor_state(indoc! {"
 3205    test:
 3206        - foo
 3207        ˇ"});
 3208}
 3209
 3210#[gpui::test]
 3211fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3212    init_test(cx, |_| {});
 3213
 3214    let editor = cx.add_window(|window, cx| {
 3215        let buffer = MultiBuffer::build_simple(
 3216            "
 3217                a
 3218                b(
 3219                    X
 3220                )
 3221                c(
 3222                    X
 3223                )
 3224            "
 3225            .unindent()
 3226            .as_str(),
 3227            cx,
 3228        );
 3229        let mut editor = build_editor(buffer, window, cx);
 3230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3231            s.select_ranges([
 3232                Point::new(2, 4)..Point::new(2, 5),
 3233                Point::new(5, 4)..Point::new(5, 5),
 3234            ])
 3235        });
 3236        editor
 3237    });
 3238
 3239    _ = editor.update(cx, |editor, window, cx| {
 3240        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3241        editor.buffer.update(cx, |buffer, cx| {
 3242            buffer.edit(
 3243                [
 3244                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3245                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3246                ],
 3247                None,
 3248                cx,
 3249            );
 3250            assert_eq!(
 3251                buffer.read(cx).text(),
 3252                "
 3253                    a
 3254                    b()
 3255                    c()
 3256                "
 3257                .unindent()
 3258            );
 3259        });
 3260        assert_eq!(
 3261            editor.selections.ranges(&editor.display_snapshot(cx)),
 3262            &[
 3263                Point::new(1, 2)..Point::new(1, 2),
 3264                Point::new(2, 2)..Point::new(2, 2),
 3265            ],
 3266        );
 3267
 3268        editor.newline(&Newline, window, cx);
 3269        assert_eq!(
 3270            editor.text(cx),
 3271            "
 3272                a
 3273                b(
 3274                )
 3275                c(
 3276                )
 3277            "
 3278            .unindent()
 3279        );
 3280
 3281        // The selections are moved after the inserted newlines
 3282        assert_eq!(
 3283            editor.selections.ranges(&editor.display_snapshot(cx)),
 3284            &[
 3285                Point::new(2, 0)..Point::new(2, 0),
 3286                Point::new(4, 0)..Point::new(4, 0),
 3287            ],
 3288        );
 3289    });
 3290}
 3291
 3292#[gpui::test]
 3293async fn test_newline_above(cx: &mut TestAppContext) {
 3294    init_test(cx, |settings| {
 3295        settings.defaults.tab_size = NonZeroU32::new(4)
 3296    });
 3297
 3298    let language = Arc::new(
 3299        Language::new(
 3300            LanguageConfig::default(),
 3301            Some(tree_sitter_rust::LANGUAGE.into()),
 3302        )
 3303        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3304        .unwrap(),
 3305    );
 3306
 3307    let mut cx = EditorTestContext::new(cx).await;
 3308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3309    cx.set_state(indoc! {"
 3310        const a: ˇA = (
 3311 3312                «const_functionˇ»(ˇ),
 3313                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3314 3315        ˇ);ˇ
 3316    "});
 3317
 3318    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        ˇ
 3321        const a: A = (
 3322            ˇ
 3323            (
 3324                ˇ
 3325                ˇ
 3326                const_function(),
 3327                ˇ
 3328                ˇ
 3329                ˇ
 3330                ˇ
 3331                something_else,
 3332                ˇ
 3333            )
 3334            ˇ
 3335            ˇ
 3336        );
 3337    "});
 3338}
 3339
 3340#[gpui::test]
 3341async fn test_newline_below(cx: &mut TestAppContext) {
 3342    init_test(cx, |settings| {
 3343        settings.defaults.tab_size = NonZeroU32::new(4)
 3344    });
 3345
 3346    let language = Arc::new(
 3347        Language::new(
 3348            LanguageConfig::default(),
 3349            Some(tree_sitter_rust::LANGUAGE.into()),
 3350        )
 3351        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3352        .unwrap(),
 3353    );
 3354
 3355    let mut cx = EditorTestContext::new(cx).await;
 3356    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3357    cx.set_state(indoc! {"
 3358        const a: ˇA = (
 3359 3360                «const_functionˇ»(ˇ),
 3361                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3362 3363        ˇ);ˇ
 3364    "});
 3365
 3366    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: A = (
 3369            ˇ
 3370            (
 3371                ˇ
 3372                const_function(),
 3373                ˇ
 3374                ˇ
 3375                something_else,
 3376                ˇ
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380            )
 3381            ˇ
 3382        );
 3383        ˇ
 3384        ˇ
 3385    "});
 3386}
 3387
 3388#[gpui::test]
 3389async fn test_newline_comments(cx: &mut TestAppContext) {
 3390    init_test(cx, |settings| {
 3391        settings.defaults.tab_size = NonZeroU32::new(4)
 3392    });
 3393
 3394    let language = Arc::new(Language::new(
 3395        LanguageConfig {
 3396            line_comments: vec!["// ".into()],
 3397            ..LanguageConfig::default()
 3398        },
 3399        None,
 3400    ));
 3401    {
 3402        let mut cx = EditorTestContext::new(cx).await;
 3403        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3404        cx.set_state(indoc! {"
 3405        // Fooˇ
 3406    "});
 3407
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        // Foo
 3411        // ˇ
 3412    "});
 3413        // Ensure that we add comment prefix when existing line contains space
 3414        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3415        cx.assert_editor_state(
 3416            indoc! {"
 3417        // Foo
 3418        //s
 3419        // ˇ
 3420    "}
 3421            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3422            .as_str(),
 3423        );
 3424        // Ensure that we add comment prefix when existing line does not contain space
 3425        cx.set_state(indoc! {"
 3426        // Foo
 3427        //ˇ
 3428    "});
 3429        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3430        cx.assert_editor_state(indoc! {"
 3431        // Foo
 3432        //
 3433        // ˇ
 3434    "});
 3435        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3436        cx.set_state(indoc! {"
 3437        ˇ// Foo
 3438    "});
 3439        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3440        cx.assert_editor_state(indoc! {"
 3441
 3442        ˇ// Foo
 3443    "});
 3444    }
 3445    // Ensure that comment continuations can be disabled.
 3446    update_test_language_settings(cx, |settings| {
 3447        settings.defaults.extend_comment_on_newline = Some(false);
 3448    });
 3449    let mut cx = EditorTestContext::new(cx).await;
 3450    cx.set_state(indoc! {"
 3451        // Fooˇ
 3452    "});
 3453    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3454    cx.assert_editor_state(indoc! {"
 3455        // Foo
 3456        ˇ
 3457    "});
 3458}
 3459
 3460#[gpui::test]
 3461async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3462    init_test(cx, |settings| {
 3463        settings.defaults.tab_size = NonZeroU32::new(4)
 3464    });
 3465
 3466    let language = Arc::new(Language::new(
 3467        LanguageConfig {
 3468            line_comments: vec!["// ".into(), "/// ".into()],
 3469            ..LanguageConfig::default()
 3470        },
 3471        None,
 3472    ));
 3473    {
 3474        let mut cx = EditorTestContext::new(cx).await;
 3475        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3476        cx.set_state(indoc! {"
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        //
 3482        // ˇ
 3483    "});
 3484
 3485        cx.set_state(indoc! {"
 3486        ///ˇ
 3487    "});
 3488        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3489        cx.assert_editor_state(indoc! {"
 3490        ///
 3491        /// ˇ
 3492    "});
 3493    }
 3494}
 3495
 3496#[gpui::test]
 3497async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3498    init_test(cx, |settings| {
 3499        settings.defaults.tab_size = NonZeroU32::new(4)
 3500    });
 3501
 3502    let language = Arc::new(
 3503        Language::new(
 3504            LanguageConfig {
 3505                documentation_comment: Some(language::BlockCommentConfig {
 3506                    start: "/**".into(),
 3507                    end: "*/".into(),
 3508                    prefix: "* ".into(),
 3509                    tab_size: 1,
 3510                }),
 3511
 3512                ..LanguageConfig::default()
 3513            },
 3514            Some(tree_sitter_rust::LANGUAGE.into()),
 3515        )
 3516        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3517        .unwrap(),
 3518    );
 3519
 3520    {
 3521        let mut cx = EditorTestContext::new(cx).await;
 3522        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3523        cx.set_state(indoc! {"
 3524        /**ˇ
 3525    "});
 3526
 3527        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3528        cx.assert_editor_state(indoc! {"
 3529        /**
 3530         * ˇ
 3531    "});
 3532        // Ensure that if cursor is before the comment start,
 3533        // we do not actually insert a comment prefix.
 3534        cx.set_state(indoc! {"
 3535        ˇ/**
 3536    "});
 3537        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3538        cx.assert_editor_state(indoc! {"
 3539
 3540        ˇ/**
 3541    "});
 3542        // Ensure that if cursor is between it doesn't add comment prefix.
 3543        cx.set_state(indoc! {"
 3544        /*ˇ*
 3545    "});
 3546        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3547        cx.assert_editor_state(indoc! {"
 3548        /*
 3549        ˇ*
 3550    "});
 3551        // Ensure that if suffix exists on same line after cursor it adds new line.
 3552        cx.set_state(indoc! {"
 3553        /**ˇ*/
 3554    "});
 3555        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3556        cx.assert_editor_state(indoc! {"
 3557        /**
 3558         * ˇ
 3559         */
 3560    "});
 3561        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3562        cx.set_state(indoc! {"
 3563        /**ˇ */
 3564    "});
 3565        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3566        cx.assert_editor_state(indoc! {"
 3567        /**
 3568         * ˇ
 3569         */
 3570    "});
 3571        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3572        cx.set_state(indoc! {"
 3573        /** ˇ*/
 3574    "});
 3575        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3576        cx.assert_editor_state(
 3577            indoc! {"
 3578        /**s
 3579         * ˇ
 3580         */
 3581    "}
 3582            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3583            .as_str(),
 3584        );
 3585        // Ensure that delimiter space is preserved when newline on already
 3586        // spaced delimiter.
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(
 3589            indoc! {"
 3590        /**s
 3591         *s
 3592         * ˇ
 3593         */
 3594    "}
 3595            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3596            .as_str(),
 3597        );
 3598        // Ensure that delimiter space is preserved when space is not
 3599        // on existing delimiter.
 3600        cx.set_state(indoc! {"
 3601        /**
 3602 3603         */
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         * ˇ
 3610         */
 3611    "});
 3612        // Ensure that if suffix exists on same line after cursor it
 3613        // doesn't add extra new line if prefix is not on same line.
 3614        cx.set_state(indoc! {"
 3615        /**
 3616        ˇ*/
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        /**
 3621
 3622        ˇ*/
 3623    "});
 3624        // Ensure that it detects suffix after existing prefix.
 3625        cx.set_state(indoc! {"
 3626        /**ˇ/
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        /**
 3631        ˇ/
 3632    "});
 3633        // Ensure that if suffix exists on same line before
 3634        // cursor it does not add comment prefix.
 3635        cx.set_state(indoc! {"
 3636        /** */ˇ
 3637    "});
 3638        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3639        cx.assert_editor_state(indoc! {"
 3640        /** */
 3641        ˇ
 3642    "});
 3643        // Ensure that if suffix exists on same line before
 3644        // cursor it does not add comment prefix.
 3645        cx.set_state(indoc! {"
 3646        /**
 3647         *
 3648         */ˇ
 3649    "});
 3650        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3651        cx.assert_editor_state(indoc! {"
 3652        /**
 3653         *
 3654         */
 3655         ˇ
 3656    "});
 3657
 3658        // Ensure that inline comment followed by code
 3659        // doesn't add comment prefix on newline
 3660        cx.set_state(indoc! {"
 3661        /** */ textˇ
 3662    "});
 3663        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3664        cx.assert_editor_state(indoc! {"
 3665        /** */ text
 3666        ˇ
 3667    "});
 3668
 3669        // Ensure that text after comment end tag
 3670        // doesn't add comment prefix on newline
 3671        cx.set_state(indoc! {"
 3672        /**
 3673         *
 3674         */ˇtext
 3675    "});
 3676        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677        cx.assert_editor_state(indoc! {"
 3678        /**
 3679         *
 3680         */
 3681         ˇtext
 3682    "});
 3683
 3684        // Ensure if not comment block it doesn't
 3685        // add comment prefix on newline
 3686        cx.set_state(indoc! {"
 3687        * textˇ
 3688    "});
 3689        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3690        cx.assert_editor_state(indoc! {"
 3691        * text
 3692        ˇ
 3693    "});
 3694    }
 3695    // Ensure that comment continuations can be disabled.
 3696    update_test_language_settings(cx, |settings| {
 3697        settings.defaults.extend_comment_on_newline = Some(false);
 3698    });
 3699    let mut cx = EditorTestContext::new(cx).await;
 3700    cx.set_state(indoc! {"
 3701        /**ˇ
 3702    "});
 3703    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3704    cx.assert_editor_state(indoc! {"
 3705        /**
 3706        ˇ
 3707    "});
 3708}
 3709
 3710#[gpui::test]
 3711async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3712    init_test(cx, |settings| {
 3713        settings.defaults.tab_size = NonZeroU32::new(4)
 3714    });
 3715
 3716    let lua_language = Arc::new(Language::new(
 3717        LanguageConfig {
 3718            line_comments: vec!["--".into()],
 3719            block_comment: Some(language::BlockCommentConfig {
 3720                start: "--[[".into(),
 3721                prefix: "".into(),
 3722                end: "]]".into(),
 3723                tab_size: 0,
 3724            }),
 3725            ..LanguageConfig::default()
 3726        },
 3727        None,
 3728    ));
 3729
 3730    let mut cx = EditorTestContext::new(cx).await;
 3731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3732
 3733    // Line with line comment should extend
 3734    cx.set_state(indoc! {"
 3735        --ˇ
 3736    "});
 3737    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3738    cx.assert_editor_state(indoc! {"
 3739        --
 3740        --ˇ
 3741    "});
 3742
 3743    // Line with block comment that matches line comment should not extend
 3744    cx.set_state(indoc! {"
 3745        --[[ˇ
 3746    "});
 3747    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3748    cx.assert_editor_state(indoc! {"
 3749        --[[
 3750        ˇ
 3751    "});
 3752}
 3753
 3754#[gpui::test]
 3755fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3756    init_test(cx, |_| {});
 3757
 3758    let editor = cx.add_window(|window, cx| {
 3759        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3760        let mut editor = build_editor(buffer, window, cx);
 3761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3762            s.select_ranges([3..4, 11..12, 19..20])
 3763        });
 3764        editor
 3765    });
 3766
 3767    _ = editor.update(cx, |editor, window, cx| {
 3768        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3769        editor.buffer.update(cx, |buffer, cx| {
 3770            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3771            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3772        });
 3773        assert_eq!(
 3774            editor.selections.ranges(&editor.display_snapshot(cx)),
 3775            &[2..2, 7..7, 12..12],
 3776        );
 3777
 3778        editor.insert("Z", window, cx);
 3779        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3780
 3781        // The selections are moved after the inserted characters
 3782        assert_eq!(
 3783            editor.selections.ranges(&editor.display_snapshot(cx)),
 3784            &[3..3, 9..9, 15..15],
 3785        );
 3786    });
 3787}
 3788
 3789#[gpui::test]
 3790async fn test_tab(cx: &mut TestAppContext) {
 3791    init_test(cx, |settings| {
 3792        settings.defaults.tab_size = NonZeroU32::new(3)
 3793    });
 3794
 3795    let mut cx = EditorTestContext::new(cx).await;
 3796    cx.set_state(indoc! {"
 3797        ˇabˇc
 3798        ˇ🏀ˇ🏀ˇefg
 3799 3800    "});
 3801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803           ˇab ˇc
 3804           ˇ🏀  ˇ🏀  ˇefg
 3805        d  ˇ
 3806    "});
 3807
 3808    cx.set_state(indoc! {"
 3809        a
 3810        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3811    "});
 3812    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3813    cx.assert_editor_state(indoc! {"
 3814        a
 3815           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    let language = Arc::new(
 3825        Language::new(
 3826            LanguageConfig::default(),
 3827            Some(tree_sitter_rust::LANGUAGE.into()),
 3828        )
 3829        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3830        .unwrap(),
 3831    );
 3832    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3833
 3834    // test when all cursors are not at suggested indent
 3835    // then simply move to their suggested indent location
 3836    cx.set_state(indoc! {"
 3837        const a: B = (
 3838            c(
 3839        ˇ
 3840        ˇ    )
 3841        );
 3842    "});
 3843    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        const a: B = (
 3846            c(
 3847                ˇ
 3848            ˇ)
 3849        );
 3850    "});
 3851
 3852    // test cursor already at suggested indent not moving when
 3853    // other cursors are yet to reach their suggested indents
 3854    cx.set_state(indoc! {"
 3855        ˇ
 3856        const a: B = (
 3857            c(
 3858                d(
 3859        ˇ
 3860                )
 3861        ˇ
 3862        ˇ    )
 3863        );
 3864    "});
 3865    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3866    cx.assert_editor_state(indoc! {"
 3867        ˇ
 3868        const a: B = (
 3869            c(
 3870                d(
 3871                    ˇ
 3872                )
 3873                ˇ
 3874            ˇ)
 3875        );
 3876    "});
 3877    // test when all cursors are at suggested indent then tab is inserted
 3878    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3879    cx.assert_editor_state(indoc! {"
 3880            ˇ
 3881        const a: B = (
 3882            c(
 3883                d(
 3884                        ˇ
 3885                )
 3886                    ˇ
 3887                ˇ)
 3888        );
 3889    "});
 3890
 3891    // test when current indent is less than suggested indent,
 3892    // we adjust line to match suggested indent and move cursor to it
 3893    //
 3894    // when no other cursor is at word boundary, all of them should move
 3895    cx.set_state(indoc! {"
 3896        const a: B = (
 3897            c(
 3898                d(
 3899        ˇ
 3900        ˇ   )
 3901        ˇ   )
 3902        );
 3903    "});
 3904    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3905    cx.assert_editor_state(indoc! {"
 3906        const a: B = (
 3907            c(
 3908                d(
 3909                    ˇ
 3910                ˇ)
 3911            ˇ)
 3912        );
 3913    "});
 3914
 3915    // test when current indent is less than suggested indent,
 3916    // we adjust line to match suggested indent and move cursor to it
 3917    //
 3918    // when some other cursor is at word boundary, it should not move
 3919    cx.set_state(indoc! {"
 3920        const a: B = (
 3921            c(
 3922                d(
 3923        ˇ
 3924        ˇ   )
 3925           ˇ)
 3926        );
 3927    "});
 3928    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3929    cx.assert_editor_state(indoc! {"
 3930        const a: B = (
 3931            c(
 3932                d(
 3933                    ˇ
 3934                ˇ)
 3935            ˇ)
 3936        );
 3937    "});
 3938
 3939    // test when current indent is more than suggested indent,
 3940    // we just move cursor to current indent instead of suggested indent
 3941    //
 3942    // when no other cursor is at word boundary, all of them should move
 3943    cx.set_state(indoc! {"
 3944        const a: B = (
 3945            c(
 3946                d(
 3947        ˇ
 3948        ˇ                )
 3949        ˇ   )
 3950        );
 3951    "});
 3952    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3953    cx.assert_editor_state(indoc! {"
 3954        const a: B = (
 3955            c(
 3956                d(
 3957                    ˇ
 3958                        ˇ)
 3959            ˇ)
 3960        );
 3961    "});
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964        const a: B = (
 3965            c(
 3966                d(
 3967                        ˇ
 3968                            ˇ)
 3969                ˇ)
 3970        );
 3971    "});
 3972
 3973    // test when current indent is more than suggested indent,
 3974    // we just move cursor to current indent instead of suggested indent
 3975    //
 3976    // when some other cursor is at word boundary, it doesn't move
 3977    cx.set_state(indoc! {"
 3978        const a: B = (
 3979            c(
 3980                d(
 3981        ˇ
 3982        ˇ                )
 3983            ˇ)
 3984        );
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        const a: B = (
 3989            c(
 3990                d(
 3991                    ˇ
 3992                        ˇ)
 3993            ˇ)
 3994        );
 3995    "});
 3996
 3997    // handle auto-indent when there are multiple cursors on the same line
 3998    cx.set_state(indoc! {"
 3999        const a: B = (
 4000            c(
 4001        ˇ    ˇ
 4002        ˇ    )
 4003        );
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        const a: B = (
 4008            c(
 4009                ˇ
 4010            ˇ)
 4011        );
 4012    "});
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4017    init_test(cx, |settings| {
 4018        settings.defaults.tab_size = NonZeroU32::new(3)
 4019    });
 4020
 4021    let mut cx = EditorTestContext::new(cx).await;
 4022    cx.set_state(indoc! {"
 4023         ˇ
 4024        \t ˇ
 4025        \t  ˇ
 4026        \t   ˇ
 4027         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4028    "});
 4029
 4030    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4031    cx.assert_editor_state(indoc! {"
 4032           ˇ
 4033        \t   ˇ
 4034        \t   ˇ
 4035        \t      ˇ
 4036         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4037    "});
 4038}
 4039
 4040#[gpui::test]
 4041async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4042    init_test(cx, |settings| {
 4043        settings.defaults.tab_size = NonZeroU32::new(4)
 4044    });
 4045
 4046    let language = Arc::new(
 4047        Language::new(
 4048            LanguageConfig::default(),
 4049            Some(tree_sitter_rust::LANGUAGE.into()),
 4050        )
 4051        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4052        .unwrap(),
 4053    );
 4054
 4055    let mut cx = EditorTestContext::new(cx).await;
 4056    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4057    cx.set_state(indoc! {"
 4058        fn a() {
 4059            if b {
 4060        \t ˇc
 4061            }
 4062        }
 4063    "});
 4064
 4065    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4066    cx.assert_editor_state(indoc! {"
 4067        fn a() {
 4068            if b {
 4069                ˇc
 4070            }
 4071        }
 4072    "});
 4073}
 4074
 4075#[gpui::test]
 4076async fn test_indent_outdent(cx: &mut TestAppContext) {
 4077    init_test(cx, |settings| {
 4078        settings.defaults.tab_size = NonZeroU32::new(4);
 4079    });
 4080
 4081    let mut cx = EditorTestContext::new(cx).await;
 4082
 4083    cx.set_state(indoc! {"
 4084          «oneˇ» «twoˇ»
 4085        three
 4086         four
 4087    "});
 4088    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4089    cx.assert_editor_state(indoc! {"
 4090            «oneˇ» «twoˇ»
 4091        three
 4092         four
 4093    "});
 4094
 4095    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4096    cx.assert_editor_state(indoc! {"
 4097        «oneˇ» «twoˇ»
 4098        three
 4099         four
 4100    "});
 4101
 4102    // select across line ending
 4103    cx.set_state(indoc! {"
 4104        one two
 4105        t«hree
 4106        ˇ» four
 4107    "});
 4108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4109    cx.assert_editor_state(indoc! {"
 4110        one two
 4111            t«hree
 4112        ˇ» four
 4113    "});
 4114
 4115    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4116    cx.assert_editor_state(indoc! {"
 4117        one two
 4118        t«hree
 4119        ˇ» four
 4120    "});
 4121
 4122    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4123    cx.set_state(indoc! {"
 4124        one two
 4125        ˇthree
 4126            four
 4127    "});
 4128    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4129    cx.assert_editor_state(indoc! {"
 4130        one two
 4131            ˇthree
 4132            four
 4133    "});
 4134
 4135    cx.set_state(indoc! {"
 4136        one two
 4137        ˇ    three
 4138            four
 4139    "});
 4140    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4141    cx.assert_editor_state(indoc! {"
 4142        one two
 4143        ˇthree
 4144            four
 4145    "});
 4146}
 4147
 4148#[gpui::test]
 4149async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4150    // This is a regression test for issue #33761
 4151    init_test(cx, |_| {});
 4152
 4153    let mut cx = EditorTestContext::new(cx).await;
 4154    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4156
 4157    cx.set_state(
 4158        r#"ˇ#     ingress:
 4159ˇ#         api:
 4160ˇ#             enabled: false
 4161ˇ#             pathType: Prefix
 4162ˇ#           console:
 4163ˇ#               enabled: false
 4164ˇ#               pathType: Prefix
 4165"#,
 4166    );
 4167
 4168    // Press tab to indent all lines
 4169    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4170
 4171    cx.assert_editor_state(
 4172        r#"    ˇ#     ingress:
 4173    ˇ#         api:
 4174    ˇ#             enabled: false
 4175    ˇ#             pathType: Prefix
 4176    ˇ#           console:
 4177    ˇ#               enabled: false
 4178    ˇ#               pathType: Prefix
 4179"#,
 4180    );
 4181}
 4182
 4183#[gpui::test]
 4184async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4185    // This is a test to make sure our fix for issue #33761 didn't break anything
 4186    init_test(cx, |_| {});
 4187
 4188    let mut cx = EditorTestContext::new(cx).await;
 4189    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4191
 4192    cx.set_state(
 4193        r#"ˇingress:
 4194ˇ  api:
 4195ˇ    enabled: false
 4196ˇ    pathType: Prefix
 4197"#,
 4198    );
 4199
 4200    // Press tab to indent all lines
 4201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4202
 4203    cx.assert_editor_state(
 4204        r#"ˇingress:
 4205    ˇapi:
 4206        ˇenabled: false
 4207        ˇpathType: Prefix
 4208"#,
 4209    );
 4210}
 4211
 4212#[gpui::test]
 4213async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4214    init_test(cx, |settings| {
 4215        settings.defaults.hard_tabs = Some(true);
 4216    });
 4217
 4218    let mut cx = EditorTestContext::new(cx).await;
 4219
 4220    // select two ranges on one line
 4221    cx.set_state(indoc! {"
 4222        «oneˇ» «twoˇ»
 4223        three
 4224        four
 4225    "});
 4226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4227    cx.assert_editor_state(indoc! {"
 4228        \t«oneˇ» «twoˇ»
 4229        three
 4230        four
 4231    "});
 4232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4233    cx.assert_editor_state(indoc! {"
 4234        \t\t«oneˇ» «twoˇ»
 4235        three
 4236        four
 4237    "});
 4238    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4239    cx.assert_editor_state(indoc! {"
 4240        \t«oneˇ» «twoˇ»
 4241        three
 4242        four
 4243    "});
 4244    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4245    cx.assert_editor_state(indoc! {"
 4246        «oneˇ» «twoˇ»
 4247        three
 4248        four
 4249    "});
 4250
 4251    // select across a line ending
 4252    cx.set_state(indoc! {"
 4253        one two
 4254        t«hree
 4255        ˇ»four
 4256    "});
 4257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4258    cx.assert_editor_state(indoc! {"
 4259        one two
 4260        \tt«hree
 4261        ˇ»four
 4262    "});
 4263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4264    cx.assert_editor_state(indoc! {"
 4265        one two
 4266        \t\tt«hree
 4267        ˇ»four
 4268    "});
 4269    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4270    cx.assert_editor_state(indoc! {"
 4271        one two
 4272        \tt«hree
 4273        ˇ»four
 4274    "});
 4275    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4276    cx.assert_editor_state(indoc! {"
 4277        one two
 4278        t«hree
 4279        ˇ»four
 4280    "});
 4281
 4282    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4283    cx.set_state(indoc! {"
 4284        one two
 4285        ˇthree
 4286        four
 4287    "});
 4288    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4289    cx.assert_editor_state(indoc! {"
 4290        one two
 4291        ˇthree
 4292        four
 4293    "});
 4294    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4295    cx.assert_editor_state(indoc! {"
 4296        one two
 4297        \tˇthree
 4298        four
 4299    "});
 4300    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4301    cx.assert_editor_state(indoc! {"
 4302        one two
 4303        ˇthree
 4304        four
 4305    "});
 4306}
 4307
 4308#[gpui::test]
 4309fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4310    init_test(cx, |settings| {
 4311        settings.languages.0.extend([
 4312            (
 4313                "TOML".into(),
 4314                LanguageSettingsContent {
 4315                    tab_size: NonZeroU32::new(2),
 4316                    ..Default::default()
 4317                },
 4318            ),
 4319            (
 4320                "Rust".into(),
 4321                LanguageSettingsContent {
 4322                    tab_size: NonZeroU32::new(4),
 4323                    ..Default::default()
 4324                },
 4325            ),
 4326        ]);
 4327    });
 4328
 4329    let toml_language = Arc::new(Language::new(
 4330        LanguageConfig {
 4331            name: "TOML".into(),
 4332            ..Default::default()
 4333        },
 4334        None,
 4335    ));
 4336    let rust_language = Arc::new(Language::new(
 4337        LanguageConfig {
 4338            name: "Rust".into(),
 4339            ..Default::default()
 4340        },
 4341        None,
 4342    ));
 4343
 4344    let toml_buffer =
 4345        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4346    let rust_buffer =
 4347        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4348    let multibuffer = cx.new(|cx| {
 4349        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4350        multibuffer.push_excerpts(
 4351            toml_buffer.clone(),
 4352            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4353            cx,
 4354        );
 4355        multibuffer.push_excerpts(
 4356            rust_buffer.clone(),
 4357            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4358            cx,
 4359        );
 4360        multibuffer
 4361    });
 4362
 4363    cx.add_window(|window, cx| {
 4364        let mut editor = build_editor(multibuffer, window, cx);
 4365
 4366        assert_eq!(
 4367            editor.text(cx),
 4368            indoc! {"
 4369                a = 1
 4370                b = 2
 4371
 4372                const c: usize = 3;
 4373            "}
 4374        );
 4375
 4376        select_ranges(
 4377            &mut editor,
 4378            indoc! {"
 4379                «aˇ» = 1
 4380                b = 2
 4381
 4382                «const c:ˇ» usize = 3;
 4383            "},
 4384            window,
 4385            cx,
 4386        );
 4387
 4388        editor.tab(&Tab, window, cx);
 4389        assert_text_with_selections(
 4390            &mut editor,
 4391            indoc! {"
 4392                  «aˇ» = 1
 4393                b = 2
 4394
 4395                    «const c:ˇ» usize = 3;
 4396            "},
 4397            cx,
 4398        );
 4399        editor.backtab(&Backtab, window, cx);
 4400        assert_text_with_selections(
 4401            &mut editor,
 4402            indoc! {"
 4403                «aˇ» = 1
 4404                b = 2
 4405
 4406                «const c:ˇ» usize = 3;
 4407            "},
 4408            cx,
 4409        );
 4410
 4411        editor
 4412    });
 4413}
 4414
 4415#[gpui::test]
 4416async fn test_backspace(cx: &mut TestAppContext) {
 4417    init_test(cx, |_| {});
 4418
 4419    let mut cx = EditorTestContext::new(cx).await;
 4420
 4421    // Basic backspace
 4422    cx.set_state(indoc! {"
 4423        onˇe two three
 4424        fou«rˇ» five six
 4425        seven «ˇeight nine
 4426        »ten
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        oˇe two three
 4431        fouˇ five six
 4432        seven ˇten
 4433    "});
 4434
 4435    // Test backspace inside and around indents
 4436    cx.set_state(indoc! {"
 4437        zero
 4438            ˇone
 4439                ˇtwo
 4440            ˇ ˇ ˇ  three
 4441        ˇ  ˇ  four
 4442    "});
 4443    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4444    cx.assert_editor_state(indoc! {"
 4445        zero
 4446        ˇone
 4447            ˇtwo
 4448        ˇ  threeˇ  four
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_delete(cx: &mut TestAppContext) {
 4454    init_test(cx, |_| {});
 4455
 4456    let mut cx = EditorTestContext::new(cx).await;
 4457    cx.set_state(indoc! {"
 4458        onˇe two three
 4459        fou«rˇ» five six
 4460        seven «ˇeight nine
 4461        »ten
 4462    "});
 4463    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4464    cx.assert_editor_state(indoc! {"
 4465        onˇ two three
 4466        fouˇ five six
 4467        seven ˇten
 4468    "});
 4469}
 4470
 4471#[gpui::test]
 4472fn test_delete_line(cx: &mut TestAppContext) {
 4473    init_test(cx, |_| {});
 4474
 4475    let editor = cx.add_window(|window, cx| {
 4476        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4477        build_editor(buffer, window, cx)
 4478    });
 4479    _ = editor.update(cx, |editor, window, cx| {
 4480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4481            s.select_display_ranges([
 4482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4483                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4484                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4485            ])
 4486        });
 4487        editor.delete_line(&DeleteLine, window, cx);
 4488        assert_eq!(editor.display_text(cx), "ghi");
 4489        assert_eq!(
 4490            editor.selections.display_ranges(cx),
 4491            vec![
 4492                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4493                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4494            ]
 4495        );
 4496    });
 4497
 4498    let editor = cx.add_window(|window, cx| {
 4499        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4500        build_editor(buffer, window, cx)
 4501    });
 4502    _ = editor.update(cx, |editor, window, cx| {
 4503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4504            s.select_display_ranges([
 4505                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4506            ])
 4507        });
 4508        editor.delete_line(&DeleteLine, window, cx);
 4509        assert_eq!(editor.display_text(cx), "ghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4513        );
 4514    });
 4515
 4516    let editor = cx.add_window(|window, cx| {
 4517        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4518        build_editor(buffer, window, cx)
 4519    });
 4520    _ = editor.update(cx, |editor, window, cx| {
 4521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4522            s.select_display_ranges([
 4523                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4524            ])
 4525        });
 4526        editor.delete_line(&DeleteLine, window, cx);
 4527        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4528        assert_eq!(
 4529            editor.selections.display_ranges(cx),
 4530            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4531        );
 4532    });
 4533}
 4534
 4535#[gpui::test]
 4536fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4537    init_test(cx, |_| {});
 4538
 4539    cx.add_window(|window, cx| {
 4540        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4541        let mut editor = build_editor(buffer.clone(), window, cx);
 4542        let buffer = buffer.read(cx).as_singleton().unwrap();
 4543
 4544        assert_eq!(
 4545            editor
 4546                .selections
 4547                .ranges::<Point>(&editor.display_snapshot(cx)),
 4548            &[Point::new(0, 0)..Point::new(0, 0)]
 4549        );
 4550
 4551        // When on single line, replace newline at end by space
 4552        editor.join_lines(&JoinLines, window, cx);
 4553        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4554        assert_eq!(
 4555            editor
 4556                .selections
 4557                .ranges::<Point>(&editor.display_snapshot(cx)),
 4558            &[Point::new(0, 3)..Point::new(0, 3)]
 4559        );
 4560
 4561        // When multiple lines are selected, remove newlines that are spanned by the selection
 4562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4563            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4564        });
 4565        editor.join_lines(&JoinLines, window, cx);
 4566        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4567        assert_eq!(
 4568            editor
 4569                .selections
 4570                .ranges::<Point>(&editor.display_snapshot(cx)),
 4571            &[Point::new(0, 11)..Point::new(0, 11)]
 4572        );
 4573
 4574        // Undo should be transactional
 4575        editor.undo(&Undo, window, cx);
 4576        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4577        assert_eq!(
 4578            editor
 4579                .selections
 4580                .ranges::<Point>(&editor.display_snapshot(cx)),
 4581            &[Point::new(0, 5)..Point::new(2, 2)]
 4582        );
 4583
 4584        // When joining an empty line don't insert a space
 4585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4586            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4587        });
 4588        editor.join_lines(&JoinLines, window, cx);
 4589        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4590        assert_eq!(
 4591            editor
 4592                .selections
 4593                .ranges::<Point>(&editor.display_snapshot(cx)),
 4594            [Point::new(2, 3)..Point::new(2, 3)]
 4595        );
 4596
 4597        // We can remove trailing newlines
 4598        editor.join_lines(&JoinLines, window, cx);
 4599        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4600        assert_eq!(
 4601            editor
 4602                .selections
 4603                .ranges::<Point>(&editor.display_snapshot(cx)),
 4604            [Point::new(2, 3)..Point::new(2, 3)]
 4605        );
 4606
 4607        // We don't blow up on the last line
 4608        editor.join_lines(&JoinLines, window, cx);
 4609        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4610        assert_eq!(
 4611            editor
 4612                .selections
 4613                .ranges::<Point>(&editor.display_snapshot(cx)),
 4614            [Point::new(2, 3)..Point::new(2, 3)]
 4615        );
 4616
 4617        // reset to test indentation
 4618        editor.buffer.update(cx, |buffer, cx| {
 4619            buffer.edit(
 4620                [
 4621                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4622                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4623                ],
 4624                None,
 4625                cx,
 4626            )
 4627        });
 4628
 4629        // We remove any leading spaces
 4630        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4632            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4633        });
 4634        editor.join_lines(&JoinLines, window, cx);
 4635        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4636
 4637        // We don't insert a space for a line containing only spaces
 4638        editor.join_lines(&JoinLines, window, cx);
 4639        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4640
 4641        // We ignore any leading tabs
 4642        editor.join_lines(&JoinLines, window, cx);
 4643        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4644
 4645        editor
 4646    });
 4647}
 4648
 4649#[gpui::test]
 4650fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4651    init_test(cx, |_| {});
 4652
 4653    cx.add_window(|window, cx| {
 4654        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4655        let mut editor = build_editor(buffer.clone(), window, cx);
 4656        let buffer = buffer.read(cx).as_singleton().unwrap();
 4657
 4658        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4659            s.select_ranges([
 4660                Point::new(0, 2)..Point::new(1, 1),
 4661                Point::new(1, 2)..Point::new(1, 2),
 4662                Point::new(3, 1)..Point::new(3, 2),
 4663            ])
 4664        });
 4665
 4666        editor.join_lines(&JoinLines, window, cx);
 4667        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4668
 4669        assert_eq!(
 4670            editor
 4671                .selections
 4672                .ranges::<Point>(&editor.display_snapshot(cx)),
 4673            [
 4674                Point::new(0, 7)..Point::new(0, 7),
 4675                Point::new(1, 3)..Point::new(1, 3)
 4676            ]
 4677        );
 4678        editor
 4679    });
 4680}
 4681
 4682#[gpui::test]
 4683async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4684    init_test(cx, |_| {});
 4685
 4686    let mut cx = EditorTestContext::new(cx).await;
 4687
 4688    let diff_base = r#"
 4689        Line 0
 4690        Line 1
 4691        Line 2
 4692        Line 3
 4693        "#
 4694    .unindent();
 4695
 4696    cx.set_state(
 4697        &r#"
 4698        ˇLine 0
 4699        Line 1
 4700        Line 2
 4701        Line 3
 4702        "#
 4703        .unindent(),
 4704    );
 4705
 4706    cx.set_head_text(&diff_base);
 4707    executor.run_until_parked();
 4708
 4709    // Join lines
 4710    cx.update_editor(|editor, window, cx| {
 4711        editor.join_lines(&JoinLines, window, cx);
 4712    });
 4713    executor.run_until_parked();
 4714
 4715    cx.assert_editor_state(
 4716        &r#"
 4717        Line 0ˇ Line 1
 4718        Line 2
 4719        Line 3
 4720        "#
 4721        .unindent(),
 4722    );
 4723    // Join again
 4724    cx.update_editor(|editor, window, cx| {
 4725        editor.join_lines(&JoinLines, window, cx);
 4726    });
 4727    executor.run_until_parked();
 4728
 4729    cx.assert_editor_state(
 4730        &r#"
 4731        Line 0 Line 1ˇ Line 2
 4732        Line 3
 4733        "#
 4734        .unindent(),
 4735    );
 4736}
 4737
 4738#[gpui::test]
 4739async fn test_custom_newlines_cause_no_false_positive_diffs(
 4740    executor: BackgroundExecutor,
 4741    cx: &mut TestAppContext,
 4742) {
 4743    init_test(cx, |_| {});
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4746    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4747    executor.run_until_parked();
 4748
 4749    cx.update_editor(|editor, window, cx| {
 4750        let snapshot = editor.snapshot(window, cx);
 4751        assert_eq!(
 4752            snapshot
 4753                .buffer_snapshot()
 4754                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4755                .collect::<Vec<_>>(),
 4756            Vec::new(),
 4757            "Should not have any diffs for files with custom newlines"
 4758        );
 4759    });
 4760}
 4761
 4762#[gpui::test]
 4763async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4764    init_test(cx, |_| {});
 4765
 4766    let mut cx = EditorTestContext::new(cx).await;
 4767
 4768    // Test sort_lines_case_insensitive()
 4769    cx.set_state(indoc! {"
 4770        «z
 4771        y
 4772        x
 4773        Z
 4774        Y
 4775        Xˇ»
 4776    "});
 4777    cx.update_editor(|e, window, cx| {
 4778        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4779    });
 4780    cx.assert_editor_state(indoc! {"
 4781        «x
 4782        X
 4783        y
 4784        Y
 4785        z
 4786        Zˇ»
 4787    "});
 4788
 4789    // Test sort_lines_by_length()
 4790    //
 4791    // Demonstrates:
 4792    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4793    // - sort is stable
 4794    cx.set_state(indoc! {"
 4795        «123
 4796        æ
 4797        12
 4798 4799        1
 4800        æˇ»
 4801    "});
 4802    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4803    cx.assert_editor_state(indoc! {"
 4804        «æ
 4805 4806        1
 4807        æ
 4808        12
 4809        123ˇ»
 4810    "});
 4811
 4812    // Test reverse_lines()
 4813    cx.set_state(indoc! {"
 4814        «5
 4815        4
 4816        3
 4817        2
 4818        1ˇ»
 4819    "});
 4820    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4821    cx.assert_editor_state(indoc! {"
 4822        «1
 4823        2
 4824        3
 4825        4
 4826        5ˇ»
 4827    "});
 4828
 4829    // Skip testing shuffle_line()
 4830
 4831    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4832    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4833
 4834    // Don't manipulate when cursor is on single line, but expand the selection
 4835    cx.set_state(indoc! {"
 4836        ddˇdd
 4837        ccc
 4838        bb
 4839        a
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4843    });
 4844    cx.assert_editor_state(indoc! {"
 4845        «ddddˇ»
 4846        ccc
 4847        bb
 4848        a
 4849    "});
 4850
 4851    // Basic manipulate case
 4852    // Start selection moves to column 0
 4853    // End of selection shrinks to fit shorter line
 4854    cx.set_state(indoc! {"
 4855        dd«d
 4856        ccc
 4857        bb
 4858        aaaaaˇ»
 4859    "});
 4860    cx.update_editor(|e, window, cx| {
 4861        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4862    });
 4863    cx.assert_editor_state(indoc! {"
 4864        «aaaaa
 4865        bb
 4866        ccc
 4867        dddˇ»
 4868    "});
 4869
 4870    // Manipulate case with newlines
 4871    cx.set_state(indoc! {"
 4872        dd«d
 4873        ccc
 4874
 4875        bb
 4876        aaaaa
 4877
 4878        ˇ»
 4879    "});
 4880    cx.update_editor(|e, window, cx| {
 4881        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4882    });
 4883    cx.assert_editor_state(indoc! {"
 4884        «
 4885
 4886        aaaaa
 4887        bb
 4888        ccc
 4889        dddˇ»
 4890
 4891    "});
 4892
 4893    // Adding new line
 4894    cx.set_state(indoc! {"
 4895        aa«a
 4896        bbˇ»b
 4897    "});
 4898    cx.update_editor(|e, window, cx| {
 4899        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4900    });
 4901    cx.assert_editor_state(indoc! {"
 4902        «aaa
 4903        bbb
 4904        added_lineˇ»
 4905    "});
 4906
 4907    // Removing line
 4908    cx.set_state(indoc! {"
 4909        aa«a
 4910        bbbˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.manipulate_immutable_lines(window, cx, |lines| {
 4914            lines.pop();
 4915        })
 4916    });
 4917    cx.assert_editor_state(indoc! {"
 4918        «aaaˇ»
 4919    "});
 4920
 4921    // Removing all lines
 4922    cx.set_state(indoc! {"
 4923        aa«a
 4924        bbbˇ»
 4925    "});
 4926    cx.update_editor(|e, window, cx| {
 4927        e.manipulate_immutable_lines(window, cx, |lines| {
 4928            lines.drain(..);
 4929        })
 4930    });
 4931    cx.assert_editor_state(indoc! {"
 4932        ˇ
 4933    "});
 4934}
 4935
 4936#[gpui::test]
 4937async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4938    init_test(cx, |_| {});
 4939
 4940    let mut cx = EditorTestContext::new(cx).await;
 4941
 4942    // Consider continuous selection as single selection
 4943    cx.set_state(indoc! {"
 4944        Aaa«aa
 4945        cˇ»c«c
 4946        bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4951    });
 4952    cx.assert_editor_state(indoc! {"
 4953        «Aaaaa
 4954        ccc
 4955        bb
 4956        aaaaaˇ»
 4957    "});
 4958
 4959    cx.set_state(indoc! {"
 4960        Aaa«aa
 4961        cˇ»c«c
 4962        bb
 4963        aaaˇ»aa
 4964    "});
 4965    cx.update_editor(|e, window, cx| {
 4966        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4967    });
 4968    cx.assert_editor_state(indoc! {"
 4969        «Aaaaa
 4970        ccc
 4971        bbˇ»
 4972    "});
 4973
 4974    // Consider non continuous selection as distinct dedup operations
 4975    cx.set_state(indoc! {"
 4976        «aaaaa
 4977        bb
 4978        aaaaa
 4979        aaaaaˇ»
 4980
 4981        aaa«aaˇ»
 4982    "});
 4983    cx.update_editor(|e, window, cx| {
 4984        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4985    });
 4986    cx.assert_editor_state(indoc! {"
 4987        «aaaaa
 4988        bbˇ»
 4989
 4990        «aaaaaˇ»
 4991    "});
 4992}
 4993
 4994#[gpui::test]
 4995async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4996    init_test(cx, |_| {});
 4997
 4998    let mut cx = EditorTestContext::new(cx).await;
 4999
 5000    cx.set_state(indoc! {"
 5001        «Aaa
 5002        aAa
 5003        Aaaˇ»
 5004    "});
 5005    cx.update_editor(|e, window, cx| {
 5006        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5007    });
 5008    cx.assert_editor_state(indoc! {"
 5009        «Aaa
 5010        aAaˇ»
 5011    "});
 5012
 5013    cx.set_state(indoc! {"
 5014        «Aaa
 5015        aAa
 5016        aaAˇ»
 5017    "});
 5018    cx.update_editor(|e, window, cx| {
 5019        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5020    });
 5021    cx.assert_editor_state(indoc! {"
 5022        «Aaaˇ»
 5023    "});
 5024}
 5025
 5026#[gpui::test]
 5027async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5028    init_test(cx, |_| {});
 5029
 5030    let mut cx = EditorTestContext::new(cx).await;
 5031
 5032    let js_language = Arc::new(Language::new(
 5033        LanguageConfig {
 5034            name: "JavaScript".into(),
 5035            wrap_characters: Some(language::WrapCharactersConfig {
 5036                start_prefix: "<".into(),
 5037                start_suffix: ">".into(),
 5038                end_prefix: "</".into(),
 5039                end_suffix: ">".into(),
 5040            }),
 5041            ..LanguageConfig::default()
 5042        },
 5043        None,
 5044    ));
 5045
 5046    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5047
 5048    cx.set_state(indoc! {"
 5049        «testˇ»
 5050    "});
 5051    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5052    cx.assert_editor_state(indoc! {"
 5053        <«ˇ»>test</«ˇ»>
 5054    "});
 5055
 5056    cx.set_state(indoc! {"
 5057        «test
 5058         testˇ»
 5059    "});
 5060    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5061    cx.assert_editor_state(indoc! {"
 5062        <«ˇ»>test
 5063         test</«ˇ»>
 5064    "});
 5065
 5066    cx.set_state(indoc! {"
 5067        teˇst
 5068    "});
 5069    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5070    cx.assert_editor_state(indoc! {"
 5071        te<«ˇ»></«ˇ»>st
 5072    "});
 5073}
 5074
 5075#[gpui::test]
 5076async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5077    init_test(cx, |_| {});
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    let js_language = Arc::new(Language::new(
 5082        LanguageConfig {
 5083            name: "JavaScript".into(),
 5084            wrap_characters: Some(language::WrapCharactersConfig {
 5085                start_prefix: "<".into(),
 5086                start_suffix: ">".into(),
 5087                end_prefix: "</".into(),
 5088                end_suffix: ">".into(),
 5089            }),
 5090            ..LanguageConfig::default()
 5091        },
 5092        None,
 5093    ));
 5094
 5095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5096
 5097    cx.set_state(indoc! {"
 5098        «testˇ»
 5099        «testˇ» «testˇ»
 5100        «testˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5103    cx.assert_editor_state(indoc! {"
 5104        <«ˇ»>test</«ˇ»>
 5105        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5106        <«ˇ»>test</«ˇ»>
 5107    "});
 5108
 5109    cx.set_state(indoc! {"
 5110        «test
 5111         testˇ»
 5112        «test
 5113         testˇ»
 5114    "});
 5115    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5116    cx.assert_editor_state(indoc! {"
 5117        <«ˇ»>test
 5118         test</«ˇ»>
 5119        <«ˇ»>test
 5120         test</«ˇ»>
 5121    "});
 5122}
 5123
 5124#[gpui::test]
 5125async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5126    init_test(cx, |_| {});
 5127
 5128    let mut cx = EditorTestContext::new(cx).await;
 5129
 5130    let plaintext_language = Arc::new(Language::new(
 5131        LanguageConfig {
 5132            name: "Plain Text".into(),
 5133            ..LanguageConfig::default()
 5134        },
 5135        None,
 5136    ));
 5137
 5138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5139
 5140    cx.set_state(indoc! {"
 5141        «testˇ»
 5142    "});
 5143    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5144    cx.assert_editor_state(indoc! {"
 5145      «testˇ»
 5146    "});
 5147}
 5148
 5149#[gpui::test]
 5150async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5151    init_test(cx, |_| {});
 5152
 5153    let mut cx = EditorTestContext::new(cx).await;
 5154
 5155    // Manipulate with multiple selections on a single line
 5156    cx.set_state(indoc! {"
 5157        dd«dd
 5158        cˇ»c«c
 5159        bb
 5160        aaaˇ»aa
 5161    "});
 5162    cx.update_editor(|e, window, cx| {
 5163        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5164    });
 5165    cx.assert_editor_state(indoc! {"
 5166        «aaaaa
 5167        bb
 5168        ccc
 5169        ddddˇ»
 5170    "});
 5171
 5172    // Manipulate with multiple disjoin selections
 5173    cx.set_state(indoc! {"
 5174 5175        4
 5176        3
 5177        2
 5178        1ˇ»
 5179
 5180        dd«dd
 5181        ccc
 5182        bb
 5183        aaaˇ»aa
 5184    "});
 5185    cx.update_editor(|e, window, cx| {
 5186        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5187    });
 5188    cx.assert_editor_state(indoc! {"
 5189        «1
 5190        2
 5191        3
 5192        4
 5193        5ˇ»
 5194
 5195        «aaaaa
 5196        bb
 5197        ccc
 5198        ddddˇ»
 5199    "});
 5200
 5201    // Adding lines on each selection
 5202    cx.set_state(indoc! {"
 5203 5204        1ˇ»
 5205
 5206        bb«bb
 5207        aaaˇ»aa
 5208    "});
 5209    cx.update_editor(|e, window, cx| {
 5210        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5211    });
 5212    cx.assert_editor_state(indoc! {"
 5213        «2
 5214        1
 5215        added lineˇ»
 5216
 5217        «bbbb
 5218        aaaaa
 5219        added lineˇ»
 5220    "});
 5221
 5222    // Removing lines on each selection
 5223    cx.set_state(indoc! {"
 5224 5225        1ˇ»
 5226
 5227        bb«bb
 5228        aaaˇ»aa
 5229    "});
 5230    cx.update_editor(|e, window, cx| {
 5231        e.manipulate_immutable_lines(window, cx, |lines| {
 5232            lines.pop();
 5233        })
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «2ˇ»
 5237
 5238        «bbbbˇ»
 5239    "});
 5240}
 5241
 5242#[gpui::test]
 5243async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5244    init_test(cx, |settings| {
 5245        settings.defaults.tab_size = NonZeroU32::new(3)
 5246    });
 5247
 5248    let mut cx = EditorTestContext::new(cx).await;
 5249
 5250    // MULTI SELECTION
 5251    // Ln.1 "«" tests empty lines
 5252    // Ln.9 tests just leading whitespace
 5253    cx.set_state(indoc! {"
 5254        «
 5255        abc                 // No indentationˇ»
 5256        «\tabc              // 1 tabˇ»
 5257        \t\tabc «      ˇ»   // 2 tabs
 5258        \t ab«c             // Tab followed by space
 5259         \tabc              // Space followed by tab (3 spaces should be the result)
 5260        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5261           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5262        \t
 5263        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5264    "});
 5265    cx.update_editor(|e, window, cx| {
 5266        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5267    });
 5268    cx.assert_editor_state(
 5269        indoc! {"
 5270            «
 5271            abc                 // No indentation
 5272               abc              // 1 tab
 5273                  abc          // 2 tabs
 5274                abc             // Tab followed by space
 5275               abc              // Space followed by tab (3 spaces should be the result)
 5276                           abc   // Mixed indentation (tab conversion depends on the column)
 5277               abc         // Already space indented
 5278               ·
 5279               abc\tdef          // Only the leading tab is manipulatedˇ»
 5280        "}
 5281        .replace("·", "")
 5282        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5283    );
 5284
 5285    // Test on just a few lines, the others should remain unchanged
 5286    // Only lines (3, 5, 10, 11) should change
 5287    cx.set_state(
 5288        indoc! {"
 5289            ·
 5290            abc                 // No indentation
 5291            \tabcˇ               // 1 tab
 5292            \t\tabc             // 2 tabs
 5293            \t abcˇ              // Tab followed by space
 5294             \tabc              // Space followed by tab (3 spaces should be the result)
 5295            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5296               abc              // Already space indented
 5297            «\t
 5298            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5299        "}
 5300        .replace("·", "")
 5301        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5302    );
 5303    cx.update_editor(|e, window, cx| {
 5304        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5305    });
 5306    cx.assert_editor_state(
 5307        indoc! {"
 5308            ·
 5309            abc                 // No indentation
 5310            «   abc               // 1 tabˇ»
 5311            \t\tabc             // 2 tabs
 5312            «    abc              // Tab followed by spaceˇ»
 5313             \tabc              // Space followed by tab (3 spaces should be the result)
 5314            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5315               abc              // Already space indented
 5316            «   ·
 5317               abc\tdef          // Only the leading tab is manipulatedˇ»
 5318        "}
 5319        .replace("·", "")
 5320        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5321    );
 5322
 5323    // SINGLE SELECTION
 5324    // Ln.1 "«" tests empty lines
 5325    // Ln.9 tests just leading whitespace
 5326    cx.set_state(indoc! {"
 5327        «
 5328        abc                 // No indentation
 5329        \tabc               // 1 tab
 5330        \t\tabc             // 2 tabs
 5331        \t abc              // Tab followed by space
 5332         \tabc              // Space followed by tab (3 spaces should be the result)
 5333        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5334           abc              // Already space indented
 5335        \t
 5336        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5340    });
 5341    cx.assert_editor_state(
 5342        indoc! {"
 5343            «
 5344            abc                 // No indentation
 5345               abc               // 1 tab
 5346                  abc             // 2 tabs
 5347                abc              // Tab followed by space
 5348               abc              // Space followed by tab (3 spaces should be the result)
 5349                           abc   // Mixed indentation (tab conversion depends on the column)
 5350               abc              // Already space indented
 5351               ·
 5352               abc\tdef          // Only the leading tab is manipulatedˇ»
 5353        "}
 5354        .replace("·", "")
 5355        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5356    );
 5357}
 5358
 5359#[gpui::test]
 5360async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5361    init_test(cx, |settings| {
 5362        settings.defaults.tab_size = NonZeroU32::new(3)
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    // MULTI SELECTION
 5368    // Ln.1 "«" tests empty lines
 5369    // Ln.11 tests just leading whitespace
 5370    cx.set_state(indoc! {"
 5371        «
 5372        abˇ»ˇc                 // No indentation
 5373         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5374          abc  «             // 2 spaces (< 3 so dont convert)
 5375           abc              // 3 spaces (convert)
 5376             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5377        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5378        «\t abc              // Tab followed by space
 5379         \tabc              // Space followed by tab (should be consumed due to tab)
 5380        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5381           \tˇ»  «\t
 5382           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5383    "});
 5384    cx.update_editor(|e, window, cx| {
 5385        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5386    });
 5387    cx.assert_editor_state(indoc! {"
 5388        «
 5389        abc                 // No indentation
 5390         abc                // 1 space (< 3 so dont convert)
 5391          abc               // 2 spaces (< 3 so dont convert)
 5392        \tabc              // 3 spaces (convert)
 5393        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5394        \t\t\tabc           // Already tab indented
 5395        \t abc              // Tab followed by space
 5396        \tabc              // Space followed by tab (should be consumed due to tab)
 5397        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5398        \t\t\t
 5399        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5400    "});
 5401
 5402    // Test on just a few lines, the other should remain unchanged
 5403    // Only lines (4, 8, 11, 12) should change
 5404    cx.set_state(
 5405        indoc! {"
 5406            ·
 5407            abc                 // No indentation
 5408             abc                // 1 space (< 3 so dont convert)
 5409              abc               // 2 spaces (< 3 so dont convert)
 5410            «   abc              // 3 spaces (convert)ˇ»
 5411                 abc            // 5 spaces (1 tab + 2 spaces)
 5412            \t\t\tabc           // Already tab indented
 5413            \t abc              // Tab followed by space
 5414             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5415               \t\t  \tabc      // Mixed indentation
 5416            \t \t  \t   \tabc   // Mixed indentation
 5417               \t  \tˇ
 5418            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5419        "}
 5420        .replace("·", "")
 5421        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5422    );
 5423    cx.update_editor(|e, window, cx| {
 5424        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5425    });
 5426    cx.assert_editor_state(
 5427        indoc! {"
 5428            ·
 5429            abc                 // No indentation
 5430             abc                // 1 space (< 3 so dont convert)
 5431              abc               // 2 spaces (< 3 so dont convert)
 5432            «\tabc              // 3 spaces (convert)ˇ»
 5433                 abc            // 5 spaces (1 tab + 2 spaces)
 5434            \t\t\tabc           // Already tab indented
 5435            \t abc              // Tab followed by space
 5436            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5437               \t\t  \tabc      // Mixed indentation
 5438            \t \t  \t   \tabc   // Mixed indentation
 5439            «\t\t\t
 5440            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5441        "}
 5442        .replace("·", "")
 5443        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5444    );
 5445
 5446    // SINGLE SELECTION
 5447    // Ln.1 "«" tests empty lines
 5448    // Ln.11 tests just leading whitespace
 5449    cx.set_state(indoc! {"
 5450        «
 5451        abc                 // No indentation
 5452         abc                // 1 space (< 3 so dont convert)
 5453          abc               // 2 spaces (< 3 so dont convert)
 5454           abc              // 3 spaces (convert)
 5455             abc            // 5 spaces (1 tab + 2 spaces)
 5456        \t\t\tabc           // Already tab indented
 5457        \t abc              // Tab followed by space
 5458         \tabc              // Space followed by tab (should be consumed due to tab)
 5459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5460           \t  \t
 5461           abc   \t         // Only the leading spaces should be convertedˇ»
 5462    "});
 5463    cx.update_editor(|e, window, cx| {
 5464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5465    });
 5466    cx.assert_editor_state(indoc! {"
 5467        «
 5468        abc                 // No indentation
 5469         abc                // 1 space (< 3 so dont convert)
 5470          abc               // 2 spaces (< 3 so dont convert)
 5471        \tabc              // 3 spaces (convert)
 5472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5473        \t\t\tabc           // Already tab indented
 5474        \t abc              // Tab followed by space
 5475        \tabc              // Space followed by tab (should be consumed due to tab)
 5476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5477        \t\t\t
 5478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5479    "});
 5480}
 5481
 5482#[gpui::test]
 5483async fn test_toggle_case(cx: &mut TestAppContext) {
 5484    init_test(cx, |_| {});
 5485
 5486    let mut cx = EditorTestContext::new(cx).await;
 5487
 5488    // If all lower case -> upper case
 5489    cx.set_state(indoc! {"
 5490        «hello worldˇ»
 5491    "});
 5492    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        «HELLO WORLDˇ»
 5495    "});
 5496
 5497    // If all upper case -> lower case
 5498    cx.set_state(indoc! {"
 5499        «HELLO WORLDˇ»
 5500    "});
 5501    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5502    cx.assert_editor_state(indoc! {"
 5503        «hello worldˇ»
 5504    "});
 5505
 5506    // If any upper case characters are identified -> lower case
 5507    // This matches JetBrains IDEs
 5508    cx.set_state(indoc! {"
 5509        «hEllo worldˇ»
 5510    "});
 5511    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5512    cx.assert_editor_state(indoc! {"
 5513        «hello worldˇ»
 5514    "});
 5515}
 5516
 5517#[gpui::test]
 5518async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5519    init_test(cx, |_| {});
 5520
 5521    let mut cx = EditorTestContext::new(cx).await;
 5522
 5523    cx.set_state(indoc! {"
 5524        «implement-windows-supportˇ»
 5525    "});
 5526    cx.update_editor(|e, window, cx| {
 5527        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5528    });
 5529    cx.assert_editor_state(indoc! {"
 5530        «Implement windows supportˇ»
 5531    "});
 5532}
 5533
 5534#[gpui::test]
 5535async fn test_manipulate_text(cx: &mut TestAppContext) {
 5536    init_test(cx, |_| {});
 5537
 5538    let mut cx = EditorTestContext::new(cx).await;
 5539
 5540    // Test convert_to_upper_case()
 5541    cx.set_state(indoc! {"
 5542        «hello worldˇ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «HELLO WORLDˇ»
 5547    "});
 5548
 5549    // Test convert_to_lower_case()
 5550    cx.set_state(indoc! {"
 5551        «HELLO WORLDˇ»
 5552    "});
 5553    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5554    cx.assert_editor_state(indoc! {"
 5555        «hello worldˇ»
 5556    "});
 5557
 5558    // Test multiple line, single selection case
 5559    cx.set_state(indoc! {"
 5560        «The quick brown
 5561        fox jumps over
 5562        the lazy dogˇ»
 5563    "});
 5564    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5565    cx.assert_editor_state(indoc! {"
 5566        «The Quick Brown
 5567        Fox Jumps Over
 5568        The Lazy Dogˇ»
 5569    "});
 5570
 5571    // Test multiple line, single selection case
 5572    cx.set_state(indoc! {"
 5573        «The quick brown
 5574        fox jumps over
 5575        the lazy dogˇ»
 5576    "});
 5577    cx.update_editor(|e, window, cx| {
 5578        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5579    });
 5580    cx.assert_editor_state(indoc! {"
 5581        «TheQuickBrown
 5582        FoxJumpsOver
 5583        TheLazyDogˇ»
 5584    "});
 5585
 5586    // From here on out, test more complex cases of manipulate_text()
 5587
 5588    // Test no selection case - should affect words cursors are in
 5589    // Cursor at beginning, middle, and end of word
 5590    cx.set_state(indoc! {"
 5591        ˇhello big beauˇtiful worldˇ
 5592    "});
 5593    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5594    cx.assert_editor_state(indoc! {"
 5595        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5596    "});
 5597
 5598    // Test multiple selections on a single line and across multiple lines
 5599    cx.set_state(indoc! {"
 5600        «Theˇ» quick «brown
 5601        foxˇ» jumps «overˇ»
 5602        the «lazyˇ» dog
 5603    "});
 5604    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5605    cx.assert_editor_state(indoc! {"
 5606        «THEˇ» quick «BROWN
 5607        FOXˇ» jumps «OVERˇ»
 5608        the «LAZYˇ» dog
 5609    "});
 5610
 5611    // Test case where text length grows
 5612    cx.set_state(indoc! {"
 5613        «tschüߡ»
 5614    "});
 5615    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5616    cx.assert_editor_state(indoc! {"
 5617        «TSCHÜSSˇ»
 5618    "});
 5619
 5620    // Test to make sure we don't crash when text shrinks
 5621    cx.set_state(indoc! {"
 5622        aaa_bbbˇ
 5623    "});
 5624    cx.update_editor(|e, window, cx| {
 5625        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5626    });
 5627    cx.assert_editor_state(indoc! {"
 5628        «aaaBbbˇ»
 5629    "});
 5630
 5631    // Test to make sure we all aware of the fact that each word can grow and shrink
 5632    // Final selections should be aware of this fact
 5633    cx.set_state(indoc! {"
 5634        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5635    "});
 5636    cx.update_editor(|e, window, cx| {
 5637        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5638    });
 5639    cx.assert_editor_state(indoc! {"
 5640        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5641    "});
 5642
 5643    cx.set_state(indoc! {"
 5644        «hElLo, WoRld!ˇ»
 5645    "});
 5646    cx.update_editor(|e, window, cx| {
 5647        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5648    });
 5649    cx.assert_editor_state(indoc! {"
 5650        «HeLlO, wOrLD!ˇ»
 5651    "});
 5652
 5653    // Test selections with `line_mode() = true`.
 5654    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5655    cx.set_state(indoc! {"
 5656        «The quick brown
 5657        fox jumps over
 5658        tˇ»he lazy dog
 5659    "});
 5660    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5661    cx.assert_editor_state(indoc! {"
 5662        «THE QUICK BROWN
 5663        FOX JUMPS OVER
 5664        THE LAZY DOGˇ»
 5665    "});
 5666}
 5667
 5668#[gpui::test]
 5669fn test_duplicate_line(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let editor = cx.add_window(|window, cx| {
 5673        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5674        build_editor(buffer, window, cx)
 5675    });
 5676    _ = editor.update(cx, |editor, window, cx| {
 5677        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5678            s.select_display_ranges([
 5679                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5681                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5682                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5683            ])
 5684        });
 5685        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5686        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5687        assert_eq!(
 5688            editor.selections.display_ranges(cx),
 5689            vec![
 5690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5691                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5692                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5693                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5694            ]
 5695        );
 5696    });
 5697
 5698    let editor = cx.add_window(|window, cx| {
 5699        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5700        build_editor(buffer, window, cx)
 5701    });
 5702    _ = editor.update(cx, |editor, window, cx| {
 5703        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5704            s.select_display_ranges([
 5705                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5707            ])
 5708        });
 5709        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5710        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5711        assert_eq!(
 5712            editor.selections.display_ranges(cx),
 5713            vec![
 5714                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5715                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5716            ]
 5717        );
 5718    });
 5719
 5720    // With `duplicate_line_up` the selections move to the duplicated lines,
 5721    // which are inserted above the original lines
 5722    let editor = cx.add_window(|window, cx| {
 5723        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5724        build_editor(buffer, window, cx)
 5725    });
 5726    _ = editor.update(cx, |editor, window, cx| {
 5727        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5728            s.select_display_ranges([
 5729                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5730                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5731                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5732                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5733            ])
 5734        });
 5735        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5736        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5737        assert_eq!(
 5738            editor.selections.display_ranges(cx),
 5739            vec![
 5740                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5741                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5742                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5743                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5744            ]
 5745        );
 5746    });
 5747
 5748    let editor = cx.add_window(|window, cx| {
 5749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5750        build_editor(buffer, window, cx)
 5751    });
 5752    _ = editor.update(cx, |editor, window, cx| {
 5753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5754            s.select_display_ranges([
 5755                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5756                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5757            ])
 5758        });
 5759        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5760        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5761        assert_eq!(
 5762            editor.selections.display_ranges(cx),
 5763            vec![
 5764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5765                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5766            ]
 5767        );
 5768    });
 5769
 5770    let editor = cx.add_window(|window, cx| {
 5771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5772        build_editor(buffer, window, cx)
 5773    });
 5774    _ = editor.update(cx, |editor, window, cx| {
 5775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5776            s.select_display_ranges([
 5777                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5778                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5779            ])
 5780        });
 5781        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5782        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5783        assert_eq!(
 5784            editor.selections.display_ranges(cx),
 5785            vec![
 5786                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5787                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5788            ]
 5789        );
 5790    });
 5791}
 5792
 5793#[gpui::test]
 5794fn test_move_line_up_down(cx: &mut TestAppContext) {
 5795    init_test(cx, |_| {});
 5796
 5797    let editor = cx.add_window(|window, cx| {
 5798        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5799        build_editor(buffer, window, cx)
 5800    });
 5801    _ = editor.update(cx, |editor, window, cx| {
 5802        editor.fold_creases(
 5803            vec![
 5804                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5805                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5806                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5807            ],
 5808            true,
 5809            window,
 5810            cx,
 5811        );
 5812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5813            s.select_display_ranges([
 5814                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5815                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5816                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5817                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5818            ])
 5819        });
 5820        assert_eq!(
 5821            editor.display_text(cx),
 5822            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5823        );
 5824
 5825        editor.move_line_up(&MoveLineUp, window, cx);
 5826        assert_eq!(
 5827            editor.display_text(cx),
 5828            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5829        );
 5830        assert_eq!(
 5831            editor.selections.display_ranges(cx),
 5832            vec![
 5833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5834                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5836                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5837            ]
 5838        );
 5839    });
 5840
 5841    _ = editor.update(cx, |editor, window, cx| {
 5842        editor.move_line_down(&MoveLineDown, window, cx);
 5843        assert_eq!(
 5844            editor.display_text(cx),
 5845            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5846        );
 5847        assert_eq!(
 5848            editor.selections.display_ranges(cx),
 5849            vec![
 5850                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5851                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5852                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5853                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5854            ]
 5855        );
 5856    });
 5857
 5858    _ = editor.update(cx, |editor, window, cx| {
 5859        editor.move_line_down(&MoveLineDown, window, cx);
 5860        assert_eq!(
 5861            editor.display_text(cx),
 5862            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5863        );
 5864        assert_eq!(
 5865            editor.selections.display_ranges(cx),
 5866            vec![
 5867                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5868                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5869                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5870                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5871            ]
 5872        );
 5873    });
 5874
 5875    _ = editor.update(cx, |editor, window, cx| {
 5876        editor.move_line_up(&MoveLineUp, window, cx);
 5877        assert_eq!(
 5878            editor.display_text(cx),
 5879            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5880        );
 5881        assert_eq!(
 5882            editor.selections.display_ranges(cx),
 5883            vec![
 5884                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5885                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5886                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5887                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5888            ]
 5889        );
 5890    });
 5891}
 5892
 5893#[gpui::test]
 5894fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5895    init_test(cx, |_| {});
 5896    let editor = cx.add_window(|window, cx| {
 5897        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5898        build_editor(buffer, window, cx)
 5899    });
 5900    _ = editor.update(cx, |editor, window, cx| {
 5901        editor.fold_creases(
 5902            vec![Crease::simple(
 5903                Point::new(6, 4)..Point::new(7, 4),
 5904                FoldPlaceholder::test(),
 5905            )],
 5906            true,
 5907            window,
 5908            cx,
 5909        );
 5910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5911            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5912        });
 5913        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5914        editor.move_line_up(&MoveLineUp, window, cx);
 5915        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5916        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5917    });
 5918}
 5919
 5920#[gpui::test]
 5921fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5922    init_test(cx, |_| {});
 5923
 5924    let editor = cx.add_window(|window, cx| {
 5925        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5926        build_editor(buffer, window, cx)
 5927    });
 5928    _ = editor.update(cx, |editor, window, cx| {
 5929        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5930        editor.insert_blocks(
 5931            [BlockProperties {
 5932                style: BlockStyle::Fixed,
 5933                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5934                height: Some(1),
 5935                render: Arc::new(|_| div().into_any()),
 5936                priority: 0,
 5937            }],
 5938            Some(Autoscroll::fit()),
 5939            cx,
 5940        );
 5941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5942            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5943        });
 5944        editor.move_line_down(&MoveLineDown, window, cx);
 5945    });
 5946}
 5947
 5948#[gpui::test]
 5949async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5950    init_test(cx, |_| {});
 5951
 5952    let mut cx = EditorTestContext::new(cx).await;
 5953    cx.set_state(
 5954        &"
 5955            ˇzero
 5956            one
 5957            two
 5958            three
 5959            four
 5960            five
 5961        "
 5962        .unindent(),
 5963    );
 5964
 5965    // Create a four-line block that replaces three lines of text.
 5966    cx.update_editor(|editor, window, cx| {
 5967        let snapshot = editor.snapshot(window, cx);
 5968        let snapshot = &snapshot.buffer_snapshot();
 5969        let placement = BlockPlacement::Replace(
 5970            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5971        );
 5972        editor.insert_blocks(
 5973            [BlockProperties {
 5974                placement,
 5975                height: Some(4),
 5976                style: BlockStyle::Sticky,
 5977                render: Arc::new(|_| gpui::div().into_any_element()),
 5978                priority: 0,
 5979            }],
 5980            None,
 5981            cx,
 5982        );
 5983    });
 5984
 5985    // Move down so that the cursor touches the block.
 5986    cx.update_editor(|editor, window, cx| {
 5987        editor.move_down(&Default::default(), window, cx);
 5988    });
 5989    cx.assert_editor_state(
 5990        &"
 5991            zero
 5992            «one
 5993            two
 5994            threeˇ»
 5995            four
 5996            five
 5997        "
 5998        .unindent(),
 5999    );
 6000
 6001    // Move down past the block.
 6002    cx.update_editor(|editor, window, cx| {
 6003        editor.move_down(&Default::default(), window, cx);
 6004    });
 6005    cx.assert_editor_state(
 6006        &"
 6007            zero
 6008            one
 6009            two
 6010            three
 6011            ˇfour
 6012            five
 6013        "
 6014        .unindent(),
 6015    );
 6016}
 6017
 6018#[gpui::test]
 6019fn test_transpose(cx: &mut TestAppContext) {
 6020    init_test(cx, |_| {});
 6021
 6022    _ = cx.add_window(|window, cx| {
 6023        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6024        editor.set_style(EditorStyle::default(), window, cx);
 6025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6026            s.select_ranges([1..1])
 6027        });
 6028        editor.transpose(&Default::default(), window, cx);
 6029        assert_eq!(editor.text(cx), "bac");
 6030        assert_eq!(
 6031            editor.selections.ranges(&editor.display_snapshot(cx)),
 6032            [2..2]
 6033        );
 6034
 6035        editor.transpose(&Default::default(), window, cx);
 6036        assert_eq!(editor.text(cx), "bca");
 6037        assert_eq!(
 6038            editor.selections.ranges(&editor.display_snapshot(cx)),
 6039            [3..3]
 6040        );
 6041
 6042        editor.transpose(&Default::default(), window, cx);
 6043        assert_eq!(editor.text(cx), "bac");
 6044        assert_eq!(
 6045            editor.selections.ranges(&editor.display_snapshot(cx)),
 6046            [3..3]
 6047        );
 6048
 6049        editor
 6050    });
 6051
 6052    _ = cx.add_window(|window, cx| {
 6053        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6054        editor.set_style(EditorStyle::default(), window, cx);
 6055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6056            s.select_ranges([3..3])
 6057        });
 6058        editor.transpose(&Default::default(), window, cx);
 6059        assert_eq!(editor.text(cx), "acb\nde");
 6060        assert_eq!(
 6061            editor.selections.ranges(&editor.display_snapshot(cx)),
 6062            [3..3]
 6063        );
 6064
 6065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6066            s.select_ranges([4..4])
 6067        });
 6068        editor.transpose(&Default::default(), window, cx);
 6069        assert_eq!(editor.text(cx), "acbd\ne");
 6070        assert_eq!(
 6071            editor.selections.ranges(&editor.display_snapshot(cx)),
 6072            [5..5]
 6073        );
 6074
 6075        editor.transpose(&Default::default(), window, cx);
 6076        assert_eq!(editor.text(cx), "acbde\n");
 6077        assert_eq!(
 6078            editor.selections.ranges(&editor.display_snapshot(cx)),
 6079            [6..6]
 6080        );
 6081
 6082        editor.transpose(&Default::default(), window, cx);
 6083        assert_eq!(editor.text(cx), "acbd\ne");
 6084        assert_eq!(
 6085            editor.selections.ranges(&editor.display_snapshot(cx)),
 6086            [6..6]
 6087        );
 6088
 6089        editor
 6090    });
 6091
 6092    _ = cx.add_window(|window, cx| {
 6093        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6094        editor.set_style(EditorStyle::default(), window, cx);
 6095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6096            s.select_ranges([1..1, 2..2, 4..4])
 6097        });
 6098        editor.transpose(&Default::default(), window, cx);
 6099        assert_eq!(editor.text(cx), "bacd\ne");
 6100        assert_eq!(
 6101            editor.selections.ranges(&editor.display_snapshot(cx)),
 6102            [2..2, 3..3, 5..5]
 6103        );
 6104
 6105        editor.transpose(&Default::default(), window, cx);
 6106        assert_eq!(editor.text(cx), "bcade\n");
 6107        assert_eq!(
 6108            editor.selections.ranges(&editor.display_snapshot(cx)),
 6109            [3..3, 4..4, 6..6]
 6110        );
 6111
 6112        editor.transpose(&Default::default(), window, cx);
 6113        assert_eq!(editor.text(cx), "bcda\ne");
 6114        assert_eq!(
 6115            editor.selections.ranges(&editor.display_snapshot(cx)),
 6116            [4..4, 6..6]
 6117        );
 6118
 6119        editor.transpose(&Default::default(), window, cx);
 6120        assert_eq!(editor.text(cx), "bcade\n");
 6121        assert_eq!(
 6122            editor.selections.ranges(&editor.display_snapshot(cx)),
 6123            [4..4, 6..6]
 6124        );
 6125
 6126        editor.transpose(&Default::default(), window, cx);
 6127        assert_eq!(editor.text(cx), "bcaed\n");
 6128        assert_eq!(
 6129            editor.selections.ranges(&editor.display_snapshot(cx)),
 6130            [5..5, 6..6]
 6131        );
 6132
 6133        editor
 6134    });
 6135
 6136    _ = cx.add_window(|window, cx| {
 6137        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6138        editor.set_style(EditorStyle::default(), window, cx);
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_ranges([4..4])
 6141        });
 6142        editor.transpose(&Default::default(), window, cx);
 6143        assert_eq!(editor.text(cx), "🏀🍐✋");
 6144        assert_eq!(
 6145            editor.selections.ranges(&editor.display_snapshot(cx)),
 6146            [8..8]
 6147        );
 6148
 6149        editor.transpose(&Default::default(), window, cx);
 6150        assert_eq!(editor.text(cx), "🏀✋🍐");
 6151        assert_eq!(
 6152            editor.selections.ranges(&editor.display_snapshot(cx)),
 6153            [11..11]
 6154        );
 6155
 6156        editor.transpose(&Default::default(), window, cx);
 6157        assert_eq!(editor.text(cx), "🏀🍐✋");
 6158        assert_eq!(
 6159            editor.selections.ranges(&editor.display_snapshot(cx)),
 6160            [11..11]
 6161        );
 6162
 6163        editor
 6164    });
 6165}
 6166
 6167#[gpui::test]
 6168async fn test_rewrap(cx: &mut TestAppContext) {
 6169    init_test(cx, |settings| {
 6170        settings.languages.0.extend([
 6171            (
 6172                "Markdown".into(),
 6173                LanguageSettingsContent {
 6174                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6175                    preferred_line_length: Some(40),
 6176                    ..Default::default()
 6177                },
 6178            ),
 6179            (
 6180                "Plain Text".into(),
 6181                LanguageSettingsContent {
 6182                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6183                    preferred_line_length: Some(40),
 6184                    ..Default::default()
 6185                },
 6186            ),
 6187            (
 6188                "C++".into(),
 6189                LanguageSettingsContent {
 6190                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6191                    preferred_line_length: Some(40),
 6192                    ..Default::default()
 6193                },
 6194            ),
 6195            (
 6196                "Python".into(),
 6197                LanguageSettingsContent {
 6198                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6199                    preferred_line_length: Some(40),
 6200                    ..Default::default()
 6201                },
 6202            ),
 6203            (
 6204                "Rust".into(),
 6205                LanguageSettingsContent {
 6206                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6207                    preferred_line_length: Some(40),
 6208                    ..Default::default()
 6209                },
 6210            ),
 6211        ])
 6212    });
 6213
 6214    let mut cx = EditorTestContext::new(cx).await;
 6215
 6216    let cpp_language = Arc::new(Language::new(
 6217        LanguageConfig {
 6218            name: "C++".into(),
 6219            line_comments: vec!["// ".into()],
 6220            ..LanguageConfig::default()
 6221        },
 6222        None,
 6223    ));
 6224    let python_language = Arc::new(Language::new(
 6225        LanguageConfig {
 6226            name: "Python".into(),
 6227            line_comments: vec!["# ".into()],
 6228            ..LanguageConfig::default()
 6229        },
 6230        None,
 6231    ));
 6232    let markdown_language = Arc::new(Language::new(
 6233        LanguageConfig {
 6234            name: "Markdown".into(),
 6235            rewrap_prefixes: vec![
 6236                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6237                regex::Regex::new("[-*+]\\s+").unwrap(),
 6238            ],
 6239            ..LanguageConfig::default()
 6240        },
 6241        None,
 6242    ));
 6243    let rust_language = Arc::new(
 6244        Language::new(
 6245            LanguageConfig {
 6246                name: "Rust".into(),
 6247                line_comments: vec!["// ".into(), "/// ".into()],
 6248                ..LanguageConfig::default()
 6249            },
 6250            Some(tree_sitter_rust::LANGUAGE.into()),
 6251        )
 6252        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6253        .unwrap(),
 6254    );
 6255
 6256    let plaintext_language = Arc::new(Language::new(
 6257        LanguageConfig {
 6258            name: "Plain Text".into(),
 6259            ..LanguageConfig::default()
 6260        },
 6261        None,
 6262    ));
 6263
 6264    // Test basic rewrapping of a long line with a cursor
 6265    assert_rewrap(
 6266        indoc! {"
 6267            // ˇThis is a long comment that needs to be wrapped.
 6268        "},
 6269        indoc! {"
 6270            // ˇThis is a long comment that needs to
 6271            // be wrapped.
 6272        "},
 6273        cpp_language.clone(),
 6274        &mut cx,
 6275    );
 6276
 6277    // Test rewrapping a full selection
 6278    assert_rewrap(
 6279        indoc! {"
 6280            «// This selected long comment needs to be wrapped.ˇ»"
 6281        },
 6282        indoc! {"
 6283            «// This selected long comment needs to
 6284            // be wrapped.ˇ»"
 6285        },
 6286        cpp_language.clone(),
 6287        &mut cx,
 6288    );
 6289
 6290    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6291    assert_rewrap(
 6292        indoc! {"
 6293            // ˇThis is the first line.
 6294            // Thisˇ is the second line.
 6295            // This is the thirdˇ line, all part of one paragraph.
 6296         "},
 6297        indoc! {"
 6298            // ˇThis is the first line. Thisˇ is the
 6299            // second line. This is the thirdˇ line,
 6300            // all part of one paragraph.
 6301         "},
 6302        cpp_language.clone(),
 6303        &mut cx,
 6304    );
 6305
 6306    // Test multiple cursors in different paragraphs trigger separate rewraps
 6307    assert_rewrap(
 6308        indoc! {"
 6309            // ˇThis is the first paragraph, first line.
 6310            // ˇThis is the first paragraph, second line.
 6311
 6312            // ˇThis is the second paragraph, first line.
 6313            // ˇThis is the second paragraph, second line.
 6314        "},
 6315        indoc! {"
 6316            // ˇThis is the first paragraph, first
 6317            // line. ˇThis is the first paragraph,
 6318            // second line.
 6319
 6320            // ˇThis is the second paragraph, first
 6321            // line. ˇThis is the second paragraph,
 6322            // second line.
 6323        "},
 6324        cpp_language.clone(),
 6325        &mut cx,
 6326    );
 6327
 6328    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6329    assert_rewrap(
 6330        indoc! {"
 6331            «// A regular long long comment to be wrapped.
 6332            /// A documentation long comment to be wrapped.ˇ»
 6333          "},
 6334        indoc! {"
 6335            «// A regular long long comment to be
 6336            // wrapped.
 6337            /// A documentation long comment to be
 6338            /// wrapped.ˇ»
 6339          "},
 6340        rust_language.clone(),
 6341        &mut cx,
 6342    );
 6343
 6344    // Test that change in indentation level trigger seperate rewraps
 6345    assert_rewrap(
 6346        indoc! {"
 6347            fn foo() {
 6348                «// This is a long comment at the base indent.
 6349                    // This is a long comment at the next indent.ˇ»
 6350            }
 6351        "},
 6352        indoc! {"
 6353            fn foo() {
 6354                «// This is a long comment at the
 6355                // base indent.
 6356                    // This is a long comment at the
 6357                    // next indent.ˇ»
 6358            }
 6359        "},
 6360        rust_language.clone(),
 6361        &mut cx,
 6362    );
 6363
 6364    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6365    assert_rewrap(
 6366        indoc! {"
 6367            # ˇThis is a long comment using a pound sign.
 6368        "},
 6369        indoc! {"
 6370            # ˇThis is a long comment using a pound
 6371            # sign.
 6372        "},
 6373        python_language,
 6374        &mut cx,
 6375    );
 6376
 6377    // Test rewrapping only affects comments, not code even when selected
 6378    assert_rewrap(
 6379        indoc! {"
 6380            «/// This doc comment is long and should be wrapped.
 6381            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6382        "},
 6383        indoc! {"
 6384            «/// This doc comment is long and should
 6385            /// be wrapped.
 6386            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6387        "},
 6388        rust_language.clone(),
 6389        &mut cx,
 6390    );
 6391
 6392    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6393    assert_rewrap(
 6394        indoc! {"
 6395            # Header
 6396
 6397            A long long long line of markdown text to wrap.ˇ
 6398         "},
 6399        indoc! {"
 6400            # Header
 6401
 6402            A long long long line of markdown text
 6403            to wrap.ˇ
 6404         "},
 6405        markdown_language.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6410    assert_rewrap(
 6411        indoc! {"
 6412            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6413            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6414            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6415        "},
 6416        indoc! {"
 6417            «1. This is a numbered list item that is
 6418               very long and needs to be wrapped
 6419               properly.
 6420            2. This is a numbered list item that is
 6421               very long and needs to be wrapped
 6422               properly.
 6423            - This is an unordered list item that is
 6424              also very long and should not merge
 6425              with the numbered item.ˇ»
 6426        "},
 6427        markdown_language.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6432    assert_rewrap(
 6433        indoc! {"
 6434            «1. This is a numbered list item that is
 6435            very long and needs to be wrapped
 6436            properly.
 6437            2. This is a numbered list item that is
 6438            very long and needs to be wrapped
 6439            properly.
 6440            - This is an unordered list item that is
 6441            also very long and should not merge with
 6442            the numbered item.ˇ»
 6443        "},
 6444        indoc! {"
 6445            «1. This is a numbered list item that is
 6446               very long and needs to be wrapped
 6447               properly.
 6448            2. This is a numbered list item that is
 6449               very long and needs to be wrapped
 6450               properly.
 6451            - This is an unordered list item that is
 6452              also very long and should not merge
 6453              with the numbered item.ˇ»
 6454        "},
 6455        markdown_language.clone(),
 6456        &mut cx,
 6457    );
 6458
 6459    // Test that rewrapping maintain indents even when they already exists.
 6460    assert_rewrap(
 6461        indoc! {"
 6462            «1. This is a numbered list
 6463               item that is very long and needs to be wrapped properly.
 6464            2. This is a numbered list
 6465               item that is very long and needs to be wrapped properly.
 6466            - This is an unordered list item that is also very long and
 6467              should not merge with the numbered item.ˇ»
 6468        "},
 6469        indoc! {"
 6470            «1. This is a numbered list item that is
 6471               very long and needs to be wrapped
 6472               properly.
 6473            2. This is a numbered list item that is
 6474               very long and needs to be wrapped
 6475               properly.
 6476            - This is an unordered list item that is
 6477              also very long and should not merge
 6478              with the numbered item.ˇ»
 6479        "},
 6480        markdown_language,
 6481        &mut cx,
 6482    );
 6483
 6484    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6485    assert_rewrap(
 6486        indoc! {"
 6487            ˇThis is a very long line of plain text that will be wrapped.
 6488        "},
 6489        indoc! {"
 6490            ˇThis is a very long line of plain text
 6491            that will be wrapped.
 6492        "},
 6493        plaintext_language.clone(),
 6494        &mut cx,
 6495    );
 6496
 6497    // Test that non-commented code acts as a paragraph boundary within a selection
 6498    assert_rewrap(
 6499        indoc! {"
 6500               «// This is the first long comment block to be wrapped.
 6501               fn my_func(a: u32);
 6502               // This is the second long comment block to be wrapped.ˇ»
 6503           "},
 6504        indoc! {"
 6505               «// This is the first long comment block
 6506               // to be wrapped.
 6507               fn my_func(a: u32);
 6508               // This is the second long comment block
 6509               // to be wrapped.ˇ»
 6510           "},
 6511        rust_language,
 6512        &mut cx,
 6513    );
 6514
 6515    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6516    assert_rewrap(
 6517        indoc! {"
 6518            «ˇThis is a very long line that will be wrapped.
 6519
 6520            This is another paragraph in the same selection.»
 6521
 6522            «\tThis is a very long indented line that will be wrapped.ˇ»
 6523         "},
 6524        indoc! {"
 6525            «ˇThis is a very long line that will be
 6526            wrapped.
 6527
 6528            This is another paragraph in the same
 6529            selection.»
 6530
 6531            «\tThis is a very long indented line
 6532            \tthat will be wrapped.ˇ»
 6533         "},
 6534        plaintext_language,
 6535        &mut cx,
 6536    );
 6537
 6538    // Test that an empty comment line acts as a paragraph boundary
 6539    assert_rewrap(
 6540        indoc! {"
 6541            // ˇThis is a long comment that will be wrapped.
 6542            //
 6543            // And this is another long comment that will also be wrapped.ˇ
 6544         "},
 6545        indoc! {"
 6546            // ˇThis is a long comment that will be
 6547            // wrapped.
 6548            //
 6549            // And this is another long comment that
 6550            // will also be wrapped.ˇ
 6551         "},
 6552        cpp_language,
 6553        &mut cx,
 6554    );
 6555
 6556    #[track_caller]
 6557    fn assert_rewrap(
 6558        unwrapped_text: &str,
 6559        wrapped_text: &str,
 6560        language: Arc<Language>,
 6561        cx: &mut EditorTestContext,
 6562    ) {
 6563        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6564        cx.set_state(unwrapped_text);
 6565        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6566        cx.assert_editor_state(wrapped_text);
 6567    }
 6568}
 6569
 6570#[gpui::test]
 6571async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6572    init_test(cx, |settings| {
 6573        settings.languages.0.extend([(
 6574            "Rust".into(),
 6575            LanguageSettingsContent {
 6576                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6577                preferred_line_length: Some(40),
 6578                ..Default::default()
 6579            },
 6580        )])
 6581    });
 6582
 6583    let mut cx = EditorTestContext::new(cx).await;
 6584
 6585    let rust_lang = Arc::new(
 6586        Language::new(
 6587            LanguageConfig {
 6588                name: "Rust".into(),
 6589                line_comments: vec!["// ".into()],
 6590                block_comment: Some(BlockCommentConfig {
 6591                    start: "/*".into(),
 6592                    end: "*/".into(),
 6593                    prefix: "* ".into(),
 6594                    tab_size: 1,
 6595                }),
 6596                documentation_comment: Some(BlockCommentConfig {
 6597                    start: "/**".into(),
 6598                    end: "*/".into(),
 6599                    prefix: "* ".into(),
 6600                    tab_size: 1,
 6601                }),
 6602
 6603                ..LanguageConfig::default()
 6604            },
 6605            Some(tree_sitter_rust::LANGUAGE.into()),
 6606        )
 6607        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6608        .unwrap(),
 6609    );
 6610
 6611    // regular block comment
 6612    assert_rewrap(
 6613        indoc! {"
 6614            /*
 6615             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6616             */
 6617            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6618        "},
 6619        indoc! {"
 6620            /*
 6621             *ˇ Lorem ipsum dolor sit amet,
 6622             * consectetur adipiscing elit.
 6623             */
 6624            /*
 6625             *ˇ Lorem ipsum dolor sit amet,
 6626             * consectetur adipiscing elit.
 6627             */
 6628        "},
 6629        rust_lang.clone(),
 6630        &mut cx,
 6631    );
 6632
 6633    // indent is respected
 6634    assert_rewrap(
 6635        indoc! {"
 6636            {}
 6637                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6638        "},
 6639        indoc! {"
 6640            {}
 6641                /*
 6642                 *ˇ Lorem ipsum dolor sit amet,
 6643                 * consectetur adipiscing elit.
 6644                 */
 6645        "},
 6646        rust_lang.clone(),
 6647        &mut cx,
 6648    );
 6649
 6650    // short block comments with inline delimiters
 6651    assert_rewrap(
 6652        indoc! {"
 6653            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6654            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6655             */
 6656            /*
 6657             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658        "},
 6659        indoc! {"
 6660            /*
 6661             *ˇ Lorem ipsum dolor sit amet,
 6662             * consectetur adipiscing elit.
 6663             */
 6664            /*
 6665             *ˇ Lorem ipsum dolor sit amet,
 6666             * consectetur adipiscing elit.
 6667             */
 6668            /*
 6669             *ˇ Lorem ipsum dolor sit amet,
 6670             * consectetur adipiscing elit.
 6671             */
 6672        "},
 6673        rust_lang.clone(),
 6674        &mut cx,
 6675    );
 6676
 6677    // multiline block comment with inline start/end delimiters
 6678    assert_rewrap(
 6679        indoc! {"
 6680            /*ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit. */
 6682        "},
 6683        indoc! {"
 6684            /*
 6685             *ˇ Lorem ipsum dolor sit amet,
 6686             * consectetur adipiscing elit.
 6687             */
 6688        "},
 6689        rust_lang.clone(),
 6690        &mut cx,
 6691    );
 6692
 6693    // block comment rewrap still respects paragraph bounds
 6694    assert_rewrap(
 6695        indoc! {"
 6696            /*
 6697             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6698             *
 6699             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6700             */
 6701        "},
 6702        indoc! {"
 6703            /*
 6704             *ˇ Lorem ipsum dolor sit amet,
 6705             * consectetur adipiscing elit.
 6706             *
 6707             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6708             */
 6709        "},
 6710        rust_lang.clone(),
 6711        &mut cx,
 6712    );
 6713
 6714    // documentation comments
 6715    assert_rewrap(
 6716        indoc! {"
 6717            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6718            /**
 6719             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6720             */
 6721        "},
 6722        indoc! {"
 6723            /**
 6724             *ˇ Lorem ipsum dolor sit amet,
 6725             * consectetur adipiscing elit.
 6726             */
 6727            /**
 6728             *ˇ Lorem ipsum dolor sit amet,
 6729             * consectetur adipiscing elit.
 6730             */
 6731        "},
 6732        rust_lang.clone(),
 6733        &mut cx,
 6734    );
 6735
 6736    // different, adjacent comments
 6737    assert_rewrap(
 6738        indoc! {"
 6739            /**
 6740             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6741             */
 6742            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6743            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6744        "},
 6745        indoc! {"
 6746            /**
 6747             *ˇ Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit.
 6749             */
 6750            /*
 6751             *ˇ Lorem ipsum dolor sit amet,
 6752             * consectetur adipiscing elit.
 6753             */
 6754            //ˇ Lorem ipsum dolor sit amet,
 6755            // consectetur adipiscing elit.
 6756        "},
 6757        rust_lang.clone(),
 6758        &mut cx,
 6759    );
 6760
 6761    // selection w/ single short block comment
 6762    assert_rewrap(
 6763        indoc! {"
 6764            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6765        "},
 6766        indoc! {"
 6767            «/*
 6768             * Lorem ipsum dolor sit amet,
 6769             * consectetur adipiscing elit.
 6770             */ˇ»
 6771        "},
 6772        rust_lang.clone(),
 6773        &mut cx,
 6774    );
 6775
 6776    // rewrapping a single comment w/ abutting comments
 6777    assert_rewrap(
 6778        indoc! {"
 6779            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6780            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6781        "},
 6782        indoc! {"
 6783            /*
 6784             * ˇLorem ipsum dolor sit amet,
 6785             * consectetur adipiscing elit.
 6786             */
 6787            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6788        "},
 6789        rust_lang.clone(),
 6790        &mut cx,
 6791    );
 6792
 6793    // selection w/ non-abutting short block comments
 6794    assert_rewrap(
 6795        indoc! {"
 6796            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6797
 6798            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6799        "},
 6800        indoc! {"
 6801            «/*
 6802             * Lorem ipsum dolor sit amet,
 6803             * consectetur adipiscing elit.
 6804             */
 6805
 6806            /*
 6807             * Lorem ipsum dolor sit amet,
 6808             * consectetur adipiscing elit.
 6809             */ˇ»
 6810        "},
 6811        rust_lang.clone(),
 6812        &mut cx,
 6813    );
 6814
 6815    // selection of multiline block comments
 6816    assert_rewrap(
 6817        indoc! {"
 6818            «/* Lorem ipsum dolor sit amet,
 6819             * consectetur adipiscing elit. */ˇ»
 6820        "},
 6821        indoc! {"
 6822            «/*
 6823             * Lorem ipsum dolor sit amet,
 6824             * consectetur adipiscing elit.
 6825             */ˇ»
 6826        "},
 6827        rust_lang.clone(),
 6828        &mut cx,
 6829    );
 6830
 6831    // partial selection of multiline block comments
 6832    assert_rewrap(
 6833        indoc! {"
 6834            «/* Lorem ipsum dolor sit amet,ˇ»
 6835             * consectetur adipiscing elit. */
 6836            /* Lorem ipsum dolor sit amet,
 6837             «* consectetur adipiscing elit. */ˇ»
 6838        "},
 6839        indoc! {"
 6840            «/*
 6841             * Lorem ipsum dolor sit amet,ˇ»
 6842             * consectetur adipiscing elit. */
 6843            /* Lorem ipsum dolor sit amet,
 6844             «* consectetur adipiscing elit.
 6845             */ˇ»
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // selection w/ abutting short block comments
 6852    // TODO: should not be combined; should rewrap as 2 comments
 6853    assert_rewrap(
 6854        indoc! {"
 6855            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6856            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6857        "},
 6858        // desired behavior:
 6859        // indoc! {"
 6860        //     «/*
 6861        //      * Lorem ipsum dolor sit amet,
 6862        //      * consectetur adipiscing elit.
 6863        //      */
 6864        //     /*
 6865        //      * Lorem ipsum dolor sit amet,
 6866        //      * consectetur adipiscing elit.
 6867        //      */ˇ»
 6868        // "},
 6869        // actual behaviour:
 6870        indoc! {"
 6871            «/*
 6872             * Lorem ipsum dolor sit amet,
 6873             * consectetur adipiscing elit. Lorem
 6874             * ipsum dolor sit amet, consectetur
 6875             * adipiscing elit.
 6876             */ˇ»
 6877        "},
 6878        rust_lang.clone(),
 6879        &mut cx,
 6880    );
 6881
 6882    // TODO: same as above, but with delimiters on separate line
 6883    // assert_rewrap(
 6884    //     indoc! {"
 6885    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6886    //          */
 6887    //         /*
 6888    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6889    //     "},
 6890    //     // desired:
 6891    //     // indoc! {"
 6892    //     //     «/*
 6893    //     //      * Lorem ipsum dolor sit amet,
 6894    //     //      * consectetur adipiscing elit.
 6895    //     //      */
 6896    //     //     /*
 6897    //     //      * Lorem ipsum dolor sit amet,
 6898    //     //      * consectetur adipiscing elit.
 6899    //     //      */ˇ»
 6900    //     // "},
 6901    //     // actual: (but with trailing w/s on the empty lines)
 6902    //     indoc! {"
 6903    //         «/*
 6904    //          * Lorem ipsum dolor sit amet,
 6905    //          * consectetur adipiscing elit.
 6906    //          *
 6907    //          */
 6908    //         /*
 6909    //          *
 6910    //          * Lorem ipsum dolor sit amet,
 6911    //          * consectetur adipiscing elit.
 6912    //          */ˇ»
 6913    //     "},
 6914    //     rust_lang.clone(),
 6915    //     &mut cx,
 6916    // );
 6917
 6918    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6919    assert_rewrap(
 6920        indoc! {"
 6921            /*
 6922             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6923             */
 6924            /*
 6925             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6926            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6927        "},
 6928        // desired:
 6929        // indoc! {"
 6930        //     /*
 6931        //      *ˇ Lorem ipsum dolor sit amet,
 6932        //      * consectetur adipiscing elit.
 6933        //      */
 6934        //     /*
 6935        //      *ˇ Lorem ipsum dolor sit amet,
 6936        //      * consectetur adipiscing elit.
 6937        //      */
 6938        //     /*
 6939        //      *ˇ Lorem ipsum dolor sit amet
 6940        //      */ /* consectetur adipiscing elit. */
 6941        // "},
 6942        // actual:
 6943        indoc! {"
 6944            /*
 6945             //ˇ Lorem ipsum dolor sit amet,
 6946             // consectetur adipiscing elit.
 6947             */
 6948            /*
 6949             * //ˇ Lorem ipsum dolor sit amet,
 6950             * consectetur adipiscing elit.
 6951             */
 6952            /*
 6953             *ˇ Lorem ipsum dolor sit amet */ /*
 6954             * consectetur adipiscing elit.
 6955             */
 6956        "},
 6957        rust_lang,
 6958        &mut cx,
 6959    );
 6960
 6961    #[track_caller]
 6962    fn assert_rewrap(
 6963        unwrapped_text: &str,
 6964        wrapped_text: &str,
 6965        language: Arc<Language>,
 6966        cx: &mut EditorTestContext,
 6967    ) {
 6968        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6969        cx.set_state(unwrapped_text);
 6970        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6971        cx.assert_editor_state(wrapped_text);
 6972    }
 6973}
 6974
 6975#[gpui::test]
 6976async fn test_hard_wrap(cx: &mut TestAppContext) {
 6977    init_test(cx, |_| {});
 6978    let mut cx = EditorTestContext::new(cx).await;
 6979
 6980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6981    cx.update_editor(|editor, _, cx| {
 6982        editor.set_hard_wrap(Some(14), cx);
 6983    });
 6984
 6985    cx.set_state(indoc!(
 6986        "
 6987        one two three ˇ
 6988        "
 6989    ));
 6990    cx.simulate_input("four");
 6991    cx.run_until_parked();
 6992
 6993    cx.assert_editor_state(indoc!(
 6994        "
 6995        one two three
 6996        fourˇ
 6997        "
 6998    ));
 6999
 7000    cx.update_editor(|editor, window, cx| {
 7001        editor.newline(&Default::default(), window, cx);
 7002    });
 7003    cx.run_until_parked();
 7004    cx.assert_editor_state(indoc!(
 7005        "
 7006        one two three
 7007        four
 7008        ˇ
 7009        "
 7010    ));
 7011
 7012    cx.simulate_input("five");
 7013    cx.run_until_parked();
 7014    cx.assert_editor_state(indoc!(
 7015        "
 7016        one two three
 7017        four
 7018        fiveˇ
 7019        "
 7020    ));
 7021
 7022    cx.update_editor(|editor, window, cx| {
 7023        editor.newline(&Default::default(), window, cx);
 7024    });
 7025    cx.run_until_parked();
 7026    cx.simulate_input("# ");
 7027    cx.run_until_parked();
 7028    cx.assert_editor_state(indoc!(
 7029        "
 7030        one two three
 7031        four
 7032        five
 7033        # ˇ
 7034        "
 7035    ));
 7036
 7037    cx.update_editor(|editor, window, cx| {
 7038        editor.newline(&Default::default(), window, cx);
 7039    });
 7040    cx.run_until_parked();
 7041    cx.assert_editor_state(indoc!(
 7042        "
 7043        one two three
 7044        four
 7045        five
 7046        #\x20
 7047 7048        "
 7049    ));
 7050
 7051    cx.simulate_input(" 6");
 7052    cx.run_until_parked();
 7053    cx.assert_editor_state(indoc!(
 7054        "
 7055        one two three
 7056        four
 7057        five
 7058        #
 7059        # 6ˇ
 7060        "
 7061    ));
 7062}
 7063
 7064#[gpui::test]
 7065async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7066    init_test(cx, |_| {});
 7067
 7068    let mut cx = EditorTestContext::new(cx).await;
 7069
 7070    cx.set_state(indoc! {"The quick brownˇ"});
 7071    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7072    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7073
 7074    cx.set_state(indoc! {"The emacs foxˇ"});
 7075    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7076    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7077
 7078    cx.set_state(indoc! {"
 7079        The quick« brownˇ»
 7080        fox jumps overˇ
 7081        the lazy dog"});
 7082    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7083    cx.assert_editor_state(indoc! {"
 7084        The quickˇ
 7085        ˇthe lazy dog"});
 7086
 7087    cx.set_state(indoc! {"
 7088        The quick« brownˇ»
 7089        fox jumps overˇ
 7090        the lazy dog"});
 7091    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7092    cx.assert_editor_state(indoc! {"
 7093        The quickˇ
 7094        fox jumps overˇthe lazy dog"});
 7095
 7096    cx.set_state(indoc! {"
 7097        The quick« brownˇ»
 7098        fox jumps overˇ
 7099        the lazy dog"});
 7100    cx.update_editor(|e, window, cx| {
 7101        e.cut_to_end_of_line(
 7102            &CutToEndOfLine {
 7103                stop_at_newlines: true,
 7104            },
 7105            window,
 7106            cx,
 7107        )
 7108    });
 7109    cx.assert_editor_state(indoc! {"
 7110        The quickˇ
 7111        fox jumps overˇ
 7112        the lazy dog"});
 7113
 7114    cx.set_state(indoc! {"
 7115        The quick« brownˇ»
 7116        fox jumps overˇ
 7117        the lazy dog"});
 7118    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7119    cx.assert_editor_state(indoc! {"
 7120        The quickˇ
 7121        fox jumps overˇthe lazy dog"});
 7122}
 7123
 7124#[gpui::test]
 7125async fn test_clipboard(cx: &mut TestAppContext) {
 7126    init_test(cx, |_| {});
 7127
 7128    let mut cx = EditorTestContext::new(cx).await;
 7129
 7130    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7131    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7132    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7133
 7134    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7135    cx.set_state("two ˇfour ˇsix ˇ");
 7136    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7137    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7138
 7139    // Paste again but with only two cursors. Since the number of cursors doesn't
 7140    // match the number of slices in the clipboard, the entire clipboard text
 7141    // is pasted at each cursor.
 7142    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7143    cx.update_editor(|e, window, cx| {
 7144        e.handle_input("( ", window, cx);
 7145        e.paste(&Paste, window, cx);
 7146        e.handle_input(") ", window, cx);
 7147    });
 7148    cx.assert_editor_state(
 7149        &([
 7150            "( one✅ ",
 7151            "three ",
 7152            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7153            "three ",
 7154            "five ) ˇ",
 7155        ]
 7156        .join("\n")),
 7157    );
 7158
 7159    // Cut with three selections, one of which is full-line.
 7160    cx.set_state(indoc! {"
 7161        1«2ˇ»3
 7162        4ˇ567
 7163        «8ˇ»9"});
 7164    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7165    cx.assert_editor_state(indoc! {"
 7166        1ˇ3
 7167        ˇ9"});
 7168
 7169    // Paste with three selections, noticing how the copied selection that was full-line
 7170    // gets inserted before the second cursor.
 7171    cx.set_state(indoc! {"
 7172        1ˇ3
 7173 7174        «oˇ»ne"});
 7175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7176    cx.assert_editor_state(indoc! {"
 7177        12ˇ3
 7178        4567
 7179 7180        8ˇne"});
 7181
 7182    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7183    cx.set_state(indoc! {"
 7184        The quick brown
 7185        fox juˇmps over
 7186        the lazy dog"});
 7187    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7188    assert_eq!(
 7189        cx.read_from_clipboard()
 7190            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7191        Some("fox jumps over\n".to_string())
 7192    );
 7193
 7194    // Paste with three selections, noticing how the copied full-line selection is inserted
 7195    // before the empty selections but replaces the selection that is non-empty.
 7196    cx.set_state(indoc! {"
 7197        Tˇhe quick brown
 7198        «foˇ»x jumps over
 7199        tˇhe lazy dog"});
 7200    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7201    cx.assert_editor_state(indoc! {"
 7202        fox jumps over
 7203        Tˇhe quick brown
 7204        fox jumps over
 7205        ˇx jumps over
 7206        fox jumps over
 7207        tˇhe lazy dog"});
 7208}
 7209
 7210#[gpui::test]
 7211async fn test_copy_trim(cx: &mut TestAppContext) {
 7212    init_test(cx, |_| {});
 7213
 7214    let mut cx = EditorTestContext::new(cx).await;
 7215    cx.set_state(
 7216        r#"            «for selection in selections.iter() {
 7217            let mut start = selection.start;
 7218            let mut end = selection.end;
 7219            let is_entire_line = selection.is_empty();
 7220            if is_entire_line {
 7221                start = Point::new(start.row, 0);ˇ»
 7222                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7223            }
 7224        "#,
 7225    );
 7226    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7227    assert_eq!(
 7228        cx.read_from_clipboard()
 7229            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7230        Some(
 7231            "for selection in selections.iter() {
 7232            let mut start = selection.start;
 7233            let mut end = selection.end;
 7234            let is_entire_line = selection.is_empty();
 7235            if is_entire_line {
 7236                start = Point::new(start.row, 0);"
 7237                .to_string()
 7238        ),
 7239        "Regular copying preserves all indentation selected",
 7240    );
 7241    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7242    assert_eq!(
 7243        cx.read_from_clipboard()
 7244            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7245        Some(
 7246            "for selection in selections.iter() {
 7247let mut start = selection.start;
 7248let mut end = selection.end;
 7249let is_entire_line = selection.is_empty();
 7250if is_entire_line {
 7251    start = Point::new(start.row, 0);"
 7252                .to_string()
 7253        ),
 7254        "Copying with stripping should strip all leading whitespaces"
 7255    );
 7256
 7257    cx.set_state(
 7258        r#"       «     for selection in selections.iter() {
 7259            let mut start = selection.start;
 7260            let mut end = selection.end;
 7261            let is_entire_line = selection.is_empty();
 7262            if is_entire_line {
 7263                start = Point::new(start.row, 0);ˇ»
 7264                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7265            }
 7266        "#,
 7267    );
 7268    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7269    assert_eq!(
 7270        cx.read_from_clipboard()
 7271            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7272        Some(
 7273            "     for selection in selections.iter() {
 7274            let mut start = selection.start;
 7275            let mut end = selection.end;
 7276            let is_entire_line = selection.is_empty();
 7277            if is_entire_line {
 7278                start = Point::new(start.row, 0);"
 7279                .to_string()
 7280        ),
 7281        "Regular copying preserves all indentation selected",
 7282    );
 7283    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7284    assert_eq!(
 7285        cx.read_from_clipboard()
 7286            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7287        Some(
 7288            "for selection in selections.iter() {
 7289let mut start = selection.start;
 7290let mut end = selection.end;
 7291let is_entire_line = selection.is_empty();
 7292if is_entire_line {
 7293    start = Point::new(start.row, 0);"
 7294                .to_string()
 7295        ),
 7296        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7297    );
 7298
 7299    cx.set_state(
 7300        r#"       «ˇ     for selection in selections.iter() {
 7301            let mut start = selection.start;
 7302            let mut end = selection.end;
 7303            let is_entire_line = selection.is_empty();
 7304            if is_entire_line {
 7305                start = Point::new(start.row, 0);»
 7306                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7307            }
 7308        "#,
 7309    );
 7310    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7311    assert_eq!(
 7312        cx.read_from_clipboard()
 7313            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7314        Some(
 7315            "     for selection in selections.iter() {
 7316            let mut start = selection.start;
 7317            let mut end = selection.end;
 7318            let is_entire_line = selection.is_empty();
 7319            if is_entire_line {
 7320                start = Point::new(start.row, 0);"
 7321                .to_string()
 7322        ),
 7323        "Regular copying for reverse selection works the same",
 7324    );
 7325    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7326    assert_eq!(
 7327        cx.read_from_clipboard()
 7328            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7329        Some(
 7330            "for selection in selections.iter() {
 7331let mut start = selection.start;
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);"
 7336                .to_string()
 7337        ),
 7338        "Copying with stripping for reverse selection works the same"
 7339    );
 7340
 7341    cx.set_state(
 7342        r#"            for selection «in selections.iter() {
 7343            let mut start = selection.start;
 7344            let mut end = selection.end;
 7345            let is_entire_line = selection.is_empty();
 7346            if is_entire_line {
 7347                start = Point::new(start.row, 0);ˇ»
 7348                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7349            }
 7350        "#,
 7351    );
 7352    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7353    assert_eq!(
 7354        cx.read_from_clipboard()
 7355            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7356        Some(
 7357            "in selections.iter() {
 7358            let mut start = selection.start;
 7359            let mut end = selection.end;
 7360            let is_entire_line = selection.is_empty();
 7361            if is_entire_line {
 7362                start = Point::new(start.row, 0);"
 7363                .to_string()
 7364        ),
 7365        "When selecting past the indent, the copying works as usual",
 7366    );
 7367    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7368    assert_eq!(
 7369        cx.read_from_clipboard()
 7370            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7371        Some(
 7372            "in selections.iter() {
 7373            let mut start = selection.start;
 7374            let mut end = selection.end;
 7375            let is_entire_line = selection.is_empty();
 7376            if is_entire_line {
 7377                start = Point::new(start.row, 0);"
 7378                .to_string()
 7379        ),
 7380        "When selecting past the indent, nothing is trimmed"
 7381    );
 7382
 7383    cx.set_state(
 7384        r#"            «for selection in selections.iter() {
 7385            let mut start = selection.start;
 7386
 7387            let mut end = selection.end;
 7388            let is_entire_line = selection.is_empty();
 7389            if is_entire_line {
 7390                start = Point::new(start.row, 0);
 7391ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7392            }
 7393        "#,
 7394    );
 7395    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7396    assert_eq!(
 7397        cx.read_from_clipboard()
 7398            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7399        Some(
 7400            "for selection in selections.iter() {
 7401let mut start = selection.start;
 7402
 7403let mut end = selection.end;
 7404let is_entire_line = selection.is_empty();
 7405if is_entire_line {
 7406    start = Point::new(start.row, 0);
 7407"
 7408            .to_string()
 7409        ),
 7410        "Copying with stripping should ignore empty lines"
 7411    );
 7412}
 7413
 7414#[gpui::test]
 7415async fn test_paste_multiline(cx: &mut TestAppContext) {
 7416    init_test(cx, |_| {});
 7417
 7418    let mut cx = EditorTestContext::new(cx).await;
 7419    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7420
 7421    // Cut an indented block, without the leading whitespace.
 7422    cx.set_state(indoc! {"
 7423        const a: B = (
 7424            c(),
 7425            «d(
 7426                e,
 7427                f
 7428            )ˇ»
 7429        );
 7430    "});
 7431    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7432    cx.assert_editor_state(indoc! {"
 7433        const a: B = (
 7434            c(),
 7435            ˇ
 7436        );
 7437    "});
 7438
 7439    // Paste it at the same position.
 7440    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7441    cx.assert_editor_state(indoc! {"
 7442        const a: B = (
 7443            c(),
 7444            d(
 7445                e,
 7446                f
 7447 7448        );
 7449    "});
 7450
 7451    // Paste it at a line with a lower indent level.
 7452    cx.set_state(indoc! {"
 7453        ˇ
 7454        const a: B = (
 7455            c(),
 7456        );
 7457    "});
 7458    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7459    cx.assert_editor_state(indoc! {"
 7460        d(
 7461            e,
 7462            f
 7463 7464        const a: B = (
 7465            c(),
 7466        );
 7467    "});
 7468
 7469    // Cut an indented block, with the leading whitespace.
 7470    cx.set_state(indoc! {"
 7471        const a: B = (
 7472            c(),
 7473        «    d(
 7474                e,
 7475                f
 7476            )
 7477        ˇ»);
 7478    "});
 7479    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7480    cx.assert_editor_state(indoc! {"
 7481        const a: B = (
 7482            c(),
 7483        ˇ);
 7484    "});
 7485
 7486    // Paste it at the same position.
 7487    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7488    cx.assert_editor_state(indoc! {"
 7489        const a: B = (
 7490            c(),
 7491            d(
 7492                e,
 7493                f
 7494            )
 7495        ˇ);
 7496    "});
 7497
 7498    // Paste it at a line with a higher indent level.
 7499    cx.set_state(indoc! {"
 7500        const a: B = (
 7501            c(),
 7502            d(
 7503                e,
 7504 7505            )
 7506        );
 7507    "});
 7508    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7509    cx.assert_editor_state(indoc! {"
 7510        const a: B = (
 7511            c(),
 7512            d(
 7513                e,
 7514                f    d(
 7515                    e,
 7516                    f
 7517                )
 7518        ˇ
 7519            )
 7520        );
 7521    "});
 7522
 7523    // Copy an indented block, starting mid-line
 7524    cx.set_state(indoc! {"
 7525        const a: B = (
 7526            c(),
 7527            somethin«g(
 7528                e,
 7529                f
 7530            )ˇ»
 7531        );
 7532    "});
 7533    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7534
 7535    // Paste it on a line with a lower indent level
 7536    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7537    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7538    cx.assert_editor_state(indoc! {"
 7539        const a: B = (
 7540            c(),
 7541            something(
 7542                e,
 7543                f
 7544            )
 7545        );
 7546        g(
 7547            e,
 7548            f
 7549"});
 7550}
 7551
 7552#[gpui::test]
 7553async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7554    init_test(cx, |_| {});
 7555
 7556    cx.write_to_clipboard(ClipboardItem::new_string(
 7557        "    d(\n        e\n    );\n".into(),
 7558    ));
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7562
 7563    cx.set_state(indoc! {"
 7564        fn a() {
 7565            b();
 7566            if c() {
 7567                ˇ
 7568            }
 7569        }
 7570    "});
 7571
 7572    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7573    cx.assert_editor_state(indoc! {"
 7574        fn a() {
 7575            b();
 7576            if c() {
 7577                d(
 7578                    e
 7579                );
 7580        ˇ
 7581            }
 7582        }
 7583    "});
 7584
 7585    cx.set_state(indoc! {"
 7586        fn a() {
 7587            b();
 7588            ˇ
 7589        }
 7590    "});
 7591
 7592    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7593    cx.assert_editor_state(indoc! {"
 7594        fn a() {
 7595            b();
 7596            d(
 7597                e
 7598            );
 7599        ˇ
 7600        }
 7601    "});
 7602}
 7603
 7604#[gpui::test]
 7605fn test_select_all(cx: &mut TestAppContext) {
 7606    init_test(cx, |_| {});
 7607
 7608    let editor = cx.add_window(|window, cx| {
 7609        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7610        build_editor(buffer, window, cx)
 7611    });
 7612    _ = editor.update(cx, |editor, window, cx| {
 7613        editor.select_all(&SelectAll, window, cx);
 7614        assert_eq!(
 7615            editor.selections.display_ranges(cx),
 7616            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7617        );
 7618    });
 7619}
 7620
 7621#[gpui::test]
 7622fn test_select_line(cx: &mut TestAppContext) {
 7623    init_test(cx, |_| {});
 7624
 7625    let editor = cx.add_window(|window, cx| {
 7626        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7627        build_editor(buffer, window, cx)
 7628    });
 7629    _ = editor.update(cx, |editor, window, cx| {
 7630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7631            s.select_display_ranges([
 7632                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7634                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7635                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7636            ])
 7637        });
 7638        editor.select_line(&SelectLine, window, cx);
 7639        assert_eq!(
 7640            editor.selections.display_ranges(cx),
 7641            vec![
 7642                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7643                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7644            ]
 7645        );
 7646    });
 7647
 7648    _ = editor.update(cx, |editor, window, cx| {
 7649        editor.select_line(&SelectLine, window, cx);
 7650        assert_eq!(
 7651            editor.selections.display_ranges(cx),
 7652            vec![
 7653                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7654                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7655            ]
 7656        );
 7657    });
 7658
 7659    _ = editor.update(cx, |editor, window, cx| {
 7660        editor.select_line(&SelectLine, window, cx);
 7661        assert_eq!(
 7662            editor.selections.display_ranges(cx),
 7663            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7664        );
 7665    });
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671    let mut cx = EditorTestContext::new(cx).await;
 7672
 7673    #[track_caller]
 7674    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7675        cx.set_state(initial_state);
 7676        cx.update_editor(|e, window, cx| {
 7677            e.split_selection_into_lines(&Default::default(), window, cx)
 7678        });
 7679        cx.assert_editor_state(expected_state);
 7680    }
 7681
 7682    // Selection starts and ends at the middle of lines, left-to-right
 7683    test(
 7684        &mut cx,
 7685        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7686        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7687    );
 7688    // Same thing, right-to-left
 7689    test(
 7690        &mut cx,
 7691        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7692        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7693    );
 7694
 7695    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7696    test(
 7697        &mut cx,
 7698        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7699        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7700    );
 7701    // Same thing, right-to-left
 7702    test(
 7703        &mut cx,
 7704        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7705        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7706    );
 7707
 7708    // Whole buffer, left-to-right, last line ends with newline
 7709    test(
 7710        &mut cx,
 7711        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7712        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7713    );
 7714    // Same thing, right-to-left
 7715    test(
 7716        &mut cx,
 7717        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7718        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7719    );
 7720
 7721    // Starts at the end of a line, ends at the start of another
 7722    test(
 7723        &mut cx,
 7724        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7725        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7726    );
 7727}
 7728
 7729#[gpui::test]
 7730async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7731    init_test(cx, |_| {});
 7732
 7733    let editor = cx.add_window(|window, cx| {
 7734        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7735        build_editor(buffer, window, cx)
 7736    });
 7737
 7738    // setup
 7739    _ = editor.update(cx, |editor, window, cx| {
 7740        editor.fold_creases(
 7741            vec![
 7742                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7743                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7744                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7745            ],
 7746            true,
 7747            window,
 7748            cx,
 7749        );
 7750        assert_eq!(
 7751            editor.display_text(cx),
 7752            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7753        );
 7754    });
 7755
 7756    _ = editor.update(cx, |editor, window, cx| {
 7757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7758            s.select_display_ranges([
 7759                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7760                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7761                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7762                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7763            ])
 7764        });
 7765        editor.split_selection_into_lines(&Default::default(), window, cx);
 7766        assert_eq!(
 7767            editor.display_text(cx),
 7768            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7769        );
 7770    });
 7771    EditorTestContext::for_editor(editor, cx)
 7772        .await
 7773        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7774
 7775    _ = editor.update(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7779            ])
 7780        });
 7781        editor.split_selection_into_lines(&Default::default(), window, cx);
 7782        assert_eq!(
 7783            editor.display_text(cx),
 7784            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7785        );
 7786        assert_eq!(
 7787            editor.selections.display_ranges(cx),
 7788            [
 7789                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7790                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7791                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7792                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7793                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7794                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7795                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7796            ]
 7797        );
 7798    });
 7799    EditorTestContext::for_editor(editor, cx)
 7800        .await
 7801        .assert_editor_state(
 7802            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7803        );
 7804}
 7805
 7806#[gpui::test]
 7807async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7808    init_test(cx, |_| {});
 7809
 7810    let mut cx = EditorTestContext::new(cx).await;
 7811
 7812    cx.set_state(indoc!(
 7813        r#"abc
 7814           defˇghi
 7815
 7816           jk
 7817           nlmo
 7818           "#
 7819    ));
 7820
 7821    cx.update_editor(|editor, window, cx| {
 7822        editor.add_selection_above(&Default::default(), window, cx);
 7823    });
 7824
 7825    cx.assert_editor_state(indoc!(
 7826        r#"abcˇ
 7827           defˇghi
 7828
 7829           jk
 7830           nlmo
 7831           "#
 7832    ));
 7833
 7834    cx.update_editor(|editor, window, cx| {
 7835        editor.add_selection_above(&Default::default(), window, cx);
 7836    });
 7837
 7838    cx.assert_editor_state(indoc!(
 7839        r#"abcˇ
 7840            defˇghi
 7841
 7842            jk
 7843            nlmo
 7844            "#
 7845    ));
 7846
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.add_selection_below(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_editor_state(indoc!(
 7852        r#"abc
 7853           defˇghi
 7854
 7855           jk
 7856           nlmo
 7857           "#
 7858    ));
 7859
 7860    cx.update_editor(|editor, window, cx| {
 7861        editor.undo_selection(&Default::default(), window, cx);
 7862    });
 7863
 7864    cx.assert_editor_state(indoc!(
 7865        r#"abcˇ
 7866           defˇghi
 7867
 7868           jk
 7869           nlmo
 7870           "#
 7871    ));
 7872
 7873    cx.update_editor(|editor, window, cx| {
 7874        editor.redo_selection(&Default::default(), window, cx);
 7875    });
 7876
 7877    cx.assert_editor_state(indoc!(
 7878        r#"abc
 7879           defˇghi
 7880
 7881           jk
 7882           nlmo
 7883           "#
 7884    ));
 7885
 7886    cx.update_editor(|editor, window, cx| {
 7887        editor.add_selection_below(&Default::default(), window, cx);
 7888    });
 7889
 7890    cx.assert_editor_state(indoc!(
 7891        r#"abc
 7892           defˇghi
 7893           ˇ
 7894           jk
 7895           nlmo
 7896           "#
 7897    ));
 7898
 7899    cx.update_editor(|editor, window, cx| {
 7900        editor.add_selection_below(&Default::default(), window, cx);
 7901    });
 7902
 7903    cx.assert_editor_state(indoc!(
 7904        r#"abc
 7905           defˇghi
 7906           ˇ
 7907           jkˇ
 7908           nlmo
 7909           "#
 7910    ));
 7911
 7912    cx.update_editor(|editor, window, cx| {
 7913        editor.add_selection_below(&Default::default(), window, cx);
 7914    });
 7915
 7916    cx.assert_editor_state(indoc!(
 7917        r#"abc
 7918           defˇghi
 7919           ˇ
 7920           jkˇ
 7921           nlmˇo
 7922           "#
 7923    ));
 7924
 7925    cx.update_editor(|editor, window, cx| {
 7926        editor.add_selection_below(&Default::default(), window, cx);
 7927    });
 7928
 7929    cx.assert_editor_state(indoc!(
 7930        r#"abc
 7931           defˇghi
 7932           ˇ
 7933           jkˇ
 7934           nlmˇo
 7935           ˇ"#
 7936    ));
 7937
 7938    // change selections
 7939    cx.set_state(indoc!(
 7940        r#"abc
 7941           def«ˇg»hi
 7942
 7943           jk
 7944           nlmo
 7945           "#
 7946    ));
 7947
 7948    cx.update_editor(|editor, window, cx| {
 7949        editor.add_selection_below(&Default::default(), window, cx);
 7950    });
 7951
 7952    cx.assert_editor_state(indoc!(
 7953        r#"abc
 7954           def«ˇg»hi
 7955
 7956           jk
 7957           nlm«ˇo»
 7958           "#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_below(&Default::default(), window, cx);
 7963    });
 7964
 7965    cx.assert_editor_state(indoc!(
 7966        r#"abc
 7967           def«ˇg»hi
 7968
 7969           jk
 7970           nlm«ˇo»
 7971           "#
 7972    ));
 7973
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.add_selection_above(&Default::default(), window, cx);
 7976    });
 7977
 7978    cx.assert_editor_state(indoc!(
 7979        r#"abc
 7980           def«ˇg»hi
 7981
 7982           jk
 7983           nlmo
 7984           "#
 7985    ));
 7986
 7987    cx.update_editor(|editor, window, cx| {
 7988        editor.add_selection_above(&Default::default(), window, cx);
 7989    });
 7990
 7991    cx.assert_editor_state(indoc!(
 7992        r#"abc
 7993           def«ˇg»hi
 7994
 7995           jk
 7996           nlmo
 7997           "#
 7998    ));
 7999
 8000    // Change selections again
 8001    cx.set_state(indoc!(
 8002        r#"a«bc
 8003           defgˇ»hi
 8004
 8005           jk
 8006           nlmo
 8007           "#
 8008    ));
 8009
 8010    cx.update_editor(|editor, window, cx| {
 8011        editor.add_selection_below(&Default::default(), window, cx);
 8012    });
 8013
 8014    cx.assert_editor_state(indoc!(
 8015        r#"a«bcˇ»
 8016           d«efgˇ»hi
 8017
 8018           j«kˇ»
 8019           nlmo
 8020           "#
 8021    ));
 8022
 8023    cx.update_editor(|editor, window, cx| {
 8024        editor.add_selection_below(&Default::default(), window, cx);
 8025    });
 8026    cx.assert_editor_state(indoc!(
 8027        r#"a«bcˇ»
 8028           d«efgˇ»hi
 8029
 8030           j«kˇ»
 8031           n«lmoˇ»
 8032           "#
 8033    ));
 8034    cx.update_editor(|editor, window, cx| {
 8035        editor.add_selection_above(&Default::default(), window, cx);
 8036    });
 8037
 8038    cx.assert_editor_state(indoc!(
 8039        r#"a«bcˇ»
 8040           d«efgˇ»hi
 8041
 8042           j«kˇ»
 8043           nlmo
 8044           "#
 8045    ));
 8046
 8047    // Change selections again
 8048    cx.set_state(indoc!(
 8049        r#"abc
 8050           d«ˇefghi
 8051
 8052           jk
 8053           nlm»o
 8054           "#
 8055    ));
 8056
 8057    cx.update_editor(|editor, window, cx| {
 8058        editor.add_selection_above(&Default::default(), window, cx);
 8059    });
 8060
 8061    cx.assert_editor_state(indoc!(
 8062        r#"a«ˇbc»
 8063           d«ˇef»ghi
 8064
 8065           j«ˇk»
 8066           n«ˇlm»o
 8067           "#
 8068    ));
 8069
 8070    cx.update_editor(|editor, window, cx| {
 8071        editor.add_selection_below(&Default::default(), window, cx);
 8072    });
 8073
 8074    cx.assert_editor_state(indoc!(
 8075        r#"abc
 8076           d«ˇef»ghi
 8077
 8078           j«ˇk»
 8079           n«ˇlm»o
 8080           "#
 8081    ));
 8082}
 8083
 8084#[gpui::test]
 8085async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8086    init_test(cx, |_| {});
 8087    let mut cx = EditorTestContext::new(cx).await;
 8088
 8089    cx.set_state(indoc!(
 8090        r#"line onˇe
 8091           liˇne two
 8092           line three
 8093           line four"#
 8094    ));
 8095
 8096    cx.update_editor(|editor, window, cx| {
 8097        editor.add_selection_below(&Default::default(), window, cx);
 8098    });
 8099
 8100    // test multiple cursors expand in the same direction
 8101    cx.assert_editor_state(indoc!(
 8102        r#"line onˇe
 8103           liˇne twˇo
 8104           liˇne three
 8105           line four"#
 8106    ));
 8107
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.add_selection_below(&Default::default(), window, cx);
 8110    });
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_below(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple cursors expand below overflow
 8117    cx.assert_editor_state(indoc!(
 8118        r#"line onˇe
 8119           liˇne twˇo
 8120           liˇne thˇree
 8121           liˇne foˇur"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple cursors retrieves back correctly
 8129    cx.assert_editor_state(indoc!(
 8130        r#"line onˇe
 8131           liˇne twˇo
 8132           liˇne thˇree
 8133           line four"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_above(&Default::default(), window, cx);
 8138    });
 8139
 8140    cx.update_editor(|editor, window, cx| {
 8141        editor.add_selection_above(&Default::default(), window, cx);
 8142    });
 8143
 8144    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8145    cx.assert_editor_state(indoc!(
 8146        r#"liˇne onˇe
 8147           liˇne two
 8148           line three
 8149           line four"#
 8150    ));
 8151
 8152    cx.update_editor(|editor, window, cx| {
 8153        editor.undo_selection(&Default::default(), window, cx);
 8154    });
 8155
 8156    // test undo
 8157    cx.assert_editor_state(indoc!(
 8158        r#"line onˇe
 8159           liˇne twˇo
 8160           line three
 8161           line four"#
 8162    ));
 8163
 8164    cx.update_editor(|editor, window, cx| {
 8165        editor.redo_selection(&Default::default(), window, cx);
 8166    });
 8167
 8168    // test redo
 8169    cx.assert_editor_state(indoc!(
 8170        r#"liˇne onˇe
 8171           liˇne two
 8172           line three
 8173           line four"#
 8174    ));
 8175
 8176    cx.set_state(indoc!(
 8177        r#"abcd
 8178           ef«ghˇ»
 8179           ijkl
 8180           «mˇ»nop"#
 8181    ));
 8182
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.add_selection_above(&Default::default(), window, cx);
 8185    });
 8186
 8187    // test multiple selections expand in the same direction
 8188    cx.assert_editor_state(indoc!(
 8189        r#"ab«cdˇ»
 8190           ef«ghˇ»
 8191           «iˇ»jkl
 8192           «mˇ»nop"#
 8193    ));
 8194
 8195    cx.update_editor(|editor, window, cx| {
 8196        editor.add_selection_above(&Default::default(), window, cx);
 8197    });
 8198
 8199    // test multiple selection upward overflow
 8200    cx.assert_editor_state(indoc!(
 8201        r#"ab«cdˇ»
 8202           «eˇ»f«ghˇ»
 8203           «iˇ»jkl
 8204           «mˇ»nop"#
 8205    ));
 8206
 8207    cx.update_editor(|editor, window, cx| {
 8208        editor.add_selection_below(&Default::default(), window, cx);
 8209    });
 8210
 8211    // test multiple selection retrieves back correctly
 8212    cx.assert_editor_state(indoc!(
 8213        r#"abcd
 8214           ef«ghˇ»
 8215           «iˇ»jkl
 8216           «mˇ»nop"#
 8217    ));
 8218
 8219    cx.update_editor(|editor, window, cx| {
 8220        editor.add_selection_below(&Default::default(), window, cx);
 8221    });
 8222
 8223    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8224    cx.assert_editor_state(indoc!(
 8225        r#"abcd
 8226           ef«ghˇ»
 8227           ij«klˇ»
 8228           «mˇ»nop"#
 8229    ));
 8230
 8231    cx.update_editor(|editor, window, cx| {
 8232        editor.undo_selection(&Default::default(), window, cx);
 8233    });
 8234
 8235    // test undo
 8236    cx.assert_editor_state(indoc!(
 8237        r#"abcd
 8238           ef«ghˇ»
 8239           «iˇ»jkl
 8240           «mˇ»nop"#
 8241    ));
 8242
 8243    cx.update_editor(|editor, window, cx| {
 8244        editor.redo_selection(&Default::default(), window, cx);
 8245    });
 8246
 8247    // test redo
 8248    cx.assert_editor_state(indoc!(
 8249        r#"abcd
 8250           ef«ghˇ»
 8251           ij«klˇ»
 8252           «mˇ»nop"#
 8253    ));
 8254}
 8255
 8256#[gpui::test]
 8257async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8258    init_test(cx, |_| {});
 8259    let mut cx = EditorTestContext::new(cx).await;
 8260
 8261    cx.set_state(indoc!(
 8262        r#"line onˇe
 8263           liˇne two
 8264           line three
 8265           line four"#
 8266    ));
 8267
 8268    cx.update_editor(|editor, window, cx| {
 8269        editor.add_selection_below(&Default::default(), window, cx);
 8270        editor.add_selection_below(&Default::default(), window, cx);
 8271        editor.add_selection_below(&Default::default(), window, cx);
 8272    });
 8273
 8274    // initial state with two multi cursor groups
 8275    cx.assert_editor_state(indoc!(
 8276        r#"line onˇe
 8277           liˇne twˇo
 8278           liˇne thˇree
 8279           liˇne foˇur"#
 8280    ));
 8281
 8282    // add single cursor in middle - simulate opt click
 8283    cx.update_editor(|editor, window, cx| {
 8284        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8285        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8286        editor.end_selection(window, cx);
 8287    });
 8288
 8289    cx.assert_editor_state(indoc!(
 8290        r#"line onˇe
 8291           liˇne twˇo
 8292           liˇneˇ thˇree
 8293           liˇne foˇur"#
 8294    ));
 8295
 8296    cx.update_editor(|editor, window, cx| {
 8297        editor.add_selection_above(&Default::default(), window, cx);
 8298    });
 8299
 8300    // test new added selection expands above and existing selection shrinks
 8301    cx.assert_editor_state(indoc!(
 8302        r#"line onˇe
 8303           liˇneˇ twˇo
 8304           liˇneˇ thˇree
 8305           line four"#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_above(&Default::default(), window, cx);
 8310    });
 8311
 8312    // test new added selection expands above and existing selection shrinks
 8313    cx.assert_editor_state(indoc!(
 8314        r#"lineˇ onˇe
 8315           liˇneˇ twˇo
 8316           lineˇ three
 8317           line four"#
 8318    ));
 8319
 8320    // intial state with two selection groups
 8321    cx.set_state(indoc!(
 8322        r#"abcd
 8323           ef«ghˇ»
 8324           ijkl
 8325           «mˇ»nop"#
 8326    ));
 8327
 8328    cx.update_editor(|editor, window, cx| {
 8329        editor.add_selection_above(&Default::default(), window, cx);
 8330        editor.add_selection_above(&Default::default(), window, cx);
 8331    });
 8332
 8333    cx.assert_editor_state(indoc!(
 8334        r#"ab«cdˇ»
 8335           «eˇ»f«ghˇ»
 8336           «iˇ»jkl
 8337           «mˇ»nop"#
 8338    ));
 8339
 8340    // add single selection in middle - simulate opt drag
 8341    cx.update_editor(|editor, window, cx| {
 8342        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8343        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8344        editor.update_selection(
 8345            DisplayPoint::new(DisplayRow(2), 4),
 8346            0,
 8347            gpui::Point::<f32>::default(),
 8348            window,
 8349            cx,
 8350        );
 8351        editor.end_selection(window, cx);
 8352    });
 8353
 8354    cx.assert_editor_state(indoc!(
 8355        r#"ab«cdˇ»
 8356           «eˇ»f«ghˇ»
 8357           «iˇ»jk«lˇ»
 8358           «mˇ»nop"#
 8359    ));
 8360
 8361    cx.update_editor(|editor, window, cx| {
 8362        editor.add_selection_below(&Default::default(), window, cx);
 8363    });
 8364
 8365    // test new added selection expands below, others shrinks from above
 8366    cx.assert_editor_state(indoc!(
 8367        r#"abcd
 8368           ef«ghˇ»
 8369           «iˇ»jk«lˇ»
 8370           «mˇ»no«pˇ»"#
 8371    ));
 8372}
 8373
 8374#[gpui::test]
 8375async fn test_select_next(cx: &mut TestAppContext) {
 8376    init_test(cx, |_| {});
 8377
 8378    let mut cx = EditorTestContext::new(cx).await;
 8379    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8384
 8385    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8386        .unwrap();
 8387    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8388
 8389    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8390    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8391
 8392    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8393    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8394
 8395    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8396        .unwrap();
 8397    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8398
 8399    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8400        .unwrap();
 8401    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8402
 8403    // Test selection direction should be preserved
 8404    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8405
 8406    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8407        .unwrap();
 8408    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8409}
 8410
 8411#[gpui::test]
 8412async fn test_select_all_matches(cx: &mut TestAppContext) {
 8413    init_test(cx, |_| {});
 8414
 8415    let mut cx = EditorTestContext::new(cx).await;
 8416
 8417    // Test caret-only selections
 8418    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8419    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8422
 8423    // Test left-to-right selections
 8424    cx.set_state("abc\n«abcˇ»\nabc");
 8425    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8426        .unwrap();
 8427    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8428
 8429    // Test right-to-left selections
 8430    cx.set_state("abc\n«ˇabc»\nabc");
 8431    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8434
 8435    // Test selecting whitespace with caret selection
 8436    cx.set_state("abc\nˇ   abc\nabc");
 8437    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8438        .unwrap();
 8439    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8440
 8441    // Test selecting whitespace with left-to-right selection
 8442    cx.set_state("abc\n«ˇ  »abc\nabc");
 8443    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8444        .unwrap();
 8445    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8446
 8447    // Test no matches with right-to-left selection
 8448    cx.set_state("abc\n«  ˇ»abc\nabc");
 8449    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8452
 8453    // Test with a single word and clip_at_line_ends=true (#29823)
 8454    cx.set_state("aˇbc");
 8455    cx.update_editor(|e, window, cx| {
 8456        e.set_clip_at_line_ends(true, cx);
 8457        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8458        e.set_clip_at_line_ends(false, cx);
 8459    });
 8460    cx.assert_editor_state("«abcˇ»");
 8461}
 8462
 8463#[gpui::test]
 8464async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8465    init_test(cx, |_| {});
 8466
 8467    let mut cx = EditorTestContext::new(cx).await;
 8468
 8469    let large_body_1 = "\nd".repeat(200);
 8470    let large_body_2 = "\ne".repeat(200);
 8471
 8472    cx.set_state(&format!(
 8473        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8474    ));
 8475    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8476        let scroll_position = editor.scroll_position(cx);
 8477        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8478        scroll_position
 8479    });
 8480
 8481    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8482        .unwrap();
 8483    cx.assert_editor_state(&format!(
 8484        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8485    ));
 8486    let scroll_position_after_selection =
 8487        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8488    assert_eq!(
 8489        initial_scroll_position, scroll_position_after_selection,
 8490        "Scroll position should not change after selecting all matches"
 8491    );
 8492}
 8493
 8494#[gpui::test]
 8495async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8496    init_test(cx, |_| {});
 8497
 8498    let mut cx = EditorLspTestContext::new_rust(
 8499        lsp::ServerCapabilities {
 8500            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8501            ..Default::default()
 8502        },
 8503        cx,
 8504    )
 8505    .await;
 8506
 8507    cx.set_state(indoc! {"
 8508        line 1
 8509        line 2
 8510        linˇe 3
 8511        line 4
 8512        line 5
 8513    "});
 8514
 8515    // Make an edit
 8516    cx.update_editor(|editor, window, cx| {
 8517        editor.handle_input("X", window, cx);
 8518    });
 8519
 8520    // Move cursor to a different position
 8521    cx.update_editor(|editor, window, cx| {
 8522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8523            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8524        });
 8525    });
 8526
 8527    cx.assert_editor_state(indoc! {"
 8528        line 1
 8529        line 2
 8530        linXe 3
 8531        line 4
 8532        liˇne 5
 8533    "});
 8534
 8535    cx.lsp
 8536        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8537            Ok(Some(vec![lsp::TextEdit::new(
 8538                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8539                "PREFIX ".to_string(),
 8540            )]))
 8541        });
 8542
 8543    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8544        .unwrap()
 8545        .await
 8546        .unwrap();
 8547
 8548    cx.assert_editor_state(indoc! {"
 8549        PREFIX line 1
 8550        line 2
 8551        linXe 3
 8552        line 4
 8553        liˇne 5
 8554    "});
 8555
 8556    // Undo formatting
 8557    cx.update_editor(|editor, window, cx| {
 8558        editor.undo(&Default::default(), window, cx);
 8559    });
 8560
 8561    // Verify cursor moved back to position after edit
 8562    cx.assert_editor_state(indoc! {"
 8563        line 1
 8564        line 2
 8565        linXˇe 3
 8566        line 4
 8567        line 5
 8568    "});
 8569}
 8570
 8571#[gpui::test]
 8572async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8573    init_test(cx, |_| {});
 8574
 8575    let mut cx = EditorTestContext::new(cx).await;
 8576
 8577    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8580    });
 8581
 8582    cx.set_state(indoc! {"
 8583        line 1
 8584        line 2
 8585        linˇe 3
 8586        line 4
 8587        line 5
 8588        line 6
 8589        line 7
 8590        line 8
 8591        line 9
 8592        line 10
 8593    "});
 8594
 8595    let snapshot = cx.buffer_snapshot();
 8596    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8597
 8598    cx.update(|_, cx| {
 8599        provider.update(cx, |provider, _| {
 8600            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8601                id: None,
 8602                edits: vec![(edit_position..edit_position, "X".into())],
 8603                edit_preview: None,
 8604            }))
 8605        })
 8606    });
 8607
 8608    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8611    });
 8612
 8613    cx.assert_editor_state(indoc! {"
 8614        line 1
 8615        line 2
 8616        lineXˇ 3
 8617        line 4
 8618        line 5
 8619        line 6
 8620        line 7
 8621        line 8
 8622        line 9
 8623        line 10
 8624    "});
 8625
 8626    cx.update_editor(|editor, window, cx| {
 8627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8628            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8629        });
 8630    });
 8631
 8632    cx.assert_editor_state(indoc! {"
 8633        line 1
 8634        line 2
 8635        lineX 3
 8636        line 4
 8637        line 5
 8638        line 6
 8639        line 7
 8640        line 8
 8641        line 9
 8642        liˇne 10
 8643    "});
 8644
 8645    cx.update_editor(|editor, window, cx| {
 8646        editor.undo(&Default::default(), window, cx);
 8647    });
 8648
 8649    cx.assert_editor_state(indoc! {"
 8650        line 1
 8651        line 2
 8652        lineˇ 3
 8653        line 4
 8654        line 5
 8655        line 6
 8656        line 7
 8657        line 8
 8658        line 9
 8659        line 10
 8660    "});
 8661}
 8662
 8663#[gpui::test]
 8664async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8665    init_test(cx, |_| {});
 8666
 8667    let mut cx = EditorTestContext::new(cx).await;
 8668    cx.set_state(
 8669        r#"let foo = 2;
 8670lˇet foo = 2;
 8671let fooˇ = 2;
 8672let foo = 2;
 8673let foo = ˇ2;"#,
 8674    );
 8675
 8676    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8677        .unwrap();
 8678    cx.assert_editor_state(
 8679        r#"let foo = 2;
 8680«letˇ» foo = 2;
 8681let «fooˇ» = 2;
 8682let foo = 2;
 8683let foo = «2ˇ»;"#,
 8684    );
 8685
 8686    // noop for multiple selections with different contents
 8687    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8688        .unwrap();
 8689    cx.assert_editor_state(
 8690        r#"let foo = 2;
 8691«letˇ» foo = 2;
 8692let «fooˇ» = 2;
 8693let foo = 2;
 8694let foo = «2ˇ»;"#,
 8695    );
 8696
 8697    // Test last selection direction should be preserved
 8698    cx.set_state(
 8699        r#"let foo = 2;
 8700let foo = 2;
 8701let «fooˇ» = 2;
 8702let «ˇfoo» = 2;
 8703let foo = 2;"#,
 8704    );
 8705
 8706    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8707        .unwrap();
 8708    cx.assert_editor_state(
 8709        r#"let foo = 2;
 8710let foo = 2;
 8711let «fooˇ» = 2;
 8712let «ˇfoo» = 2;
 8713let «ˇfoo» = 2;"#,
 8714    );
 8715}
 8716
 8717#[gpui::test]
 8718async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8719    init_test(cx, |_| {});
 8720
 8721    let mut cx =
 8722        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8723
 8724    cx.assert_editor_state(indoc! {"
 8725        ˇbbb
 8726        ccc
 8727
 8728        bbb
 8729        ccc
 8730        "});
 8731    cx.dispatch_action(SelectPrevious::default());
 8732    cx.assert_editor_state(indoc! {"
 8733                «bbbˇ»
 8734                ccc
 8735
 8736                bbb
 8737                ccc
 8738                "});
 8739    cx.dispatch_action(SelectPrevious::default());
 8740    cx.assert_editor_state(indoc! {"
 8741                «bbbˇ»
 8742                ccc
 8743
 8744                «bbbˇ»
 8745                ccc
 8746                "});
 8747}
 8748
 8749#[gpui::test]
 8750async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8751    init_test(cx, |_| {});
 8752
 8753    let mut cx = EditorTestContext::new(cx).await;
 8754    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8755
 8756    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8757        .unwrap();
 8758    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8759
 8760    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8761        .unwrap();
 8762    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8763
 8764    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8765    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8766
 8767    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8768    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8773
 8774    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8775        .unwrap();
 8776    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8777}
 8778
 8779#[gpui::test]
 8780async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8781    init_test(cx, |_| {});
 8782
 8783    let mut cx = EditorTestContext::new(cx).await;
 8784    cx.set_state("");
 8785
 8786    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8787        .unwrap();
 8788    cx.assert_editor_state("«aˇ»");
 8789    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8790        .unwrap();
 8791    cx.assert_editor_state("«aˇ»");
 8792}
 8793
 8794#[gpui::test]
 8795async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8796    init_test(cx, |_| {});
 8797
 8798    let mut cx = EditorTestContext::new(cx).await;
 8799    cx.set_state(
 8800        r#"let foo = 2;
 8801lˇet foo = 2;
 8802let fooˇ = 2;
 8803let foo = 2;
 8804let foo = ˇ2;"#,
 8805    );
 8806
 8807    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8808        .unwrap();
 8809    cx.assert_editor_state(
 8810        r#"let foo = 2;
 8811«letˇ» foo = 2;
 8812let «fooˇ» = 2;
 8813let foo = 2;
 8814let foo = «2ˇ»;"#,
 8815    );
 8816
 8817    // noop for multiple selections with different contents
 8818    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8819        .unwrap();
 8820    cx.assert_editor_state(
 8821        r#"let foo = 2;
 8822«letˇ» foo = 2;
 8823let «fooˇ» = 2;
 8824let foo = 2;
 8825let foo = «2ˇ»;"#,
 8826    );
 8827}
 8828
 8829#[gpui::test]
 8830async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8831    init_test(cx, |_| {});
 8832
 8833    let mut cx = EditorTestContext::new(cx).await;
 8834    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8835
 8836    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8837        .unwrap();
 8838    // selection direction is preserved
 8839    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8840
 8841    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8842        .unwrap();
 8843    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8844
 8845    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8846    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8847
 8848    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8849    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8850
 8851    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8854
 8855    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8858}
 8859
 8860#[gpui::test]
 8861async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8862    init_test(cx, |_| {});
 8863
 8864    let language = Arc::new(Language::new(
 8865        LanguageConfig::default(),
 8866        Some(tree_sitter_rust::LANGUAGE.into()),
 8867    ));
 8868
 8869    let text = r#"
 8870        use mod1::mod2::{mod3, mod4};
 8871
 8872        fn fn_1(param1: bool, param2: &str) {
 8873            let var1 = "text";
 8874        }
 8875    "#
 8876    .unindent();
 8877
 8878    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8879    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8880    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8881
 8882    editor
 8883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8884        .await;
 8885
 8886    editor.update_in(cx, |editor, window, cx| {
 8887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8888            s.select_display_ranges([
 8889                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8890                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8891                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8892            ]);
 8893        });
 8894        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8895    });
 8896    editor.update(cx, |editor, cx| {
 8897        assert_text_with_selections(
 8898            editor,
 8899            indoc! {r#"
 8900                use mod1::mod2::{mod3, «mod4ˇ»};
 8901
 8902                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8903                    let var1 = "«ˇtext»";
 8904                }
 8905            "#},
 8906            cx,
 8907        );
 8908    });
 8909
 8910    editor.update_in(cx, |editor, window, cx| {
 8911        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8912    });
 8913    editor.update(cx, |editor, cx| {
 8914        assert_text_with_selections(
 8915            editor,
 8916            indoc! {r#"
 8917                use mod1::mod2::«{mod3, mod4}ˇ»;
 8918
 8919                «ˇfn fn_1(param1: bool, param2: &str) {
 8920                    let var1 = "text";
 8921 8922            "#},
 8923            cx,
 8924        );
 8925    });
 8926
 8927    editor.update_in(cx, |editor, window, cx| {
 8928        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8929    });
 8930    assert_eq!(
 8931        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8932        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8933    );
 8934
 8935    // Trying to expand the selected syntax node one more time has no effect.
 8936    editor.update_in(cx, |editor, window, cx| {
 8937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8938    });
 8939    assert_eq!(
 8940        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8941        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8942    );
 8943
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8946    });
 8947    editor.update(cx, |editor, cx| {
 8948        assert_text_with_selections(
 8949            editor,
 8950            indoc! {r#"
 8951                use mod1::mod2::«{mod3, mod4}ˇ»;
 8952
 8953                «ˇfn fn_1(param1: bool, param2: &str) {
 8954                    let var1 = "text";
 8955 8956            "#},
 8957            cx,
 8958        );
 8959    });
 8960
 8961    editor.update_in(cx, |editor, window, cx| {
 8962        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8963    });
 8964    editor.update(cx, |editor, cx| {
 8965        assert_text_with_selections(
 8966            editor,
 8967            indoc! {r#"
 8968                use mod1::mod2::{mod3, «mod4ˇ»};
 8969
 8970                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8971                    let var1 = "«ˇtext»";
 8972                }
 8973            "#},
 8974            cx,
 8975        );
 8976    });
 8977
 8978    editor.update_in(cx, |editor, window, cx| {
 8979        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8980    });
 8981    editor.update(cx, |editor, cx| {
 8982        assert_text_with_selections(
 8983            editor,
 8984            indoc! {r#"
 8985                use mod1::mod2::{mod3, moˇd4};
 8986
 8987                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8988                    let var1 = "teˇxt";
 8989                }
 8990            "#},
 8991            cx,
 8992        );
 8993    });
 8994
 8995    // Trying to shrink the selected syntax node one more time has no effect.
 8996    editor.update_in(cx, |editor, window, cx| {
 8997        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8998    });
 8999    editor.update_in(cx, |editor, _, cx| {
 9000        assert_text_with_selections(
 9001            editor,
 9002            indoc! {r#"
 9003                use mod1::mod2::{mod3, moˇd4};
 9004
 9005                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9006                    let var1 = "teˇxt";
 9007                }
 9008            "#},
 9009            cx,
 9010        );
 9011    });
 9012
 9013    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9014    // a fold.
 9015    editor.update_in(cx, |editor, window, cx| {
 9016        editor.fold_creases(
 9017            vec![
 9018                Crease::simple(
 9019                    Point::new(0, 21)..Point::new(0, 24),
 9020                    FoldPlaceholder::test(),
 9021                ),
 9022                Crease::simple(
 9023                    Point::new(3, 20)..Point::new(3, 22),
 9024                    FoldPlaceholder::test(),
 9025                ),
 9026            ],
 9027            true,
 9028            window,
 9029            cx,
 9030        );
 9031        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9032    });
 9033    editor.update(cx, |editor, cx| {
 9034        assert_text_with_selections(
 9035            editor,
 9036            indoc! {r#"
 9037                use mod1::mod2::«{mod3, mod4}ˇ»;
 9038
 9039                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9040                    let var1 = "«ˇtext»";
 9041                }
 9042            "#},
 9043            cx,
 9044        );
 9045    });
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let language = Arc::new(Language::new(
 9053        LanguageConfig::default(),
 9054        Some(tree_sitter_rust::LANGUAGE.into()),
 9055    ));
 9056
 9057    let text = "let a = 2;";
 9058
 9059    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9060    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9061    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9062
 9063    editor
 9064        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9065        .await;
 9066
 9067    // Test case 1: Cursor at end of word
 9068    editor.update_in(cx, |editor, window, cx| {
 9069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9070            s.select_display_ranges([
 9071                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9072            ]);
 9073        });
 9074    });
 9075    editor.update(cx, |editor, cx| {
 9076        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9077    });
 9078    editor.update_in(cx, |editor, window, cx| {
 9079        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9080    });
 9081    editor.update(cx, |editor, cx| {
 9082        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9083    });
 9084    editor.update_in(cx, |editor, window, cx| {
 9085        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9086    });
 9087    editor.update(cx, |editor, cx| {
 9088        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9089    });
 9090
 9091    // Test case 2: Cursor at end of statement
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update(cx, |editor, cx| {
 9100        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9101    });
 9102    editor.update_in(cx, |editor, window, cx| {
 9103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9104    });
 9105    editor.update(cx, |editor, cx| {
 9106        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9107    });
 9108}
 9109
 9110#[gpui::test]
 9111async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9112    init_test(cx, |_| {});
 9113
 9114    let language = Arc::new(Language::new(
 9115        LanguageConfig {
 9116            name: "JavaScript".into(),
 9117            ..Default::default()
 9118        },
 9119        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9120    ));
 9121
 9122    let text = r#"
 9123        let a = {
 9124            key: "value",
 9125        };
 9126    "#
 9127    .unindent();
 9128
 9129    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9130    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9131    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9132
 9133    editor
 9134        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9135        .await;
 9136
 9137    // Test case 1: Cursor after '{'
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9140            s.select_display_ranges([
 9141                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9142            ]);
 9143        });
 9144    });
 9145    editor.update(cx, |editor, cx| {
 9146        assert_text_with_selections(
 9147            editor,
 9148            indoc! {r#"
 9149                let a = {ˇ
 9150                    key: "value",
 9151                };
 9152            "#},
 9153            cx,
 9154        );
 9155    });
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9158    });
 9159    editor.update(cx, |editor, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                let a = «ˇ{
 9164                    key: "value",
 9165                }»;
 9166            "#},
 9167            cx,
 9168        );
 9169    });
 9170
 9171    // Test case 2: Cursor after ':'
 9172    editor.update_in(cx, |editor, window, cx| {
 9173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9174            s.select_display_ranges([
 9175                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9176            ]);
 9177        });
 9178    });
 9179    editor.update(cx, |editor, cx| {
 9180        assert_text_with_selections(
 9181            editor,
 9182            indoc! {r#"
 9183                let a = {
 9184                    key:ˇ "value",
 9185                };
 9186            "#},
 9187            cx,
 9188        );
 9189    });
 9190    editor.update_in(cx, |editor, window, cx| {
 9191        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9192    });
 9193    editor.update(cx, |editor, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                let a = {
 9198                    «ˇkey: "value"»,
 9199                };
 9200            "#},
 9201            cx,
 9202        );
 9203    });
 9204    editor.update_in(cx, |editor, window, cx| {
 9205        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9206    });
 9207    editor.update(cx, |editor, cx| {
 9208        assert_text_with_selections(
 9209            editor,
 9210            indoc! {r#"
 9211                let a = «ˇ{
 9212                    key: "value",
 9213                }»;
 9214            "#},
 9215            cx,
 9216        );
 9217    });
 9218
 9219    // Test case 3: Cursor after ','
 9220    editor.update_in(cx, |editor, window, cx| {
 9221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9222            s.select_display_ranges([
 9223                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9224            ]);
 9225        });
 9226    });
 9227    editor.update(cx, |editor, cx| {
 9228        assert_text_with_selections(
 9229            editor,
 9230            indoc! {r#"
 9231                let a = {
 9232                    key: "value",ˇ
 9233                };
 9234            "#},
 9235            cx,
 9236        );
 9237    });
 9238    editor.update_in(cx, |editor, window, cx| {
 9239        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9240    });
 9241    editor.update(cx, |editor, cx| {
 9242        assert_text_with_selections(
 9243            editor,
 9244            indoc! {r#"
 9245                let a = «ˇ{
 9246                    key: "value",
 9247                }»;
 9248            "#},
 9249            cx,
 9250        );
 9251    });
 9252
 9253    // Test case 4: Cursor after ';'
 9254    editor.update_in(cx, |editor, window, cx| {
 9255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9256            s.select_display_ranges([
 9257                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9258            ]);
 9259        });
 9260    });
 9261    editor.update(cx, |editor, cx| {
 9262        assert_text_with_selections(
 9263            editor,
 9264            indoc! {r#"
 9265                let a = {
 9266                    key: "value",
 9267                };ˇ
 9268            "#},
 9269            cx,
 9270        );
 9271    });
 9272    editor.update_in(cx, |editor, window, cx| {
 9273        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9274    });
 9275    editor.update(cx, |editor, cx| {
 9276        assert_text_with_selections(
 9277            editor,
 9278            indoc! {r#"
 9279                «ˇlet a = {
 9280                    key: "value",
 9281                };
 9282                »"#},
 9283            cx,
 9284        );
 9285    });
 9286}
 9287
 9288#[gpui::test]
 9289async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9290    init_test(cx, |_| {});
 9291
 9292    let language = Arc::new(Language::new(
 9293        LanguageConfig::default(),
 9294        Some(tree_sitter_rust::LANGUAGE.into()),
 9295    ));
 9296
 9297    let text = r#"
 9298        use mod1::mod2::{mod3, mod4};
 9299
 9300        fn fn_1(param1: bool, param2: &str) {
 9301            let var1 = "hello world";
 9302        }
 9303    "#
 9304    .unindent();
 9305
 9306    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9307    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9308    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9309
 9310    editor
 9311        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9312        .await;
 9313
 9314    // Test 1: Cursor on a letter of a string word
 9315    editor.update_in(cx, |editor, window, cx| {
 9316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9317            s.select_display_ranges([
 9318                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9319            ]);
 9320        });
 9321    });
 9322    editor.update_in(cx, |editor, window, cx| {
 9323        assert_text_with_selections(
 9324            editor,
 9325            indoc! {r#"
 9326                use mod1::mod2::{mod3, mod4};
 9327
 9328                fn fn_1(param1: bool, param2: &str) {
 9329                    let var1 = "hˇello world";
 9330                }
 9331            "#},
 9332            cx,
 9333        );
 9334        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9335        assert_text_with_selections(
 9336            editor,
 9337            indoc! {r#"
 9338                use mod1::mod2::{mod3, mod4};
 9339
 9340                fn fn_1(param1: bool, param2: &str) {
 9341                    let var1 = "«ˇhello» world";
 9342                }
 9343            "#},
 9344            cx,
 9345        );
 9346    });
 9347
 9348    // Test 2: Partial selection within a word
 9349    editor.update_in(cx, |editor, window, cx| {
 9350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9351            s.select_display_ranges([
 9352                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9353            ]);
 9354        });
 9355    });
 9356    editor.update_in(cx, |editor, window, cx| {
 9357        assert_text_with_selections(
 9358            editor,
 9359            indoc! {r#"
 9360                use mod1::mod2::{mod3, mod4};
 9361
 9362                fn fn_1(param1: bool, param2: &str) {
 9363                    let var1 = "h«elˇ»lo world";
 9364                }
 9365            "#},
 9366            cx,
 9367        );
 9368        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9369        assert_text_with_selections(
 9370            editor,
 9371            indoc! {r#"
 9372                use mod1::mod2::{mod3, mod4};
 9373
 9374                fn fn_1(param1: bool, param2: &str) {
 9375                    let var1 = "«ˇhello» world";
 9376                }
 9377            "#},
 9378            cx,
 9379        );
 9380    });
 9381
 9382    // Test 3: Complete word already selected
 9383    editor.update_in(cx, |editor, window, cx| {
 9384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9385            s.select_display_ranges([
 9386                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9387            ]);
 9388        });
 9389    });
 9390    editor.update_in(cx, |editor, window, cx| {
 9391        assert_text_with_selections(
 9392            editor,
 9393            indoc! {r#"
 9394                use mod1::mod2::{mod3, mod4};
 9395
 9396                fn fn_1(param1: bool, param2: &str) {
 9397                    let var1 = "«helloˇ» world";
 9398                }
 9399            "#},
 9400            cx,
 9401        );
 9402        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9403        assert_text_with_selections(
 9404            editor,
 9405            indoc! {r#"
 9406                use mod1::mod2::{mod3, mod4};
 9407
 9408                fn fn_1(param1: bool, param2: &str) {
 9409                    let var1 = "«hello worldˇ»";
 9410                }
 9411            "#},
 9412            cx,
 9413        );
 9414    });
 9415
 9416    // Test 4: Selection spanning across words
 9417    editor.update_in(cx, |editor, window, cx| {
 9418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9419            s.select_display_ranges([
 9420                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9421            ]);
 9422        });
 9423    });
 9424    editor.update_in(cx, |editor, window, cx| {
 9425        assert_text_with_selections(
 9426            editor,
 9427            indoc! {r#"
 9428                use mod1::mod2::{mod3, mod4};
 9429
 9430                fn fn_1(param1: bool, param2: &str) {
 9431                    let var1 = "hel«lo woˇ»rld";
 9432                }
 9433            "#},
 9434            cx,
 9435        );
 9436        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9437        assert_text_with_selections(
 9438            editor,
 9439            indoc! {r#"
 9440                use mod1::mod2::{mod3, mod4};
 9441
 9442                fn fn_1(param1: bool, param2: &str) {
 9443                    let var1 = "«ˇhello world»";
 9444                }
 9445            "#},
 9446            cx,
 9447        );
 9448    });
 9449
 9450    // Test 5: Expansion beyond string
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9453        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, mod4};
 9458
 9459                fn fn_1(param1: bool, param2: &str) {
 9460                    «ˇlet var1 = "hello world";»
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465    });
 9466}
 9467
 9468#[gpui::test]
 9469async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9470    init_test(cx, |_| {});
 9471
 9472    let mut cx = EditorTestContext::new(cx).await;
 9473
 9474    let language = Arc::new(Language::new(
 9475        LanguageConfig::default(),
 9476        Some(tree_sitter_rust::LANGUAGE.into()),
 9477    ));
 9478
 9479    cx.update_buffer(|buffer, cx| {
 9480        buffer.set_language(Some(language), cx);
 9481    });
 9482
 9483    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9484    cx.update_editor(|editor, window, cx| {
 9485        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9486    });
 9487
 9488    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9489
 9490    cx.set_state(indoc! { r#"fn a() {
 9491          // what
 9492          // a
 9493          // ˇlong
 9494          // method
 9495          // I
 9496          // sure
 9497          // hope
 9498          // it
 9499          // works
 9500    }"# });
 9501
 9502    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9503    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9504    cx.update(|_, cx| {
 9505        multi_buffer.update(cx, |multi_buffer, cx| {
 9506            multi_buffer.set_excerpts_for_path(
 9507                PathKey::for_buffer(&buffer, cx),
 9508                buffer,
 9509                [Point::new(1, 0)..Point::new(1, 0)],
 9510                3,
 9511                cx,
 9512            );
 9513        });
 9514    });
 9515
 9516    let editor2 = cx.new_window_entity(|window, cx| {
 9517        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9518    });
 9519
 9520    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9521    cx.update_editor(|editor, window, cx| {
 9522        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9523            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9524        })
 9525    });
 9526
 9527    cx.assert_editor_state(indoc! { "
 9528        fn a() {
 9529              // what
 9530              // a
 9531        ˇ      // long
 9532              // method"});
 9533
 9534    cx.update_editor(|editor, window, cx| {
 9535        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9536    });
 9537
 9538    // Although we could potentially make the action work when the syntax node
 9539    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9540    // did. Maybe we could also expand the excerpt to contain the range?
 9541    cx.assert_editor_state(indoc! { "
 9542        fn a() {
 9543              // what
 9544              // a
 9545        ˇ      // long
 9546              // method"});
 9547}
 9548
 9549#[gpui::test]
 9550async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9551    init_test(cx, |_| {});
 9552
 9553    let base_text = r#"
 9554        impl A {
 9555            // this is an uncommitted comment
 9556
 9557            fn b() {
 9558                c();
 9559            }
 9560
 9561            // this is another uncommitted comment
 9562
 9563            fn d() {
 9564                // e
 9565                // f
 9566            }
 9567        }
 9568
 9569        fn g() {
 9570            // h
 9571        }
 9572    "#
 9573    .unindent();
 9574
 9575    let text = r#"
 9576        ˇimpl A {
 9577
 9578            fn b() {
 9579                c();
 9580            }
 9581
 9582            fn d() {
 9583                // e
 9584                // f
 9585            }
 9586        }
 9587
 9588        fn g() {
 9589            // h
 9590        }
 9591    "#
 9592    .unindent();
 9593
 9594    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9595    cx.set_state(&text);
 9596    cx.set_head_text(&base_text);
 9597    cx.update_editor(|editor, window, cx| {
 9598        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9599    });
 9600
 9601    cx.assert_state_with_diff(
 9602        "
 9603        ˇimpl A {
 9604      -     // this is an uncommitted comment
 9605
 9606            fn b() {
 9607                c();
 9608            }
 9609
 9610      -     // this is another uncommitted comment
 9611      -
 9612            fn d() {
 9613                // e
 9614                // f
 9615            }
 9616        }
 9617
 9618        fn g() {
 9619            // h
 9620        }
 9621    "
 9622        .unindent(),
 9623    );
 9624
 9625    let expected_display_text = "
 9626        impl A {
 9627            // this is an uncommitted comment
 9628
 9629            fn b() {
 9630 9631            }
 9632
 9633            // this is another uncommitted comment
 9634
 9635            fn d() {
 9636 9637            }
 9638        }
 9639
 9640        fn g() {
 9641 9642        }
 9643        "
 9644    .unindent();
 9645
 9646    cx.update_editor(|editor, window, cx| {
 9647        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9648        assert_eq!(editor.display_text(cx), expected_display_text);
 9649    });
 9650}
 9651
 9652#[gpui::test]
 9653async fn test_autoindent(cx: &mut TestAppContext) {
 9654    init_test(cx, |_| {});
 9655
 9656    let language = Arc::new(
 9657        Language::new(
 9658            LanguageConfig {
 9659                brackets: BracketPairConfig {
 9660                    pairs: vec![
 9661                        BracketPair {
 9662                            start: "{".to_string(),
 9663                            end: "}".to_string(),
 9664                            close: false,
 9665                            surround: false,
 9666                            newline: true,
 9667                        },
 9668                        BracketPair {
 9669                            start: "(".to_string(),
 9670                            end: ")".to_string(),
 9671                            close: false,
 9672                            surround: false,
 9673                            newline: true,
 9674                        },
 9675                    ],
 9676                    ..Default::default()
 9677                },
 9678                ..Default::default()
 9679            },
 9680            Some(tree_sitter_rust::LANGUAGE.into()),
 9681        )
 9682        .with_indents_query(
 9683            r#"
 9684                (_ "(" ")" @end) @indent
 9685                (_ "{" "}" @end) @indent
 9686            "#,
 9687        )
 9688        .unwrap(),
 9689    );
 9690
 9691    let text = "fn a() {}";
 9692
 9693    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9694    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9695    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9696    editor
 9697        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9698        .await;
 9699
 9700    editor.update_in(cx, |editor, window, cx| {
 9701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9702            s.select_ranges([5..5, 8..8, 9..9])
 9703        });
 9704        editor.newline(&Newline, window, cx);
 9705        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9706        assert_eq!(
 9707            editor.selections.ranges(&editor.display_snapshot(cx)),
 9708            &[
 9709                Point::new(1, 4)..Point::new(1, 4),
 9710                Point::new(3, 4)..Point::new(3, 4),
 9711                Point::new(5, 0)..Point::new(5, 0)
 9712            ]
 9713        );
 9714    });
 9715}
 9716
 9717#[gpui::test]
 9718async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9719    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9720
 9721    let language = Arc::new(
 9722        Language::new(
 9723            LanguageConfig {
 9724                brackets: BracketPairConfig {
 9725                    pairs: vec![
 9726                        BracketPair {
 9727                            start: "{".to_string(),
 9728                            end: "}".to_string(),
 9729                            close: false,
 9730                            surround: false,
 9731                            newline: true,
 9732                        },
 9733                        BracketPair {
 9734                            start: "(".to_string(),
 9735                            end: ")".to_string(),
 9736                            close: false,
 9737                            surround: false,
 9738                            newline: true,
 9739                        },
 9740                    ],
 9741                    ..Default::default()
 9742                },
 9743                ..Default::default()
 9744            },
 9745            Some(tree_sitter_rust::LANGUAGE.into()),
 9746        )
 9747        .with_indents_query(
 9748            r#"
 9749                (_ "(" ")" @end) @indent
 9750                (_ "{" "}" @end) @indent
 9751            "#,
 9752        )
 9753        .unwrap(),
 9754    );
 9755
 9756    let text = "fn a() {}";
 9757
 9758    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9759    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9760    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9761    editor
 9762        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9763        .await;
 9764
 9765    editor.update_in(cx, |editor, window, cx| {
 9766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9767            s.select_ranges([5..5, 8..8, 9..9])
 9768        });
 9769        editor.newline(&Newline, window, cx);
 9770        assert_eq!(
 9771            editor.text(cx),
 9772            indoc!(
 9773                "
 9774                fn a(
 9775
 9776                ) {
 9777
 9778                }
 9779                "
 9780            )
 9781        );
 9782        assert_eq!(
 9783            editor.selections.ranges(&editor.display_snapshot(cx)),
 9784            &[
 9785                Point::new(1, 0)..Point::new(1, 0),
 9786                Point::new(3, 0)..Point::new(3, 0),
 9787                Point::new(5, 0)..Point::new(5, 0)
 9788            ]
 9789        );
 9790    });
 9791}
 9792
 9793#[gpui::test]
 9794async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9795    init_test(cx, |settings| {
 9796        settings.defaults.auto_indent = Some(true);
 9797        settings.languages.0.insert(
 9798            "python".into(),
 9799            LanguageSettingsContent {
 9800                auto_indent: Some(false),
 9801                ..Default::default()
 9802            },
 9803        );
 9804    });
 9805
 9806    let mut cx = EditorTestContext::new(cx).await;
 9807
 9808    let injected_language = Arc::new(
 9809        Language::new(
 9810            LanguageConfig {
 9811                brackets: BracketPairConfig {
 9812                    pairs: vec![
 9813                        BracketPair {
 9814                            start: "{".to_string(),
 9815                            end: "}".to_string(),
 9816                            close: false,
 9817                            surround: false,
 9818                            newline: true,
 9819                        },
 9820                        BracketPair {
 9821                            start: "(".to_string(),
 9822                            end: ")".to_string(),
 9823                            close: true,
 9824                            surround: false,
 9825                            newline: true,
 9826                        },
 9827                    ],
 9828                    ..Default::default()
 9829                },
 9830                name: "python".into(),
 9831                ..Default::default()
 9832            },
 9833            Some(tree_sitter_python::LANGUAGE.into()),
 9834        )
 9835        .with_indents_query(
 9836            r#"
 9837                (_ "(" ")" @end) @indent
 9838                (_ "{" "}" @end) @indent
 9839            "#,
 9840        )
 9841        .unwrap(),
 9842    );
 9843
 9844    let language = Arc::new(
 9845        Language::new(
 9846            LanguageConfig {
 9847                brackets: BracketPairConfig {
 9848                    pairs: vec![
 9849                        BracketPair {
 9850                            start: "{".to_string(),
 9851                            end: "}".to_string(),
 9852                            close: false,
 9853                            surround: false,
 9854                            newline: true,
 9855                        },
 9856                        BracketPair {
 9857                            start: "(".to_string(),
 9858                            end: ")".to_string(),
 9859                            close: true,
 9860                            surround: false,
 9861                            newline: true,
 9862                        },
 9863                    ],
 9864                    ..Default::default()
 9865                },
 9866                name: LanguageName::new("rust"),
 9867                ..Default::default()
 9868            },
 9869            Some(tree_sitter_rust::LANGUAGE.into()),
 9870        )
 9871        .with_indents_query(
 9872            r#"
 9873                (_ "(" ")" @end) @indent
 9874                (_ "{" "}" @end) @indent
 9875            "#,
 9876        )
 9877        .unwrap()
 9878        .with_injection_query(
 9879            r#"
 9880            (macro_invocation
 9881                macro: (identifier) @_macro_name
 9882                (token_tree) @injection.content
 9883                (#set! injection.language "python"))
 9884           "#,
 9885        )
 9886        .unwrap(),
 9887    );
 9888
 9889    cx.language_registry().add(injected_language);
 9890    cx.language_registry().add(language.clone());
 9891
 9892    cx.update_buffer(|buffer, cx| {
 9893        buffer.set_language(Some(language), cx);
 9894    });
 9895
 9896    cx.set_state(r#"struct A {ˇ}"#);
 9897
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.newline(&Default::default(), window, cx);
 9900    });
 9901
 9902    cx.assert_editor_state(indoc!(
 9903        "struct A {
 9904            ˇ
 9905        }"
 9906    ));
 9907
 9908    cx.set_state(r#"select_biased!(ˇ)"#);
 9909
 9910    cx.update_editor(|editor, window, cx| {
 9911        editor.newline(&Default::default(), window, cx);
 9912        editor.handle_input("def ", window, cx);
 9913        editor.handle_input("(", window, cx);
 9914        editor.newline(&Default::default(), window, cx);
 9915        editor.handle_input("a", window, cx);
 9916    });
 9917
 9918    cx.assert_editor_state(indoc!(
 9919        "select_biased!(
 9920        def (
 9921 9922        )
 9923        )"
 9924    ));
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9929    init_test(cx, |_| {});
 9930
 9931    {
 9932        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9933        cx.set_state(indoc! {"
 9934            impl A {
 9935
 9936                fn b() {}
 9937
 9938            «fn c() {
 9939
 9940            }ˇ»
 9941            }
 9942        "});
 9943
 9944        cx.update_editor(|editor, window, cx| {
 9945            editor.autoindent(&Default::default(), window, cx);
 9946        });
 9947
 9948        cx.assert_editor_state(indoc! {"
 9949            impl A {
 9950
 9951                fn b() {}
 9952
 9953                «fn c() {
 9954
 9955                }ˇ»
 9956            }
 9957        "});
 9958    }
 9959
 9960    {
 9961        let mut cx = EditorTestContext::new_multibuffer(
 9962            cx,
 9963            [indoc! { "
 9964                impl A {
 9965                «
 9966                // a
 9967                fn b(){}
 9968                »
 9969                «
 9970                    }
 9971                    fn c(){}
 9972                »
 9973            "}],
 9974        );
 9975
 9976        let buffer = cx.update_editor(|editor, _, cx| {
 9977            let buffer = editor.buffer().update(cx, |buffer, _| {
 9978                buffer.all_buffers().iter().next().unwrap().clone()
 9979            });
 9980            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9981            buffer
 9982        });
 9983
 9984        cx.run_until_parked();
 9985        cx.update_editor(|editor, window, cx| {
 9986            editor.select_all(&Default::default(), window, cx);
 9987            editor.autoindent(&Default::default(), window, cx)
 9988        });
 9989        cx.run_until_parked();
 9990
 9991        cx.update(|_, cx| {
 9992            assert_eq!(
 9993                buffer.read(cx).text(),
 9994                indoc! { "
 9995                    impl A {
 9996
 9997                        // a
 9998                        fn b(){}
 9999
10000
10001                    }
10002                    fn c(){}
10003
10004                " }
10005            )
10006        });
10007    }
10008}
10009
10010#[gpui::test]
10011async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10012    init_test(cx, |_| {});
10013
10014    let mut cx = EditorTestContext::new(cx).await;
10015
10016    let language = Arc::new(Language::new(
10017        LanguageConfig {
10018            brackets: BracketPairConfig {
10019                pairs: vec![
10020                    BracketPair {
10021                        start: "{".to_string(),
10022                        end: "}".to_string(),
10023                        close: true,
10024                        surround: true,
10025                        newline: true,
10026                    },
10027                    BracketPair {
10028                        start: "(".to_string(),
10029                        end: ")".to_string(),
10030                        close: true,
10031                        surround: true,
10032                        newline: true,
10033                    },
10034                    BracketPair {
10035                        start: "/*".to_string(),
10036                        end: " */".to_string(),
10037                        close: true,
10038                        surround: true,
10039                        newline: true,
10040                    },
10041                    BracketPair {
10042                        start: "[".to_string(),
10043                        end: "]".to_string(),
10044                        close: false,
10045                        surround: false,
10046                        newline: true,
10047                    },
10048                    BracketPair {
10049                        start: "\"".to_string(),
10050                        end: "\"".to_string(),
10051                        close: true,
10052                        surround: true,
10053                        newline: false,
10054                    },
10055                    BracketPair {
10056                        start: "<".to_string(),
10057                        end: ">".to_string(),
10058                        close: false,
10059                        surround: true,
10060                        newline: true,
10061                    },
10062                ],
10063                ..Default::default()
10064            },
10065            autoclose_before: "})]".to_string(),
10066            ..Default::default()
10067        },
10068        Some(tree_sitter_rust::LANGUAGE.into()),
10069    ));
10070
10071    cx.language_registry().add(language.clone());
10072    cx.update_buffer(|buffer, cx| {
10073        buffer.set_language(Some(language), cx);
10074    });
10075
10076    cx.set_state(
10077        &r#"
10078            🏀ˇ
10079            εˇ
10080            ❤️ˇ
10081        "#
10082        .unindent(),
10083    );
10084
10085    // autoclose multiple nested brackets at multiple cursors
10086    cx.update_editor(|editor, window, cx| {
10087        editor.handle_input("{", window, cx);
10088        editor.handle_input("{", window, cx);
10089        editor.handle_input("{", window, cx);
10090    });
10091    cx.assert_editor_state(
10092        &"
10093            🏀{{{ˇ}}}
10094            ε{{{ˇ}}}
10095            ❤️{{{ˇ}}}
10096        "
10097        .unindent(),
10098    );
10099
10100    // insert a different closing bracket
10101    cx.update_editor(|editor, window, cx| {
10102        editor.handle_input(")", window, cx);
10103    });
10104    cx.assert_editor_state(
10105        &"
10106            🏀{{{)ˇ}}}
10107            ε{{{)ˇ}}}
10108            ❤️{{{)ˇ}}}
10109        "
10110        .unindent(),
10111    );
10112
10113    // skip over the auto-closed brackets when typing a closing bracket
10114    cx.update_editor(|editor, window, cx| {
10115        editor.move_right(&MoveRight, window, cx);
10116        editor.handle_input("}", window, cx);
10117        editor.handle_input("}", window, cx);
10118        editor.handle_input("}", window, cx);
10119    });
10120    cx.assert_editor_state(
10121        &"
10122            🏀{{{)}}}}ˇ
10123            ε{{{)}}}}ˇ
10124            ❤️{{{)}}}}ˇ
10125        "
10126        .unindent(),
10127    );
10128
10129    // autoclose multi-character pairs
10130    cx.set_state(
10131        &"
10132            ˇ
10133            ˇ
10134        "
10135        .unindent(),
10136    );
10137    cx.update_editor(|editor, window, cx| {
10138        editor.handle_input("/", window, cx);
10139        editor.handle_input("*", window, cx);
10140    });
10141    cx.assert_editor_state(
10142        &"
10143            /*ˇ */
10144            /*ˇ */
10145        "
10146        .unindent(),
10147    );
10148
10149    // one cursor autocloses a multi-character pair, one cursor
10150    // does not autoclose.
10151    cx.set_state(
10152        &"
1015310154            ˇ
10155        "
10156        .unindent(),
10157    );
10158    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10159    cx.assert_editor_state(
10160        &"
10161            /*ˇ */
1016210163        "
10164        .unindent(),
10165    );
10166
10167    // Don't autoclose if the next character isn't whitespace and isn't
10168    // listed in the language's "autoclose_before" section.
10169    cx.set_state("ˇa b");
10170    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10171    cx.assert_editor_state("{ˇa b");
10172
10173    // Don't autoclose if `close` is false for the bracket pair
10174    cx.set_state("ˇ");
10175    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10176    cx.assert_editor_state("");
10177
10178    // Surround with brackets if text is selected
10179    cx.set_state("«aˇ» b");
10180    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10181    cx.assert_editor_state("{«aˇ»} b");
10182
10183    // Autoclose when not immediately after a word character
10184    cx.set_state("a ˇ");
10185    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10186    cx.assert_editor_state("a \"ˇ\"");
10187
10188    // Autoclose pair where the start and end characters are the same
10189    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10190    cx.assert_editor_state("a \"\"ˇ");
10191
10192    // Don't autoclose when immediately after a word character
10193    cx.set_state("");
10194    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10195    cx.assert_editor_state("a\"ˇ");
10196
10197    // Do autoclose when after a non-word character
10198    cx.set_state("");
10199    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10200    cx.assert_editor_state("{\"ˇ\"");
10201
10202    // Non identical pairs autoclose regardless of preceding character
10203    cx.set_state("");
10204    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10205    cx.assert_editor_state("a{ˇ}");
10206
10207    // Don't autoclose pair if autoclose is disabled
10208    cx.set_state("ˇ");
10209    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10210    cx.assert_editor_state("");
10211
10212    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10213    cx.set_state("«aˇ» b");
10214    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10215    cx.assert_editor_state("<«aˇ»> b");
10216}
10217
10218#[gpui::test]
10219async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10220    init_test(cx, |settings| {
10221        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10222    });
10223
10224    let mut cx = EditorTestContext::new(cx).await;
10225
10226    let language = Arc::new(Language::new(
10227        LanguageConfig {
10228            brackets: BracketPairConfig {
10229                pairs: vec![
10230                    BracketPair {
10231                        start: "{".to_string(),
10232                        end: "}".to_string(),
10233                        close: true,
10234                        surround: true,
10235                        newline: true,
10236                    },
10237                    BracketPair {
10238                        start: "(".to_string(),
10239                        end: ")".to_string(),
10240                        close: true,
10241                        surround: true,
10242                        newline: true,
10243                    },
10244                    BracketPair {
10245                        start: "[".to_string(),
10246                        end: "]".to_string(),
10247                        close: false,
10248                        surround: false,
10249                        newline: true,
10250                    },
10251                ],
10252                ..Default::default()
10253            },
10254            autoclose_before: "})]".to_string(),
10255            ..Default::default()
10256        },
10257        Some(tree_sitter_rust::LANGUAGE.into()),
10258    ));
10259
10260    cx.language_registry().add(language.clone());
10261    cx.update_buffer(|buffer, cx| {
10262        buffer.set_language(Some(language), cx);
10263    });
10264
10265    cx.set_state(
10266        &"
10267            ˇ
10268            ˇ
10269            ˇ
10270        "
10271        .unindent(),
10272    );
10273
10274    // ensure only matching closing brackets are skipped over
10275    cx.update_editor(|editor, window, cx| {
10276        editor.handle_input("}", window, cx);
10277        editor.move_left(&MoveLeft, window, cx);
10278        editor.handle_input(")", window, cx);
10279        editor.move_left(&MoveLeft, window, cx);
10280    });
10281    cx.assert_editor_state(
10282        &"
10283            ˇ)}
10284            ˇ)}
10285            ˇ)}
10286        "
10287        .unindent(),
10288    );
10289
10290    // skip-over closing brackets at multiple cursors
10291    cx.update_editor(|editor, window, cx| {
10292        editor.handle_input(")", window, cx);
10293        editor.handle_input("}", window, cx);
10294    });
10295    cx.assert_editor_state(
10296        &"
10297            )}ˇ
10298            )}ˇ
10299            )}ˇ
10300        "
10301        .unindent(),
10302    );
10303
10304    // ignore non-close brackets
10305    cx.update_editor(|editor, window, cx| {
10306        editor.handle_input("]", window, cx);
10307        editor.move_left(&MoveLeft, window, cx);
10308        editor.handle_input("]", window, cx);
10309    });
10310    cx.assert_editor_state(
10311        &"
10312            )}]ˇ]
10313            )}]ˇ]
10314            )}]ˇ]
10315        "
10316        .unindent(),
10317    );
10318}
10319
10320#[gpui::test]
10321async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10322    init_test(cx, |_| {});
10323
10324    let mut cx = EditorTestContext::new(cx).await;
10325
10326    let html_language = Arc::new(
10327        Language::new(
10328            LanguageConfig {
10329                name: "HTML".into(),
10330                brackets: BracketPairConfig {
10331                    pairs: vec![
10332                        BracketPair {
10333                            start: "<".into(),
10334                            end: ">".into(),
10335                            close: true,
10336                            ..Default::default()
10337                        },
10338                        BracketPair {
10339                            start: "{".into(),
10340                            end: "}".into(),
10341                            close: true,
10342                            ..Default::default()
10343                        },
10344                        BracketPair {
10345                            start: "(".into(),
10346                            end: ")".into(),
10347                            close: true,
10348                            ..Default::default()
10349                        },
10350                    ],
10351                    ..Default::default()
10352                },
10353                autoclose_before: "})]>".into(),
10354                ..Default::default()
10355            },
10356            Some(tree_sitter_html::LANGUAGE.into()),
10357        )
10358        .with_injection_query(
10359            r#"
10360            (script_element
10361                (raw_text) @injection.content
10362                (#set! injection.language "javascript"))
10363            "#,
10364        )
10365        .unwrap(),
10366    );
10367
10368    let javascript_language = Arc::new(Language::new(
10369        LanguageConfig {
10370            name: "JavaScript".into(),
10371            brackets: BracketPairConfig {
10372                pairs: vec![
10373                    BracketPair {
10374                        start: "/*".into(),
10375                        end: " */".into(),
10376                        close: true,
10377                        ..Default::default()
10378                    },
10379                    BracketPair {
10380                        start: "{".into(),
10381                        end: "}".into(),
10382                        close: true,
10383                        ..Default::default()
10384                    },
10385                    BracketPair {
10386                        start: "(".into(),
10387                        end: ")".into(),
10388                        close: true,
10389                        ..Default::default()
10390                    },
10391                ],
10392                ..Default::default()
10393            },
10394            autoclose_before: "})]>".into(),
10395            ..Default::default()
10396        },
10397        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10398    ));
10399
10400    cx.language_registry().add(html_language.clone());
10401    cx.language_registry().add(javascript_language);
10402    cx.executor().run_until_parked();
10403
10404    cx.update_buffer(|buffer, cx| {
10405        buffer.set_language(Some(html_language), cx);
10406    });
10407
10408    cx.set_state(
10409        &r#"
10410            <body>ˇ
10411                <script>
10412                    var x = 1;ˇ
10413                </script>
10414            </body>ˇ
10415        "#
10416        .unindent(),
10417    );
10418
10419    // Precondition: different languages are active at different locations.
10420    cx.update_editor(|editor, window, cx| {
10421        let snapshot = editor.snapshot(window, cx);
10422        let cursors = editor
10423            .selections
10424            .ranges::<usize>(&editor.display_snapshot(cx));
10425        let languages = cursors
10426            .iter()
10427            .map(|c| snapshot.language_at(c.start).unwrap().name())
10428            .collect::<Vec<_>>();
10429        assert_eq!(
10430            languages,
10431            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10432        );
10433    });
10434
10435    // Angle brackets autoclose in HTML, but not JavaScript.
10436    cx.update_editor(|editor, window, cx| {
10437        editor.handle_input("<", window, cx);
10438        editor.handle_input("a", window, cx);
10439    });
10440    cx.assert_editor_state(
10441        &r#"
10442            <body><aˇ>
10443                <script>
10444                    var x = 1;<aˇ
10445                </script>
10446            </body><aˇ>
10447        "#
10448        .unindent(),
10449    );
10450
10451    // Curly braces and parens autoclose in both HTML and JavaScript.
10452    cx.update_editor(|editor, window, cx| {
10453        editor.handle_input(" b=", window, cx);
10454        editor.handle_input("{", window, cx);
10455        editor.handle_input("c", window, cx);
10456        editor.handle_input("(", window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body><a b={c(ˇ)}>
10461                <script>
10462                    var x = 1;<a b={c(ˇ)}
10463                </script>
10464            </body><a b={c(ˇ)}>
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Brackets that were already autoclosed are skipped.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input(")", window, cx);
10472        editor.handle_input("d", window, cx);
10473        editor.handle_input("}", window, cx);
10474    });
10475    cx.assert_editor_state(
10476        &r#"
10477            <body><a b={c()d}ˇ>
10478                <script>
10479                    var x = 1;<a b={c()d}ˇ
10480                </script>
10481            </body><a b={c()d}ˇ>
10482        "#
10483        .unindent(),
10484    );
10485    cx.update_editor(|editor, window, cx| {
10486        editor.handle_input(">", window, cx);
10487    });
10488    cx.assert_editor_state(
10489        &r#"
10490            <body><a b={c()d}>ˇ
10491                <script>
10492                    var x = 1;<a b={c()d}>ˇ
10493                </script>
10494            </body><a b={c()d}>ˇ
10495        "#
10496        .unindent(),
10497    );
10498
10499    // Reset
10500    cx.set_state(
10501        &r#"
10502            <body>ˇ
10503                <script>
10504                    var x = 1;ˇ
10505                </script>
10506            </body>ˇ
10507        "#
10508        .unindent(),
10509    );
10510
10511    cx.update_editor(|editor, window, cx| {
10512        editor.handle_input("<", window, cx);
10513    });
10514    cx.assert_editor_state(
10515        &r#"
10516            <body><ˇ>
10517                <script>
10518                    var x = 1;<ˇ
10519                </script>
10520            </body><ˇ>
10521        "#
10522        .unindent(),
10523    );
10524
10525    // When backspacing, the closing angle brackets are removed.
10526    cx.update_editor(|editor, window, cx| {
10527        editor.backspace(&Backspace, window, cx);
10528    });
10529    cx.assert_editor_state(
10530        &r#"
10531            <body>ˇ
10532                <script>
10533                    var x = 1;ˇ
10534                </script>
10535            </body>ˇ
10536        "#
10537        .unindent(),
10538    );
10539
10540    // Block comments autoclose in JavaScript, but not HTML.
10541    cx.update_editor(|editor, window, cx| {
10542        editor.handle_input("/", window, cx);
10543        editor.handle_input("*", window, cx);
10544    });
10545    cx.assert_editor_state(
10546        &r#"
10547            <body>/*ˇ
10548                <script>
10549                    var x = 1;/*ˇ */
10550                </script>
10551            </body>/*ˇ
10552        "#
10553        .unindent(),
10554    );
10555}
10556
10557#[gpui::test]
10558async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10559    init_test(cx, |_| {});
10560
10561    let mut cx = EditorTestContext::new(cx).await;
10562
10563    let rust_language = Arc::new(
10564        Language::new(
10565            LanguageConfig {
10566                name: "Rust".into(),
10567                brackets: serde_json::from_value(json!([
10568                    { "start": "{", "end": "}", "close": true, "newline": true },
10569                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10570                ]))
10571                .unwrap(),
10572                autoclose_before: "})]>".into(),
10573                ..Default::default()
10574            },
10575            Some(tree_sitter_rust::LANGUAGE.into()),
10576        )
10577        .with_override_query("(string_literal) @string")
10578        .unwrap(),
10579    );
10580
10581    cx.language_registry().add(rust_language.clone());
10582    cx.update_buffer(|buffer, cx| {
10583        buffer.set_language(Some(rust_language), cx);
10584    });
10585
10586    cx.set_state(
10587        &r#"
10588            let x = ˇ
10589        "#
10590        .unindent(),
10591    );
10592
10593    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10594    cx.update_editor(|editor, window, cx| {
10595        editor.handle_input("\"", window, cx);
10596    });
10597    cx.assert_editor_state(
10598        &r#"
10599            let x = "ˇ"
10600        "#
10601        .unindent(),
10602    );
10603
10604    // Inserting another quotation mark. The cursor moves across the existing
10605    // automatically-inserted quotation mark.
10606    cx.update_editor(|editor, window, cx| {
10607        editor.handle_input("\"", window, cx);
10608    });
10609    cx.assert_editor_state(
10610        &r#"
10611            let x = ""ˇ
10612        "#
10613        .unindent(),
10614    );
10615
10616    // Reset
10617    cx.set_state(
10618        &r#"
10619            let x = ˇ
10620        "#
10621        .unindent(),
10622    );
10623
10624    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10625    cx.update_editor(|editor, window, cx| {
10626        editor.handle_input("\"", window, cx);
10627        editor.handle_input(" ", window, cx);
10628        editor.move_left(&Default::default(), window, cx);
10629        editor.handle_input("\\", window, cx);
10630        editor.handle_input("\"", window, cx);
10631    });
10632    cx.assert_editor_state(
10633        &r#"
10634            let x = "\"ˇ "
10635        "#
10636        .unindent(),
10637    );
10638
10639    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10640    // mark. Nothing is inserted.
10641    cx.update_editor(|editor, window, cx| {
10642        editor.move_right(&Default::default(), window, cx);
10643        editor.handle_input("\"", window, cx);
10644    });
10645    cx.assert_editor_state(
10646        &r#"
10647            let x = "\" "ˇ
10648        "#
10649        .unindent(),
10650    );
10651}
10652
10653#[gpui::test]
10654async fn test_surround_with_pair(cx: &mut TestAppContext) {
10655    init_test(cx, |_| {});
10656
10657    let language = Arc::new(Language::new(
10658        LanguageConfig {
10659            brackets: BracketPairConfig {
10660                pairs: vec![
10661                    BracketPair {
10662                        start: "{".to_string(),
10663                        end: "}".to_string(),
10664                        close: true,
10665                        surround: true,
10666                        newline: true,
10667                    },
10668                    BracketPair {
10669                        start: "/* ".to_string(),
10670                        end: "*/".to_string(),
10671                        close: true,
10672                        surround: true,
10673                        ..Default::default()
10674                    },
10675                ],
10676                ..Default::default()
10677            },
10678            ..Default::default()
10679        },
10680        Some(tree_sitter_rust::LANGUAGE.into()),
10681    ));
10682
10683    let text = r#"
10684        a
10685        b
10686        c
10687    "#
10688    .unindent();
10689
10690    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10691    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10692    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10693    editor
10694        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10695        .await;
10696
10697    editor.update_in(cx, |editor, window, cx| {
10698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10699            s.select_display_ranges([
10700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10702                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10703            ])
10704        });
10705
10706        editor.handle_input("{", window, cx);
10707        editor.handle_input("{", window, cx);
10708        editor.handle_input("{", window, cx);
10709        assert_eq!(
10710            editor.text(cx),
10711            "
10712                {{{a}}}
10713                {{{b}}}
10714                {{{c}}}
10715            "
10716            .unindent()
10717        );
10718        assert_eq!(
10719            editor.selections.display_ranges(cx),
10720            [
10721                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10722                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10723                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10724            ]
10725        );
10726
10727        editor.undo(&Undo, window, cx);
10728        editor.undo(&Undo, window, cx);
10729        editor.undo(&Undo, window, cx);
10730        assert_eq!(
10731            editor.text(cx),
10732            "
10733                a
10734                b
10735                c
10736            "
10737            .unindent()
10738        );
10739        assert_eq!(
10740            editor.selections.display_ranges(cx),
10741            [
10742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10743                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10744                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10745            ]
10746        );
10747
10748        // Ensure inserting the first character of a multi-byte bracket pair
10749        // doesn't surround the selections with the bracket.
10750        editor.handle_input("/", window, cx);
10751        assert_eq!(
10752            editor.text(cx),
10753            "
10754                /
10755                /
10756                /
10757            "
10758            .unindent()
10759        );
10760        assert_eq!(
10761            editor.selections.display_ranges(cx),
10762            [
10763                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10764                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10765                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10766            ]
10767        );
10768
10769        editor.undo(&Undo, window, cx);
10770        assert_eq!(
10771            editor.text(cx),
10772            "
10773                a
10774                b
10775                c
10776            "
10777            .unindent()
10778        );
10779        assert_eq!(
10780            editor.selections.display_ranges(cx),
10781            [
10782                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10783                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10784                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10785            ]
10786        );
10787
10788        // Ensure inserting the last character of a multi-byte bracket pair
10789        // doesn't surround the selections with the bracket.
10790        editor.handle_input("*", window, cx);
10791        assert_eq!(
10792            editor.text(cx),
10793            "
10794                *
10795                *
10796                *
10797            "
10798            .unindent()
10799        );
10800        assert_eq!(
10801            editor.selections.display_ranges(cx),
10802            [
10803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10804                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10805                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10806            ]
10807        );
10808    });
10809}
10810
10811#[gpui::test]
10812async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10813    init_test(cx, |_| {});
10814
10815    let language = Arc::new(Language::new(
10816        LanguageConfig {
10817            brackets: BracketPairConfig {
10818                pairs: vec![BracketPair {
10819                    start: "{".to_string(),
10820                    end: "}".to_string(),
10821                    close: true,
10822                    surround: true,
10823                    newline: true,
10824                }],
10825                ..Default::default()
10826            },
10827            autoclose_before: "}".to_string(),
10828            ..Default::default()
10829        },
10830        Some(tree_sitter_rust::LANGUAGE.into()),
10831    ));
10832
10833    let text = r#"
10834        a
10835        b
10836        c
10837    "#
10838    .unindent();
10839
10840    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10841    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10842    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10843    editor
10844        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10845        .await;
10846
10847    editor.update_in(cx, |editor, window, cx| {
10848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10849            s.select_ranges([
10850                Point::new(0, 1)..Point::new(0, 1),
10851                Point::new(1, 1)..Point::new(1, 1),
10852                Point::new(2, 1)..Point::new(2, 1),
10853            ])
10854        });
10855
10856        editor.handle_input("{", window, cx);
10857        editor.handle_input("{", window, cx);
10858        editor.handle_input("_", window, cx);
10859        assert_eq!(
10860            editor.text(cx),
10861            "
10862                a{{_}}
10863                b{{_}}
10864                c{{_}}
10865            "
10866            .unindent()
10867        );
10868        assert_eq!(
10869            editor
10870                .selections
10871                .ranges::<Point>(&editor.display_snapshot(cx)),
10872            [
10873                Point::new(0, 4)..Point::new(0, 4),
10874                Point::new(1, 4)..Point::new(1, 4),
10875                Point::new(2, 4)..Point::new(2, 4)
10876            ]
10877        );
10878
10879        editor.backspace(&Default::default(), window, cx);
10880        editor.backspace(&Default::default(), window, cx);
10881        assert_eq!(
10882            editor.text(cx),
10883            "
10884                a{}
10885                b{}
10886                c{}
10887            "
10888            .unindent()
10889        );
10890        assert_eq!(
10891            editor
10892                .selections
10893                .ranges::<Point>(&editor.display_snapshot(cx)),
10894            [
10895                Point::new(0, 2)..Point::new(0, 2),
10896                Point::new(1, 2)..Point::new(1, 2),
10897                Point::new(2, 2)..Point::new(2, 2)
10898            ]
10899        );
10900
10901        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10902        assert_eq!(
10903            editor.text(cx),
10904            "
10905                a
10906                b
10907                c
10908            "
10909            .unindent()
10910        );
10911        assert_eq!(
10912            editor
10913                .selections
10914                .ranges::<Point>(&editor.display_snapshot(cx)),
10915            [
10916                Point::new(0, 1)..Point::new(0, 1),
10917                Point::new(1, 1)..Point::new(1, 1),
10918                Point::new(2, 1)..Point::new(2, 1)
10919            ]
10920        );
10921    });
10922}
10923
10924#[gpui::test]
10925async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10926    init_test(cx, |settings| {
10927        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10928    });
10929
10930    let mut cx = EditorTestContext::new(cx).await;
10931
10932    let language = Arc::new(Language::new(
10933        LanguageConfig {
10934            brackets: BracketPairConfig {
10935                pairs: vec![
10936                    BracketPair {
10937                        start: "{".to_string(),
10938                        end: "}".to_string(),
10939                        close: true,
10940                        surround: true,
10941                        newline: true,
10942                    },
10943                    BracketPair {
10944                        start: "(".to_string(),
10945                        end: ")".to_string(),
10946                        close: true,
10947                        surround: true,
10948                        newline: true,
10949                    },
10950                    BracketPair {
10951                        start: "[".to_string(),
10952                        end: "]".to_string(),
10953                        close: false,
10954                        surround: true,
10955                        newline: true,
10956                    },
10957                ],
10958                ..Default::default()
10959            },
10960            autoclose_before: "})]".to_string(),
10961            ..Default::default()
10962        },
10963        Some(tree_sitter_rust::LANGUAGE.into()),
10964    ));
10965
10966    cx.language_registry().add(language.clone());
10967    cx.update_buffer(|buffer, cx| {
10968        buffer.set_language(Some(language), cx);
10969    });
10970
10971    cx.set_state(
10972        &"
10973            {(ˇ)}
10974            [[ˇ]]
10975            {(ˇ)}
10976        "
10977        .unindent(),
10978    );
10979
10980    cx.update_editor(|editor, window, cx| {
10981        editor.backspace(&Default::default(), window, cx);
10982        editor.backspace(&Default::default(), window, cx);
10983    });
10984
10985    cx.assert_editor_state(
10986        &"
10987            ˇ
10988            ˇ]]
10989            ˇ
10990        "
10991        .unindent(),
10992    );
10993
10994    cx.update_editor(|editor, window, cx| {
10995        editor.handle_input("{", window, cx);
10996        editor.handle_input("{", window, cx);
10997        editor.move_right(&MoveRight, window, cx);
10998        editor.move_right(&MoveRight, window, cx);
10999        editor.move_left(&MoveLeft, window, cx);
11000        editor.move_left(&MoveLeft, window, cx);
11001        editor.backspace(&Default::default(), window, cx);
11002    });
11003
11004    cx.assert_editor_state(
11005        &"
11006            {ˇ}
11007            {ˇ}]]
11008            {ˇ}
11009        "
11010        .unindent(),
11011    );
11012
11013    cx.update_editor(|editor, window, cx| {
11014        editor.backspace(&Default::default(), window, cx);
11015    });
11016
11017    cx.assert_editor_state(
11018        &"
11019            ˇ
11020            ˇ]]
11021            ˇ
11022        "
11023        .unindent(),
11024    );
11025}
11026
11027#[gpui::test]
11028async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11029    init_test(cx, |_| {});
11030
11031    let language = Arc::new(Language::new(
11032        LanguageConfig::default(),
11033        Some(tree_sitter_rust::LANGUAGE.into()),
11034    ));
11035
11036    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11037    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11038    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11039    editor
11040        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11041        .await;
11042
11043    editor.update_in(cx, |editor, window, cx| {
11044        editor.set_auto_replace_emoji_shortcode(true);
11045
11046        editor.handle_input("Hello ", window, cx);
11047        editor.handle_input(":wave", window, cx);
11048        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11049
11050        editor.handle_input(":", window, cx);
11051        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11052
11053        editor.handle_input(" :smile", window, cx);
11054        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11055
11056        editor.handle_input(":", window, cx);
11057        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11058
11059        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11060        editor.handle_input(":wave", window, cx);
11061        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11062
11063        editor.handle_input(":", window, cx);
11064        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11065
11066        editor.handle_input(":1", window, cx);
11067        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11068
11069        editor.handle_input(":", window, cx);
11070        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11071
11072        // Ensure shortcode does not get replaced when it is part of a word
11073        editor.handle_input(" Test:wave", window, cx);
11074        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11075
11076        editor.handle_input(":", window, cx);
11077        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11078
11079        editor.set_auto_replace_emoji_shortcode(false);
11080
11081        // Ensure shortcode does not get replaced when auto replace is off
11082        editor.handle_input(" :wave", window, cx);
11083        assert_eq!(
11084            editor.text(cx),
11085            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11086        );
11087
11088        editor.handle_input(":", window, cx);
11089        assert_eq!(
11090            editor.text(cx),
11091            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11092        );
11093    });
11094}
11095
11096#[gpui::test]
11097async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11098    init_test(cx, |_| {});
11099
11100    let (text, insertion_ranges) = marked_text_ranges(
11101        indoc! {"
11102            ˇ
11103        "},
11104        false,
11105    );
11106
11107    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11108    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11109
11110    _ = editor.update_in(cx, |editor, window, cx| {
11111        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11112
11113        editor
11114            .insert_snippet(&insertion_ranges, snippet, window, cx)
11115            .unwrap();
11116
11117        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11118            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11119            assert_eq!(editor.text(cx), expected_text);
11120            assert_eq!(
11121                editor
11122                    .selections
11123                    .ranges::<usize>(&editor.display_snapshot(cx)),
11124                selection_ranges
11125            );
11126        }
11127
11128        assert(
11129            editor,
11130            cx,
11131            indoc! {"
11132            type «» =•
11133            "},
11134        );
11135
11136        assert!(editor.context_menu_visible(), "There should be a matches");
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11142    init_test(cx, |_| {});
11143
11144    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11145        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11146        assert_eq!(editor.text(cx), expected_text);
11147        assert_eq!(
11148            editor
11149                .selections
11150                .ranges::<usize>(&editor.display_snapshot(cx)),
11151            selection_ranges
11152        );
11153    }
11154
11155    let (text, insertion_ranges) = marked_text_ranges(
11156        indoc! {"
11157            ˇ
11158        "},
11159        false,
11160    );
11161
11162    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11164
11165    _ = editor.update_in(cx, |editor, window, cx| {
11166        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11167
11168        editor
11169            .insert_snippet(&insertion_ranges, snippet, window, cx)
11170            .unwrap();
11171
11172        assert_state(
11173            editor,
11174            cx,
11175            indoc! {"
11176            type «» = ;•
11177            "},
11178        );
11179
11180        assert!(
11181            editor.context_menu_visible(),
11182            "Context menu should be visible for placeholder choices"
11183        );
11184
11185        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11186
11187        assert_state(
11188            editor,
11189            cx,
11190            indoc! {"
11191            type  = «»;•
11192            "},
11193        );
11194
11195        assert!(
11196            !editor.context_menu_visible(),
11197            "Context menu should be hidden after moving to next tabstop"
11198        );
11199
11200        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11201
11202        assert_state(
11203            editor,
11204            cx,
11205            indoc! {"
11206            type  = ; ˇ
11207            "},
11208        );
11209
11210        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11211
11212        assert_state(
11213            editor,
11214            cx,
11215            indoc! {"
11216            type  = ; ˇ
11217            "},
11218        );
11219    });
11220
11221    _ = editor.update_in(cx, |editor, window, cx| {
11222        editor.select_all(&SelectAll, window, cx);
11223        editor.backspace(&Backspace, window, cx);
11224
11225        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11226        let insertion_ranges = editor
11227            .selections
11228            .all(&editor.display_snapshot(cx))
11229            .iter()
11230            .map(|s| s.range())
11231            .collect::<Vec<_>>();
11232
11233        editor
11234            .insert_snippet(&insertion_ranges, snippet, window, cx)
11235            .unwrap();
11236
11237        assert_state(editor, cx, "fn «» = value;•");
11238
11239        assert!(
11240            editor.context_menu_visible(),
11241            "Context menu should be visible for placeholder choices"
11242        );
11243
11244        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11245
11246        assert_state(editor, cx, "fn  = «valueˇ»;•");
11247
11248        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11249
11250        assert_state(editor, cx, "fn «» = value;•");
11251
11252        assert!(
11253            editor.context_menu_visible(),
11254            "Context menu should be visible again after returning to first tabstop"
11255        );
11256
11257        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11258
11259        assert_state(editor, cx, "fn «» = value;•");
11260    });
11261}
11262
11263#[gpui::test]
11264async fn test_snippets(cx: &mut TestAppContext) {
11265    init_test(cx, |_| {});
11266
11267    let mut cx = EditorTestContext::new(cx).await;
11268
11269    cx.set_state(indoc! {"
11270        a.ˇ b
11271        a.ˇ b
11272        a.ˇ b
11273    "});
11274
11275    cx.update_editor(|editor, window, cx| {
11276        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11277        let insertion_ranges = editor
11278            .selections
11279            .all(&editor.display_snapshot(cx))
11280            .iter()
11281            .map(|s| s.range())
11282            .collect::<Vec<_>>();
11283        editor
11284            .insert_snippet(&insertion_ranges, snippet, window, cx)
11285            .unwrap();
11286    });
11287
11288    cx.assert_editor_state(indoc! {"
11289        a.f(«oneˇ», two, «threeˇ») b
11290        a.f(«oneˇ», two, «threeˇ») b
11291        a.f(«oneˇ», two, «threeˇ») b
11292    "});
11293
11294    // Can't move earlier than the first tab stop
11295    cx.update_editor(|editor, window, cx| {
11296        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11297    });
11298    cx.assert_editor_state(indoc! {"
11299        a.f(«oneˇ», two, «threeˇ») b
11300        a.f(«oneˇ», two, «threeˇ») b
11301        a.f(«oneˇ», two, «threeˇ») b
11302    "});
11303
11304    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11305    cx.assert_editor_state(indoc! {"
11306        a.f(one, «twoˇ», three) b
11307        a.f(one, «twoˇ», three) b
11308        a.f(one, «twoˇ», three) b
11309    "});
11310
11311    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11312    cx.assert_editor_state(indoc! {"
11313        a.f(«oneˇ», two, «threeˇ») b
11314        a.f(«oneˇ», two, «threeˇ») b
11315        a.f(«oneˇ», two, «threeˇ») b
11316    "});
11317
11318    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11319    cx.assert_editor_state(indoc! {"
11320        a.f(one, «twoˇ», three) b
11321        a.f(one, «twoˇ», three) b
11322        a.f(one, «twoˇ», three) b
11323    "});
11324    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11325    cx.assert_editor_state(indoc! {"
11326        a.f(one, two, three)ˇ b
11327        a.f(one, two, three)ˇ b
11328        a.f(one, two, three)ˇ b
11329    "});
11330
11331    // As soon as the last tab stop is reached, snippet state is gone
11332    cx.update_editor(|editor, window, cx| {
11333        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11334    });
11335    cx.assert_editor_state(indoc! {"
11336        a.f(one, two, three)ˇ b
11337        a.f(one, two, three)ˇ b
11338        a.f(one, two, three)ˇ b
11339    "});
11340}
11341
11342#[gpui::test]
11343async fn test_snippet_indentation(cx: &mut TestAppContext) {
11344    init_test(cx, |_| {});
11345
11346    let mut cx = EditorTestContext::new(cx).await;
11347
11348    cx.update_editor(|editor, window, cx| {
11349        let snippet = Snippet::parse(indoc! {"
11350            /*
11351             * Multiline comment with leading indentation
11352             *
11353             * $1
11354             */
11355            $0"})
11356        .unwrap();
11357        let insertion_ranges = editor
11358            .selections
11359            .all(&editor.display_snapshot(cx))
11360            .iter()
11361            .map(|s| s.range())
11362            .collect::<Vec<_>>();
11363        editor
11364            .insert_snippet(&insertion_ranges, snippet, window, cx)
11365            .unwrap();
11366    });
11367
11368    cx.assert_editor_state(indoc! {"
11369        /*
11370         * Multiline comment with leading indentation
11371         *
11372         * ˇ
11373         */
11374    "});
11375
11376    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11377    cx.assert_editor_state(indoc! {"
11378        /*
11379         * Multiline comment with leading indentation
11380         *
11381         *•
11382         */
11383        ˇ"});
11384}
11385
11386#[gpui::test]
11387async fn test_document_format_during_save(cx: &mut TestAppContext) {
11388    init_test(cx, |_| {});
11389
11390    let fs = FakeFs::new(cx.executor());
11391    fs.insert_file(path!("/file.rs"), Default::default()).await;
11392
11393    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11394
11395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11396    language_registry.add(rust_lang());
11397    let mut fake_servers = language_registry.register_fake_lsp(
11398        "Rust",
11399        FakeLspAdapter {
11400            capabilities: lsp::ServerCapabilities {
11401                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11402                ..Default::default()
11403            },
11404            ..Default::default()
11405        },
11406    );
11407
11408    let buffer = project
11409        .update(cx, |project, cx| {
11410            project.open_local_buffer(path!("/file.rs"), cx)
11411        })
11412        .await
11413        .unwrap();
11414
11415    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11416    let (editor, cx) = cx.add_window_view(|window, cx| {
11417        build_editor_with_project(project.clone(), buffer, window, cx)
11418    });
11419    editor.update_in(cx, |editor, window, cx| {
11420        editor.set_text("one\ntwo\nthree\n", window, cx)
11421    });
11422    assert!(cx.read(|cx| editor.is_dirty(cx)));
11423
11424    cx.executor().start_waiting();
11425    let fake_server = fake_servers.next().await.unwrap();
11426
11427    {
11428        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11429            move |params, _| async move {
11430                assert_eq!(
11431                    params.text_document.uri,
11432                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11433                );
11434                assert_eq!(params.options.tab_size, 4);
11435                Ok(Some(vec![lsp::TextEdit::new(
11436                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11437                    ", ".to_string(),
11438                )]))
11439            },
11440        );
11441        let save = editor
11442            .update_in(cx, |editor, window, cx| {
11443                editor.save(
11444                    SaveOptions {
11445                        format: true,
11446                        autosave: false,
11447                    },
11448                    project.clone(),
11449                    window,
11450                    cx,
11451                )
11452            })
11453            .unwrap();
11454        cx.executor().start_waiting();
11455        save.await;
11456
11457        assert_eq!(
11458            editor.update(cx, |editor, cx| editor.text(cx)),
11459            "one, two\nthree\n"
11460        );
11461        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11462    }
11463
11464    {
11465        editor.update_in(cx, |editor, window, cx| {
11466            editor.set_text("one\ntwo\nthree\n", window, cx)
11467        });
11468        assert!(cx.read(|cx| editor.is_dirty(cx)));
11469
11470        // Ensure we can still save even if formatting hangs.
11471        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11472            move |params, _| async move {
11473                assert_eq!(
11474                    params.text_document.uri,
11475                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11476                );
11477                futures::future::pending::<()>().await;
11478                unreachable!()
11479            },
11480        );
11481        let save = editor
11482            .update_in(cx, |editor, window, cx| {
11483                editor.save(
11484                    SaveOptions {
11485                        format: true,
11486                        autosave: false,
11487                    },
11488                    project.clone(),
11489                    window,
11490                    cx,
11491                )
11492            })
11493            .unwrap();
11494        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11495        cx.executor().start_waiting();
11496        save.await;
11497        assert_eq!(
11498            editor.update(cx, |editor, cx| editor.text(cx)),
11499            "one\ntwo\nthree\n"
11500        );
11501    }
11502
11503    // Set rust language override and assert overridden tabsize is sent to language server
11504    update_test_language_settings(cx, |settings| {
11505        settings.languages.0.insert(
11506            "Rust".into(),
11507            LanguageSettingsContent {
11508                tab_size: NonZeroU32::new(8),
11509                ..Default::default()
11510            },
11511        );
11512    });
11513
11514    {
11515        editor.update_in(cx, |editor, window, cx| {
11516            editor.set_text("somehting_new\n", window, cx)
11517        });
11518        assert!(cx.read(|cx| editor.is_dirty(cx)));
11519        let _formatting_request_signal = fake_server
11520            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11521                assert_eq!(
11522                    params.text_document.uri,
11523                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11524                );
11525                assert_eq!(params.options.tab_size, 8);
11526                Ok(Some(vec![]))
11527            });
11528        let save = editor
11529            .update_in(cx, |editor, window, cx| {
11530                editor.save(
11531                    SaveOptions {
11532                        format: true,
11533                        autosave: false,
11534                    },
11535                    project.clone(),
11536                    window,
11537                    cx,
11538                )
11539            })
11540            .unwrap();
11541        cx.executor().start_waiting();
11542        save.await;
11543    }
11544}
11545
11546#[gpui::test]
11547async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11548    init_test(cx, |settings| {
11549        settings.defaults.ensure_final_newline_on_save = Some(false);
11550    });
11551
11552    let fs = FakeFs::new(cx.executor());
11553    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11554
11555    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11556
11557    let buffer = project
11558        .update(cx, |project, cx| {
11559            project.open_local_buffer(path!("/file.txt"), cx)
11560        })
11561        .await
11562        .unwrap();
11563
11564    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11565    let (editor, cx) = cx.add_window_view(|window, cx| {
11566        build_editor_with_project(project.clone(), buffer, window, cx)
11567    });
11568    editor.update_in(cx, |editor, window, cx| {
11569        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11570            s.select_ranges([0..0])
11571        });
11572    });
11573    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11574
11575    editor.update_in(cx, |editor, window, cx| {
11576        editor.handle_input("\n", window, cx)
11577    });
11578    cx.run_until_parked();
11579    save(&editor, &project, cx).await;
11580    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11581
11582    editor.update_in(cx, |editor, window, cx| {
11583        editor.undo(&Default::default(), window, cx);
11584    });
11585    save(&editor, &project, cx).await;
11586    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11587
11588    editor.update_in(cx, |editor, window, cx| {
11589        editor.redo(&Default::default(), window, cx);
11590    });
11591    cx.run_until_parked();
11592    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11593
11594    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11595        let save = editor
11596            .update_in(cx, |editor, window, cx| {
11597                editor.save(
11598                    SaveOptions {
11599                        format: true,
11600                        autosave: false,
11601                    },
11602                    project.clone(),
11603                    window,
11604                    cx,
11605                )
11606            })
11607            .unwrap();
11608        cx.executor().start_waiting();
11609        save.await;
11610        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11611    }
11612}
11613
11614#[gpui::test]
11615async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11616    init_test(cx, |_| {});
11617
11618    let cols = 4;
11619    let rows = 10;
11620    let sample_text_1 = sample_text(rows, cols, 'a');
11621    assert_eq!(
11622        sample_text_1,
11623        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11624    );
11625    let sample_text_2 = sample_text(rows, cols, 'l');
11626    assert_eq!(
11627        sample_text_2,
11628        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11629    );
11630    let sample_text_3 = sample_text(rows, cols, 'v');
11631    assert_eq!(
11632        sample_text_3,
11633        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11634    );
11635
11636    let fs = FakeFs::new(cx.executor());
11637    fs.insert_tree(
11638        path!("/a"),
11639        json!({
11640            "main.rs": sample_text_1,
11641            "other.rs": sample_text_2,
11642            "lib.rs": sample_text_3,
11643        }),
11644    )
11645    .await;
11646
11647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11648    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11649    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11650
11651    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11652    language_registry.add(rust_lang());
11653    let mut fake_servers = language_registry.register_fake_lsp(
11654        "Rust",
11655        FakeLspAdapter {
11656            capabilities: lsp::ServerCapabilities {
11657                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11658                ..Default::default()
11659            },
11660            ..Default::default()
11661        },
11662    );
11663
11664    let worktree = project.update(cx, |project, cx| {
11665        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11666        assert_eq!(worktrees.len(), 1);
11667        worktrees.pop().unwrap()
11668    });
11669    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11670
11671    let buffer_1 = project
11672        .update(cx, |project, cx| {
11673            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11674        })
11675        .await
11676        .unwrap();
11677    let buffer_2 = project
11678        .update(cx, |project, cx| {
11679            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11680        })
11681        .await
11682        .unwrap();
11683    let buffer_3 = project
11684        .update(cx, |project, cx| {
11685            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11686        })
11687        .await
11688        .unwrap();
11689
11690    let multi_buffer = cx.new(|cx| {
11691        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11692        multi_buffer.push_excerpts(
11693            buffer_1.clone(),
11694            [
11695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11698            ],
11699            cx,
11700        );
11701        multi_buffer.push_excerpts(
11702            buffer_2.clone(),
11703            [
11704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11707            ],
11708            cx,
11709        );
11710        multi_buffer.push_excerpts(
11711            buffer_3.clone(),
11712            [
11713                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11714                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11715                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11716            ],
11717            cx,
11718        );
11719        multi_buffer
11720    });
11721    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11722        Editor::new(
11723            EditorMode::full(),
11724            multi_buffer,
11725            Some(project.clone()),
11726            window,
11727            cx,
11728        )
11729    });
11730
11731    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11732        editor.change_selections(
11733            SelectionEffects::scroll(Autoscroll::Next),
11734            window,
11735            cx,
11736            |s| s.select_ranges(Some(1..2)),
11737        );
11738        editor.insert("|one|two|three|", window, cx);
11739    });
11740    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11741    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11742        editor.change_selections(
11743            SelectionEffects::scroll(Autoscroll::Next),
11744            window,
11745            cx,
11746            |s| s.select_ranges(Some(60..70)),
11747        );
11748        editor.insert("|four|five|six|", window, cx);
11749    });
11750    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11751
11752    // First two buffers should be edited, but not the third one.
11753    assert_eq!(
11754        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11755        "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}",
11756    );
11757    buffer_1.update(cx, |buffer, _| {
11758        assert!(buffer.is_dirty());
11759        assert_eq!(
11760            buffer.text(),
11761            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11762        )
11763    });
11764    buffer_2.update(cx, |buffer, _| {
11765        assert!(buffer.is_dirty());
11766        assert_eq!(
11767            buffer.text(),
11768            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11769        )
11770    });
11771    buffer_3.update(cx, |buffer, _| {
11772        assert!(!buffer.is_dirty());
11773        assert_eq!(buffer.text(), sample_text_3,)
11774    });
11775    cx.executor().run_until_parked();
11776
11777    cx.executor().start_waiting();
11778    let save = multi_buffer_editor
11779        .update_in(cx, |editor, window, cx| {
11780            editor.save(
11781                SaveOptions {
11782                    format: true,
11783                    autosave: false,
11784                },
11785                project.clone(),
11786                window,
11787                cx,
11788            )
11789        })
11790        .unwrap();
11791
11792    let fake_server = fake_servers.next().await.unwrap();
11793    fake_server
11794        .server
11795        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11796            Ok(Some(vec![lsp::TextEdit::new(
11797                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11798                format!("[{} formatted]", params.text_document.uri),
11799            )]))
11800        })
11801        .detach();
11802    save.await;
11803
11804    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11805    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11806    assert_eq!(
11807        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11808        uri!(
11809            "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}"
11810        ),
11811    );
11812    buffer_1.update(cx, |buffer, _| {
11813        assert!(!buffer.is_dirty());
11814        assert_eq!(
11815            buffer.text(),
11816            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11817        )
11818    });
11819    buffer_2.update(cx, |buffer, _| {
11820        assert!(!buffer.is_dirty());
11821        assert_eq!(
11822            buffer.text(),
11823            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11824        )
11825    });
11826    buffer_3.update(cx, |buffer, _| {
11827        assert!(!buffer.is_dirty());
11828        assert_eq!(buffer.text(), sample_text_3,)
11829    });
11830}
11831
11832#[gpui::test]
11833async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11834    init_test(cx, |_| {});
11835
11836    let fs = FakeFs::new(cx.executor());
11837    fs.insert_tree(
11838        path!("/dir"),
11839        json!({
11840            "file1.rs": "fn main() { println!(\"hello\"); }",
11841            "file2.rs": "fn test() { println!(\"test\"); }",
11842            "file3.rs": "fn other() { println!(\"other\"); }\n",
11843        }),
11844    )
11845    .await;
11846
11847    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11848    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11849    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11850
11851    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11852    language_registry.add(rust_lang());
11853
11854    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11855    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11856
11857    // Open three buffers
11858    let buffer_1 = project
11859        .update(cx, |project, cx| {
11860            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11861        })
11862        .await
11863        .unwrap();
11864    let buffer_2 = project
11865        .update(cx, |project, cx| {
11866            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11867        })
11868        .await
11869        .unwrap();
11870    let buffer_3 = project
11871        .update(cx, |project, cx| {
11872            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11873        })
11874        .await
11875        .unwrap();
11876
11877    // Create a multi-buffer with all three buffers
11878    let multi_buffer = cx.new(|cx| {
11879        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11880        multi_buffer.push_excerpts(
11881            buffer_1.clone(),
11882            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11883            cx,
11884        );
11885        multi_buffer.push_excerpts(
11886            buffer_2.clone(),
11887            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11888            cx,
11889        );
11890        multi_buffer.push_excerpts(
11891            buffer_3.clone(),
11892            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11893            cx,
11894        );
11895        multi_buffer
11896    });
11897
11898    let editor = cx.new_window_entity(|window, cx| {
11899        Editor::new(
11900            EditorMode::full(),
11901            multi_buffer,
11902            Some(project.clone()),
11903            window,
11904            cx,
11905        )
11906    });
11907
11908    // Edit only the first buffer
11909    editor.update_in(cx, |editor, window, cx| {
11910        editor.change_selections(
11911            SelectionEffects::scroll(Autoscroll::Next),
11912            window,
11913            cx,
11914            |s| s.select_ranges(Some(10..10)),
11915        );
11916        editor.insert("// edited", window, cx);
11917    });
11918
11919    // Verify that only buffer 1 is dirty
11920    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11921    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11922    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11923
11924    // Get write counts after file creation (files were created with initial content)
11925    // We expect each file to have been written once during creation
11926    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11927    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11928    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11929
11930    // Perform autosave
11931    let save_task = editor.update_in(cx, |editor, window, cx| {
11932        editor.save(
11933            SaveOptions {
11934                format: true,
11935                autosave: true,
11936            },
11937            project.clone(),
11938            window,
11939            cx,
11940        )
11941    });
11942    save_task.await.unwrap();
11943
11944    // Only the dirty buffer should have been saved
11945    assert_eq!(
11946        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11947        1,
11948        "Buffer 1 was dirty, so it should have been written once during autosave"
11949    );
11950    assert_eq!(
11951        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11952        0,
11953        "Buffer 2 was clean, so it should not have been written during autosave"
11954    );
11955    assert_eq!(
11956        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11957        0,
11958        "Buffer 3 was clean, so it should not have been written during autosave"
11959    );
11960
11961    // Verify buffer states after autosave
11962    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11963    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11964    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11965
11966    // Now perform a manual save (format = true)
11967    let save_task = editor.update_in(cx, |editor, window, cx| {
11968        editor.save(
11969            SaveOptions {
11970                format: true,
11971                autosave: false,
11972            },
11973            project.clone(),
11974            window,
11975            cx,
11976        )
11977    });
11978    save_task.await.unwrap();
11979
11980    // During manual save, clean buffers don't get written to disk
11981    // They just get did_save called for language server notifications
11982    assert_eq!(
11983        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11984        1,
11985        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11986    );
11987    assert_eq!(
11988        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11989        0,
11990        "Buffer 2 should not have been written at all"
11991    );
11992    assert_eq!(
11993        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11994        0,
11995        "Buffer 3 should not have been written at all"
11996    );
11997}
11998
11999async fn setup_range_format_test(
12000    cx: &mut TestAppContext,
12001) -> (
12002    Entity<Project>,
12003    Entity<Editor>,
12004    &mut gpui::VisualTestContext,
12005    lsp::FakeLanguageServer,
12006) {
12007    init_test(cx, |_| {});
12008
12009    let fs = FakeFs::new(cx.executor());
12010    fs.insert_file(path!("/file.rs"), Default::default()).await;
12011
12012    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12013
12014    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12015    language_registry.add(rust_lang());
12016    let mut fake_servers = language_registry.register_fake_lsp(
12017        "Rust",
12018        FakeLspAdapter {
12019            capabilities: lsp::ServerCapabilities {
12020                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12021                ..lsp::ServerCapabilities::default()
12022            },
12023            ..FakeLspAdapter::default()
12024        },
12025    );
12026
12027    let buffer = project
12028        .update(cx, |project, cx| {
12029            project.open_local_buffer(path!("/file.rs"), cx)
12030        })
12031        .await
12032        .unwrap();
12033
12034    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12035    let (editor, cx) = cx.add_window_view(|window, cx| {
12036        build_editor_with_project(project.clone(), buffer, window, cx)
12037    });
12038
12039    cx.executor().start_waiting();
12040    let fake_server = fake_servers.next().await.unwrap();
12041
12042    (project, editor, cx, fake_server)
12043}
12044
12045#[gpui::test]
12046async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12047    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12048
12049    editor.update_in(cx, |editor, window, cx| {
12050        editor.set_text("one\ntwo\nthree\n", window, cx)
12051    });
12052    assert!(cx.read(|cx| editor.is_dirty(cx)));
12053
12054    let save = editor
12055        .update_in(cx, |editor, window, cx| {
12056            editor.save(
12057                SaveOptions {
12058                    format: true,
12059                    autosave: false,
12060                },
12061                project.clone(),
12062                window,
12063                cx,
12064            )
12065        })
12066        .unwrap();
12067    fake_server
12068        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12069            assert_eq!(
12070                params.text_document.uri,
12071                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12072            );
12073            assert_eq!(params.options.tab_size, 4);
12074            Ok(Some(vec![lsp::TextEdit::new(
12075                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12076                ", ".to_string(),
12077            )]))
12078        })
12079        .next()
12080        .await;
12081    cx.executor().start_waiting();
12082    save.await;
12083    assert_eq!(
12084        editor.update(cx, |editor, cx| editor.text(cx)),
12085        "one, two\nthree\n"
12086    );
12087    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12088}
12089
12090#[gpui::test]
12091async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12092    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12093
12094    editor.update_in(cx, |editor, window, cx| {
12095        editor.set_text("one\ntwo\nthree\n", window, cx)
12096    });
12097    assert!(cx.read(|cx| editor.is_dirty(cx)));
12098
12099    // Test that save still works when formatting hangs
12100    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12101        move |params, _| async move {
12102            assert_eq!(
12103                params.text_document.uri,
12104                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12105            );
12106            futures::future::pending::<()>().await;
12107            unreachable!()
12108        },
12109    );
12110    let save = editor
12111        .update_in(cx, |editor, window, cx| {
12112            editor.save(
12113                SaveOptions {
12114                    format: true,
12115                    autosave: false,
12116                },
12117                project.clone(),
12118                window,
12119                cx,
12120            )
12121        })
12122        .unwrap();
12123    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12124    cx.executor().start_waiting();
12125    save.await;
12126    assert_eq!(
12127        editor.update(cx, |editor, cx| editor.text(cx)),
12128        "one\ntwo\nthree\n"
12129    );
12130    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12131}
12132
12133#[gpui::test]
12134async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12135    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12136
12137    // Buffer starts clean, no formatting should be requested
12138    let save = editor
12139        .update_in(cx, |editor, window, cx| {
12140            editor.save(
12141                SaveOptions {
12142                    format: false,
12143                    autosave: false,
12144                },
12145                project.clone(),
12146                window,
12147                cx,
12148            )
12149        })
12150        .unwrap();
12151    let _pending_format_request = fake_server
12152        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12153            panic!("Should not be invoked");
12154        })
12155        .next();
12156    cx.executor().start_waiting();
12157    save.await;
12158    cx.run_until_parked();
12159}
12160
12161#[gpui::test]
12162async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12163    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12164
12165    // Set Rust language override and assert overridden tabsize is sent to language server
12166    update_test_language_settings(cx, |settings| {
12167        settings.languages.0.insert(
12168            "Rust".into(),
12169            LanguageSettingsContent {
12170                tab_size: NonZeroU32::new(8),
12171                ..Default::default()
12172            },
12173        );
12174    });
12175
12176    editor.update_in(cx, |editor, window, cx| {
12177        editor.set_text("something_new\n", window, cx)
12178    });
12179    assert!(cx.read(|cx| editor.is_dirty(cx)));
12180    let save = editor
12181        .update_in(cx, |editor, window, cx| {
12182            editor.save(
12183                SaveOptions {
12184                    format: true,
12185                    autosave: false,
12186                },
12187                project.clone(),
12188                window,
12189                cx,
12190            )
12191        })
12192        .unwrap();
12193    fake_server
12194        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12195            assert_eq!(
12196                params.text_document.uri,
12197                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12198            );
12199            assert_eq!(params.options.tab_size, 8);
12200            Ok(Some(Vec::new()))
12201        })
12202        .next()
12203        .await;
12204    save.await;
12205}
12206
12207#[gpui::test]
12208async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12209    init_test(cx, |settings| {
12210        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12211            settings::LanguageServerFormatterSpecifier::Current,
12212        )))
12213    });
12214
12215    let fs = FakeFs::new(cx.executor());
12216    fs.insert_file(path!("/file.rs"), Default::default()).await;
12217
12218    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12219
12220    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12221    language_registry.add(Arc::new(Language::new(
12222        LanguageConfig {
12223            name: "Rust".into(),
12224            matcher: LanguageMatcher {
12225                path_suffixes: vec!["rs".to_string()],
12226                ..Default::default()
12227            },
12228            ..LanguageConfig::default()
12229        },
12230        Some(tree_sitter_rust::LANGUAGE.into()),
12231    )));
12232    update_test_language_settings(cx, |settings| {
12233        // Enable Prettier formatting for the same buffer, and ensure
12234        // LSP is called instead of Prettier.
12235        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12236    });
12237    let mut fake_servers = language_registry.register_fake_lsp(
12238        "Rust",
12239        FakeLspAdapter {
12240            capabilities: lsp::ServerCapabilities {
12241                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12242                ..Default::default()
12243            },
12244            ..Default::default()
12245        },
12246    );
12247
12248    let buffer = project
12249        .update(cx, |project, cx| {
12250            project.open_local_buffer(path!("/file.rs"), cx)
12251        })
12252        .await
12253        .unwrap();
12254
12255    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12256    let (editor, cx) = cx.add_window_view(|window, cx| {
12257        build_editor_with_project(project.clone(), buffer, window, cx)
12258    });
12259    editor.update_in(cx, |editor, window, cx| {
12260        editor.set_text("one\ntwo\nthree\n", window, cx)
12261    });
12262
12263    cx.executor().start_waiting();
12264    let fake_server = fake_servers.next().await.unwrap();
12265
12266    let format = editor
12267        .update_in(cx, |editor, window, cx| {
12268            editor.perform_format(
12269                project.clone(),
12270                FormatTrigger::Manual,
12271                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12272                window,
12273                cx,
12274            )
12275        })
12276        .unwrap();
12277    fake_server
12278        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12279            assert_eq!(
12280                params.text_document.uri,
12281                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12282            );
12283            assert_eq!(params.options.tab_size, 4);
12284            Ok(Some(vec![lsp::TextEdit::new(
12285                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12286                ", ".to_string(),
12287            )]))
12288        })
12289        .next()
12290        .await;
12291    cx.executor().start_waiting();
12292    format.await;
12293    assert_eq!(
12294        editor.update(cx, |editor, cx| editor.text(cx)),
12295        "one, two\nthree\n"
12296    );
12297
12298    editor.update_in(cx, |editor, window, cx| {
12299        editor.set_text("one\ntwo\nthree\n", window, cx)
12300    });
12301    // Ensure we don't lock if formatting hangs.
12302    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12303        move |params, _| async move {
12304            assert_eq!(
12305                params.text_document.uri,
12306                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12307            );
12308            futures::future::pending::<()>().await;
12309            unreachable!()
12310        },
12311    );
12312    let format = editor
12313        .update_in(cx, |editor, window, cx| {
12314            editor.perform_format(
12315                project,
12316                FormatTrigger::Manual,
12317                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12318                window,
12319                cx,
12320            )
12321        })
12322        .unwrap();
12323    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12324    cx.executor().start_waiting();
12325    format.await;
12326    assert_eq!(
12327        editor.update(cx, |editor, cx| editor.text(cx)),
12328        "one\ntwo\nthree\n"
12329    );
12330}
12331
12332#[gpui::test]
12333async fn test_multiple_formatters(cx: &mut TestAppContext) {
12334    init_test(cx, |settings| {
12335        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12336        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12337            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12338            Formatter::CodeAction("code-action-1".into()),
12339            Formatter::CodeAction("code-action-2".into()),
12340        ]))
12341    });
12342
12343    let fs = FakeFs::new(cx.executor());
12344    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12345        .await;
12346
12347    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12348    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12349    language_registry.add(rust_lang());
12350
12351    let mut fake_servers = language_registry.register_fake_lsp(
12352        "Rust",
12353        FakeLspAdapter {
12354            capabilities: lsp::ServerCapabilities {
12355                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12356                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12357                    commands: vec!["the-command-for-code-action-1".into()],
12358                    ..Default::default()
12359                }),
12360                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12361                ..Default::default()
12362            },
12363            ..Default::default()
12364        },
12365    );
12366
12367    let buffer = project
12368        .update(cx, |project, cx| {
12369            project.open_local_buffer(path!("/file.rs"), cx)
12370        })
12371        .await
12372        .unwrap();
12373
12374    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12375    let (editor, cx) = cx.add_window_view(|window, cx| {
12376        build_editor_with_project(project.clone(), buffer, window, cx)
12377    });
12378
12379    cx.executor().start_waiting();
12380
12381    let fake_server = fake_servers.next().await.unwrap();
12382    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12383        move |_params, _| async move {
12384            Ok(Some(vec![lsp::TextEdit::new(
12385                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12386                "applied-formatting\n".to_string(),
12387            )]))
12388        },
12389    );
12390    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12391        move |params, _| async move {
12392            let requested_code_actions = params.context.only.expect("Expected code action request");
12393            assert_eq!(requested_code_actions.len(), 1);
12394
12395            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12396            let code_action = match requested_code_actions[0].as_str() {
12397                "code-action-1" => lsp::CodeAction {
12398                    kind: Some("code-action-1".into()),
12399                    edit: Some(lsp::WorkspaceEdit::new(
12400                        [(
12401                            uri,
12402                            vec![lsp::TextEdit::new(
12403                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12404                                "applied-code-action-1-edit\n".to_string(),
12405                            )],
12406                        )]
12407                        .into_iter()
12408                        .collect(),
12409                    )),
12410                    command: Some(lsp::Command {
12411                        command: "the-command-for-code-action-1".into(),
12412                        ..Default::default()
12413                    }),
12414                    ..Default::default()
12415                },
12416                "code-action-2" => lsp::CodeAction {
12417                    kind: Some("code-action-2".into()),
12418                    edit: Some(lsp::WorkspaceEdit::new(
12419                        [(
12420                            uri,
12421                            vec![lsp::TextEdit::new(
12422                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12423                                "applied-code-action-2-edit\n".to_string(),
12424                            )],
12425                        )]
12426                        .into_iter()
12427                        .collect(),
12428                    )),
12429                    ..Default::default()
12430                },
12431                req => panic!("Unexpected code action request: {:?}", req),
12432            };
12433            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12434                code_action,
12435            )]))
12436        },
12437    );
12438
12439    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12440        move |params, _| async move { Ok(params) }
12441    });
12442
12443    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12444    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12445        let fake = fake_server.clone();
12446        let lock = command_lock.clone();
12447        move |params, _| {
12448            assert_eq!(params.command, "the-command-for-code-action-1");
12449            let fake = fake.clone();
12450            let lock = lock.clone();
12451            async move {
12452                lock.lock().await;
12453                fake.server
12454                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12455                        label: None,
12456                        edit: lsp::WorkspaceEdit {
12457                            changes: Some(
12458                                [(
12459                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12460                                    vec![lsp::TextEdit {
12461                                        range: lsp::Range::new(
12462                                            lsp::Position::new(0, 0),
12463                                            lsp::Position::new(0, 0),
12464                                        ),
12465                                        new_text: "applied-code-action-1-command\n".into(),
12466                                    }],
12467                                )]
12468                                .into_iter()
12469                                .collect(),
12470                            ),
12471                            ..Default::default()
12472                        },
12473                    })
12474                    .await
12475                    .into_response()
12476                    .unwrap();
12477                Ok(Some(json!(null)))
12478            }
12479        }
12480    });
12481
12482    cx.executor().start_waiting();
12483    editor
12484        .update_in(cx, |editor, window, cx| {
12485            editor.perform_format(
12486                project.clone(),
12487                FormatTrigger::Manual,
12488                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12489                window,
12490                cx,
12491            )
12492        })
12493        .unwrap()
12494        .await;
12495    editor.update(cx, |editor, cx| {
12496        assert_eq!(
12497            editor.text(cx),
12498            r#"
12499                applied-code-action-2-edit
12500                applied-code-action-1-command
12501                applied-code-action-1-edit
12502                applied-formatting
12503                one
12504                two
12505                three
12506            "#
12507            .unindent()
12508        );
12509    });
12510
12511    editor.update_in(cx, |editor, window, cx| {
12512        editor.undo(&Default::default(), window, cx);
12513        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12514    });
12515
12516    // Perform a manual edit while waiting for an LSP command
12517    // that's being run as part of a formatting code action.
12518    let lock_guard = command_lock.lock().await;
12519    let format = editor
12520        .update_in(cx, |editor, window, cx| {
12521            editor.perform_format(
12522                project.clone(),
12523                FormatTrigger::Manual,
12524                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12525                window,
12526                cx,
12527            )
12528        })
12529        .unwrap();
12530    cx.run_until_parked();
12531    editor.update(cx, |editor, cx| {
12532        assert_eq!(
12533            editor.text(cx),
12534            r#"
12535                applied-code-action-1-edit
12536                applied-formatting
12537                one
12538                two
12539                three
12540            "#
12541            .unindent()
12542        );
12543
12544        editor.buffer.update(cx, |buffer, cx| {
12545            let ix = buffer.len(cx);
12546            buffer.edit([(ix..ix, "edited\n")], None, cx);
12547        });
12548    });
12549
12550    // Allow the LSP command to proceed. Because the buffer was edited,
12551    // the second code action will not be run.
12552    drop(lock_guard);
12553    format.await;
12554    editor.update_in(cx, |editor, window, cx| {
12555        assert_eq!(
12556            editor.text(cx),
12557            r#"
12558                applied-code-action-1-command
12559                applied-code-action-1-edit
12560                applied-formatting
12561                one
12562                two
12563                three
12564                edited
12565            "#
12566            .unindent()
12567        );
12568
12569        // The manual edit is undone first, because it is the last thing the user did
12570        // (even though the command completed afterwards).
12571        editor.undo(&Default::default(), window, cx);
12572        assert_eq!(
12573            editor.text(cx),
12574            r#"
12575                applied-code-action-1-command
12576                applied-code-action-1-edit
12577                applied-formatting
12578                one
12579                two
12580                three
12581            "#
12582            .unindent()
12583        );
12584
12585        // All the formatting (including the command, which completed after the manual edit)
12586        // is undone together.
12587        editor.undo(&Default::default(), window, cx);
12588        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12589    });
12590}
12591
12592#[gpui::test]
12593async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12594    init_test(cx, |settings| {
12595        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12596            settings::LanguageServerFormatterSpecifier::Current,
12597        )]))
12598    });
12599
12600    let fs = FakeFs::new(cx.executor());
12601    fs.insert_file(path!("/file.ts"), Default::default()).await;
12602
12603    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12604
12605    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12606    language_registry.add(Arc::new(Language::new(
12607        LanguageConfig {
12608            name: "TypeScript".into(),
12609            matcher: LanguageMatcher {
12610                path_suffixes: vec!["ts".to_string()],
12611                ..Default::default()
12612            },
12613            ..LanguageConfig::default()
12614        },
12615        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12616    )));
12617    update_test_language_settings(cx, |settings| {
12618        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12619    });
12620    let mut fake_servers = language_registry.register_fake_lsp(
12621        "TypeScript",
12622        FakeLspAdapter {
12623            capabilities: lsp::ServerCapabilities {
12624                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12625                ..Default::default()
12626            },
12627            ..Default::default()
12628        },
12629    );
12630
12631    let buffer = project
12632        .update(cx, |project, cx| {
12633            project.open_local_buffer(path!("/file.ts"), cx)
12634        })
12635        .await
12636        .unwrap();
12637
12638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12639    let (editor, cx) = cx.add_window_view(|window, cx| {
12640        build_editor_with_project(project.clone(), buffer, window, cx)
12641    });
12642    editor.update_in(cx, |editor, window, cx| {
12643        editor.set_text(
12644            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12645            window,
12646            cx,
12647        )
12648    });
12649
12650    cx.executor().start_waiting();
12651    let fake_server = fake_servers.next().await.unwrap();
12652
12653    let format = editor
12654        .update_in(cx, |editor, window, cx| {
12655            editor.perform_code_action_kind(
12656                project.clone(),
12657                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12658                window,
12659                cx,
12660            )
12661        })
12662        .unwrap();
12663    fake_server
12664        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12665            assert_eq!(
12666                params.text_document.uri,
12667                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12668            );
12669            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12670                lsp::CodeAction {
12671                    title: "Organize Imports".to_string(),
12672                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12673                    edit: Some(lsp::WorkspaceEdit {
12674                        changes: Some(
12675                            [(
12676                                params.text_document.uri.clone(),
12677                                vec![lsp::TextEdit::new(
12678                                    lsp::Range::new(
12679                                        lsp::Position::new(1, 0),
12680                                        lsp::Position::new(2, 0),
12681                                    ),
12682                                    "".to_string(),
12683                                )],
12684                            )]
12685                            .into_iter()
12686                            .collect(),
12687                        ),
12688                        ..Default::default()
12689                    }),
12690                    ..Default::default()
12691                },
12692            )]))
12693        })
12694        .next()
12695        .await;
12696    cx.executor().start_waiting();
12697    format.await;
12698    assert_eq!(
12699        editor.update(cx, |editor, cx| editor.text(cx)),
12700        "import { a } from 'module';\n\nconst x = a;\n"
12701    );
12702
12703    editor.update_in(cx, |editor, window, cx| {
12704        editor.set_text(
12705            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12706            window,
12707            cx,
12708        )
12709    });
12710    // Ensure we don't lock if code action hangs.
12711    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12712        move |params, _| async move {
12713            assert_eq!(
12714                params.text_document.uri,
12715                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12716            );
12717            futures::future::pending::<()>().await;
12718            unreachable!()
12719        },
12720    );
12721    let format = editor
12722        .update_in(cx, |editor, window, cx| {
12723            editor.perform_code_action_kind(
12724                project,
12725                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12726                window,
12727                cx,
12728            )
12729        })
12730        .unwrap();
12731    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12732    cx.executor().start_waiting();
12733    format.await;
12734    assert_eq!(
12735        editor.update(cx, |editor, cx| editor.text(cx)),
12736        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12737    );
12738}
12739
12740#[gpui::test]
12741async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12742    init_test(cx, |_| {});
12743
12744    let mut cx = EditorLspTestContext::new_rust(
12745        lsp::ServerCapabilities {
12746            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12747            ..Default::default()
12748        },
12749        cx,
12750    )
12751    .await;
12752
12753    cx.set_state(indoc! {"
12754        one.twoˇ
12755    "});
12756
12757    // The format request takes a long time. When it completes, it inserts
12758    // a newline and an indent before the `.`
12759    cx.lsp
12760        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12761            let executor = cx.background_executor().clone();
12762            async move {
12763                executor.timer(Duration::from_millis(100)).await;
12764                Ok(Some(vec![lsp::TextEdit {
12765                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12766                    new_text: "\n    ".into(),
12767                }]))
12768            }
12769        });
12770
12771    // Submit a format request.
12772    let format_1 = cx
12773        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12774        .unwrap();
12775    cx.executor().run_until_parked();
12776
12777    // Submit a second format request.
12778    let format_2 = cx
12779        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12780        .unwrap();
12781    cx.executor().run_until_parked();
12782
12783    // Wait for both format requests to complete
12784    cx.executor().advance_clock(Duration::from_millis(200));
12785    cx.executor().start_waiting();
12786    format_1.await.unwrap();
12787    cx.executor().start_waiting();
12788    format_2.await.unwrap();
12789
12790    // The formatting edits only happens once.
12791    cx.assert_editor_state(indoc! {"
12792        one
12793            .twoˇ
12794    "});
12795}
12796
12797#[gpui::test]
12798async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12799    init_test(cx, |settings| {
12800        settings.defaults.formatter = Some(FormatterList::default())
12801    });
12802
12803    let mut cx = EditorLspTestContext::new_rust(
12804        lsp::ServerCapabilities {
12805            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12806            ..Default::default()
12807        },
12808        cx,
12809    )
12810    .await;
12811
12812    // Record which buffer changes have been sent to the language server
12813    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12814    cx.lsp
12815        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12816            let buffer_changes = buffer_changes.clone();
12817            move |params, _| {
12818                buffer_changes.lock().extend(
12819                    params
12820                        .content_changes
12821                        .into_iter()
12822                        .map(|e| (e.range.unwrap(), e.text)),
12823                );
12824            }
12825        });
12826    // Handle formatting requests to the language server.
12827    cx.lsp
12828        .set_request_handler::<lsp::request::Formatting, _, _>({
12829            let buffer_changes = buffer_changes.clone();
12830            move |_, _| {
12831                let buffer_changes = buffer_changes.clone();
12832                // Insert blank lines between each line of the buffer.
12833                async move {
12834                    // When formatting is requested, trailing whitespace has already been stripped,
12835                    // and the trailing newline has already been added.
12836                    assert_eq!(
12837                        &buffer_changes.lock()[1..],
12838                        &[
12839                            (
12840                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12841                                "".into()
12842                            ),
12843                            (
12844                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12845                                "".into()
12846                            ),
12847                            (
12848                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12849                                "\n".into()
12850                            ),
12851                        ]
12852                    );
12853
12854                    Ok(Some(vec![
12855                        lsp::TextEdit {
12856                            range: lsp::Range::new(
12857                                lsp::Position::new(1, 0),
12858                                lsp::Position::new(1, 0),
12859                            ),
12860                            new_text: "\n".into(),
12861                        },
12862                        lsp::TextEdit {
12863                            range: lsp::Range::new(
12864                                lsp::Position::new(2, 0),
12865                                lsp::Position::new(2, 0),
12866                            ),
12867                            new_text: "\n".into(),
12868                        },
12869                    ]))
12870                }
12871            }
12872        });
12873
12874    // Set up a buffer white some trailing whitespace and no trailing newline.
12875    cx.set_state(
12876        &[
12877            "one ",   //
12878            "twoˇ",   //
12879            "three ", //
12880            "four",   //
12881        ]
12882        .join("\n"),
12883    );
12884    cx.run_until_parked();
12885
12886    // Submit a format request.
12887    let format = cx
12888        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12889        .unwrap();
12890
12891    cx.run_until_parked();
12892    // After formatting the buffer, the trailing whitespace is stripped,
12893    // a newline is appended, and the edits provided by the language server
12894    // have been applied.
12895    format.await.unwrap();
12896
12897    cx.assert_editor_state(
12898        &[
12899            "one",   //
12900            "",      //
12901            "twoˇ",  //
12902            "",      //
12903            "three", //
12904            "four",  //
12905            "",      //
12906        ]
12907        .join("\n"),
12908    );
12909
12910    // Undoing the formatting undoes the trailing whitespace removal, the
12911    // trailing newline, and the LSP edits.
12912    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12913    cx.assert_editor_state(
12914        &[
12915            "one ",   //
12916            "twoˇ",   //
12917            "three ", //
12918            "four",   //
12919        ]
12920        .join("\n"),
12921    );
12922}
12923
12924#[gpui::test]
12925async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12926    cx: &mut TestAppContext,
12927) {
12928    init_test(cx, |_| {});
12929
12930    cx.update(|cx| {
12931        cx.update_global::<SettingsStore, _>(|settings, cx| {
12932            settings.update_user_settings(cx, |settings| {
12933                settings.editor.auto_signature_help = Some(true);
12934            });
12935        });
12936    });
12937
12938    let mut cx = EditorLspTestContext::new_rust(
12939        lsp::ServerCapabilities {
12940            signature_help_provider: Some(lsp::SignatureHelpOptions {
12941                ..Default::default()
12942            }),
12943            ..Default::default()
12944        },
12945        cx,
12946    )
12947    .await;
12948
12949    let language = Language::new(
12950        LanguageConfig {
12951            name: "Rust".into(),
12952            brackets: BracketPairConfig {
12953                pairs: vec![
12954                    BracketPair {
12955                        start: "{".to_string(),
12956                        end: "}".to_string(),
12957                        close: true,
12958                        surround: true,
12959                        newline: true,
12960                    },
12961                    BracketPair {
12962                        start: "(".to_string(),
12963                        end: ")".to_string(),
12964                        close: true,
12965                        surround: true,
12966                        newline: true,
12967                    },
12968                    BracketPair {
12969                        start: "/*".to_string(),
12970                        end: " */".to_string(),
12971                        close: true,
12972                        surround: true,
12973                        newline: true,
12974                    },
12975                    BracketPair {
12976                        start: "[".to_string(),
12977                        end: "]".to_string(),
12978                        close: false,
12979                        surround: false,
12980                        newline: true,
12981                    },
12982                    BracketPair {
12983                        start: "\"".to_string(),
12984                        end: "\"".to_string(),
12985                        close: true,
12986                        surround: true,
12987                        newline: false,
12988                    },
12989                    BracketPair {
12990                        start: "<".to_string(),
12991                        end: ">".to_string(),
12992                        close: false,
12993                        surround: true,
12994                        newline: true,
12995                    },
12996                ],
12997                ..Default::default()
12998            },
12999            autoclose_before: "})]".to_string(),
13000            ..Default::default()
13001        },
13002        Some(tree_sitter_rust::LANGUAGE.into()),
13003    );
13004    let language = Arc::new(language);
13005
13006    cx.language_registry().add(language.clone());
13007    cx.update_buffer(|buffer, cx| {
13008        buffer.set_language(Some(language), cx);
13009    });
13010
13011    cx.set_state(
13012        &r#"
13013            fn main() {
13014                sampleˇ
13015            }
13016        "#
13017        .unindent(),
13018    );
13019
13020    cx.update_editor(|editor, window, cx| {
13021        editor.handle_input("(", window, cx);
13022    });
13023    cx.assert_editor_state(
13024        &"
13025            fn main() {
13026                sample(ˇ)
13027            }
13028        "
13029        .unindent(),
13030    );
13031
13032    let mocked_response = lsp::SignatureHelp {
13033        signatures: vec![lsp::SignatureInformation {
13034            label: "fn sample(param1: u8, param2: u8)".to_string(),
13035            documentation: None,
13036            parameters: Some(vec![
13037                lsp::ParameterInformation {
13038                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13039                    documentation: None,
13040                },
13041                lsp::ParameterInformation {
13042                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13043                    documentation: None,
13044                },
13045            ]),
13046            active_parameter: None,
13047        }],
13048        active_signature: Some(0),
13049        active_parameter: Some(0),
13050    };
13051    handle_signature_help_request(&mut cx, mocked_response).await;
13052
13053    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13054        .await;
13055
13056    cx.editor(|editor, _, _| {
13057        let signature_help_state = editor.signature_help_state.popover().cloned();
13058        let signature = signature_help_state.unwrap();
13059        assert_eq!(
13060            signature.signatures[signature.current_signature].label,
13061            "fn sample(param1: u8, param2: u8)"
13062        );
13063    });
13064}
13065
13066#[gpui::test]
13067async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13068    init_test(cx, |_| {});
13069
13070    cx.update(|cx| {
13071        cx.update_global::<SettingsStore, _>(|settings, cx| {
13072            settings.update_user_settings(cx, |settings| {
13073                settings.editor.auto_signature_help = Some(false);
13074                settings.editor.show_signature_help_after_edits = Some(false);
13075            });
13076        });
13077    });
13078
13079    let mut cx = EditorLspTestContext::new_rust(
13080        lsp::ServerCapabilities {
13081            signature_help_provider: Some(lsp::SignatureHelpOptions {
13082                ..Default::default()
13083            }),
13084            ..Default::default()
13085        },
13086        cx,
13087    )
13088    .await;
13089
13090    let language = Language::new(
13091        LanguageConfig {
13092            name: "Rust".into(),
13093            brackets: BracketPairConfig {
13094                pairs: vec![
13095                    BracketPair {
13096                        start: "{".to_string(),
13097                        end: "}".to_string(),
13098                        close: true,
13099                        surround: true,
13100                        newline: true,
13101                    },
13102                    BracketPair {
13103                        start: "(".to_string(),
13104                        end: ")".to_string(),
13105                        close: true,
13106                        surround: true,
13107                        newline: true,
13108                    },
13109                    BracketPair {
13110                        start: "/*".to_string(),
13111                        end: " */".to_string(),
13112                        close: true,
13113                        surround: true,
13114                        newline: true,
13115                    },
13116                    BracketPair {
13117                        start: "[".to_string(),
13118                        end: "]".to_string(),
13119                        close: false,
13120                        surround: false,
13121                        newline: true,
13122                    },
13123                    BracketPair {
13124                        start: "\"".to_string(),
13125                        end: "\"".to_string(),
13126                        close: true,
13127                        surround: true,
13128                        newline: false,
13129                    },
13130                    BracketPair {
13131                        start: "<".to_string(),
13132                        end: ">".to_string(),
13133                        close: false,
13134                        surround: true,
13135                        newline: true,
13136                    },
13137                ],
13138                ..Default::default()
13139            },
13140            autoclose_before: "})]".to_string(),
13141            ..Default::default()
13142        },
13143        Some(tree_sitter_rust::LANGUAGE.into()),
13144    );
13145    let language = Arc::new(language);
13146
13147    cx.language_registry().add(language.clone());
13148    cx.update_buffer(|buffer, cx| {
13149        buffer.set_language(Some(language), cx);
13150    });
13151
13152    // Ensure that signature_help is not called when no signature help is enabled.
13153    cx.set_state(
13154        &r#"
13155            fn main() {
13156                sampleˇ
13157            }
13158        "#
13159        .unindent(),
13160    );
13161    cx.update_editor(|editor, window, cx| {
13162        editor.handle_input("(", window, cx);
13163    });
13164    cx.assert_editor_state(
13165        &"
13166            fn main() {
13167                sample(ˇ)
13168            }
13169        "
13170        .unindent(),
13171    );
13172    cx.editor(|editor, _, _| {
13173        assert!(editor.signature_help_state.task().is_none());
13174    });
13175
13176    let mocked_response = lsp::SignatureHelp {
13177        signatures: vec![lsp::SignatureInformation {
13178            label: "fn sample(param1: u8, param2: u8)".to_string(),
13179            documentation: None,
13180            parameters: Some(vec![
13181                lsp::ParameterInformation {
13182                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13183                    documentation: None,
13184                },
13185                lsp::ParameterInformation {
13186                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13187                    documentation: None,
13188                },
13189            ]),
13190            active_parameter: None,
13191        }],
13192        active_signature: Some(0),
13193        active_parameter: Some(0),
13194    };
13195
13196    // Ensure that signature_help is called when enabled afte edits
13197    cx.update(|_, cx| {
13198        cx.update_global::<SettingsStore, _>(|settings, cx| {
13199            settings.update_user_settings(cx, |settings| {
13200                settings.editor.auto_signature_help = Some(false);
13201                settings.editor.show_signature_help_after_edits = Some(true);
13202            });
13203        });
13204    });
13205    cx.set_state(
13206        &r#"
13207            fn main() {
13208                sampleˇ
13209            }
13210        "#
13211        .unindent(),
13212    );
13213    cx.update_editor(|editor, window, cx| {
13214        editor.handle_input("(", window, cx);
13215    });
13216    cx.assert_editor_state(
13217        &"
13218            fn main() {
13219                sample(ˇ)
13220            }
13221        "
13222        .unindent(),
13223    );
13224    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13225    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13226        .await;
13227    cx.update_editor(|editor, _, _| {
13228        let signature_help_state = editor.signature_help_state.popover().cloned();
13229        assert!(signature_help_state.is_some());
13230        let signature = signature_help_state.unwrap();
13231        assert_eq!(
13232            signature.signatures[signature.current_signature].label,
13233            "fn sample(param1: u8, param2: u8)"
13234        );
13235        editor.signature_help_state = SignatureHelpState::default();
13236    });
13237
13238    // Ensure that signature_help is called when auto signature help override is enabled
13239    cx.update(|_, cx| {
13240        cx.update_global::<SettingsStore, _>(|settings, cx| {
13241            settings.update_user_settings(cx, |settings| {
13242                settings.editor.auto_signature_help = Some(true);
13243                settings.editor.show_signature_help_after_edits = Some(false);
13244            });
13245        });
13246    });
13247    cx.set_state(
13248        &r#"
13249            fn main() {
13250                sampleˇ
13251            }
13252        "#
13253        .unindent(),
13254    );
13255    cx.update_editor(|editor, window, cx| {
13256        editor.handle_input("(", window, cx);
13257    });
13258    cx.assert_editor_state(
13259        &"
13260            fn main() {
13261                sample(ˇ)
13262            }
13263        "
13264        .unindent(),
13265    );
13266    handle_signature_help_request(&mut cx, mocked_response).await;
13267    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13268        .await;
13269    cx.editor(|editor, _, _| {
13270        let signature_help_state = editor.signature_help_state.popover().cloned();
13271        assert!(signature_help_state.is_some());
13272        let signature = signature_help_state.unwrap();
13273        assert_eq!(
13274            signature.signatures[signature.current_signature].label,
13275            "fn sample(param1: u8, param2: u8)"
13276        );
13277    });
13278}
13279
13280#[gpui::test]
13281async fn test_signature_help(cx: &mut TestAppContext) {
13282    init_test(cx, |_| {});
13283    cx.update(|cx| {
13284        cx.update_global::<SettingsStore, _>(|settings, cx| {
13285            settings.update_user_settings(cx, |settings| {
13286                settings.editor.auto_signature_help = Some(true);
13287            });
13288        });
13289    });
13290
13291    let mut cx = EditorLspTestContext::new_rust(
13292        lsp::ServerCapabilities {
13293            signature_help_provider: Some(lsp::SignatureHelpOptions {
13294                ..Default::default()
13295            }),
13296            ..Default::default()
13297        },
13298        cx,
13299    )
13300    .await;
13301
13302    // A test that directly calls `show_signature_help`
13303    cx.update_editor(|editor, window, cx| {
13304        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13305    });
13306
13307    let mocked_response = lsp::SignatureHelp {
13308        signatures: vec![lsp::SignatureInformation {
13309            label: "fn sample(param1: u8, param2: u8)".to_string(),
13310            documentation: None,
13311            parameters: Some(vec![
13312                lsp::ParameterInformation {
13313                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13314                    documentation: None,
13315                },
13316                lsp::ParameterInformation {
13317                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13318                    documentation: None,
13319                },
13320            ]),
13321            active_parameter: None,
13322        }],
13323        active_signature: Some(0),
13324        active_parameter: Some(0),
13325    };
13326    handle_signature_help_request(&mut cx, mocked_response).await;
13327
13328    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13329        .await;
13330
13331    cx.editor(|editor, _, _| {
13332        let signature_help_state = editor.signature_help_state.popover().cloned();
13333        assert!(signature_help_state.is_some());
13334        let signature = signature_help_state.unwrap();
13335        assert_eq!(
13336            signature.signatures[signature.current_signature].label,
13337            "fn sample(param1: u8, param2: u8)"
13338        );
13339    });
13340
13341    // When exiting outside from inside the brackets, `signature_help` is closed.
13342    cx.set_state(indoc! {"
13343        fn main() {
13344            sample(ˇ);
13345        }
13346
13347        fn sample(param1: u8, param2: u8) {}
13348    "});
13349
13350    cx.update_editor(|editor, window, cx| {
13351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13352            s.select_ranges([0..0])
13353        });
13354    });
13355
13356    let mocked_response = lsp::SignatureHelp {
13357        signatures: Vec::new(),
13358        active_signature: None,
13359        active_parameter: None,
13360    };
13361    handle_signature_help_request(&mut cx, mocked_response).await;
13362
13363    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13364        .await;
13365
13366    cx.editor(|editor, _, _| {
13367        assert!(!editor.signature_help_state.is_shown());
13368    });
13369
13370    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13371    cx.set_state(indoc! {"
13372        fn main() {
13373            sample(ˇ);
13374        }
13375
13376        fn sample(param1: u8, param2: u8) {}
13377    "});
13378
13379    let mocked_response = lsp::SignatureHelp {
13380        signatures: vec![lsp::SignatureInformation {
13381            label: "fn sample(param1: u8, param2: u8)".to_string(),
13382            documentation: None,
13383            parameters: Some(vec![
13384                lsp::ParameterInformation {
13385                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13386                    documentation: None,
13387                },
13388                lsp::ParameterInformation {
13389                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13390                    documentation: None,
13391                },
13392            ]),
13393            active_parameter: None,
13394        }],
13395        active_signature: Some(0),
13396        active_parameter: Some(0),
13397    };
13398    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13399    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13400        .await;
13401    cx.editor(|editor, _, _| {
13402        assert!(editor.signature_help_state.is_shown());
13403    });
13404
13405    // Restore the popover with more parameter input
13406    cx.set_state(indoc! {"
13407        fn main() {
13408            sample(param1, param2ˇ);
13409        }
13410
13411        fn sample(param1: u8, param2: u8) {}
13412    "});
13413
13414    let mocked_response = lsp::SignatureHelp {
13415        signatures: vec![lsp::SignatureInformation {
13416            label: "fn sample(param1: u8, param2: u8)".to_string(),
13417            documentation: None,
13418            parameters: Some(vec![
13419                lsp::ParameterInformation {
13420                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13421                    documentation: None,
13422                },
13423                lsp::ParameterInformation {
13424                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13425                    documentation: None,
13426                },
13427            ]),
13428            active_parameter: None,
13429        }],
13430        active_signature: Some(0),
13431        active_parameter: Some(1),
13432    };
13433    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13434    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13435        .await;
13436
13437    // When selecting a range, the popover is gone.
13438    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13439    cx.update_editor(|editor, window, cx| {
13440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13441            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13442        })
13443    });
13444    cx.assert_editor_state(indoc! {"
13445        fn main() {
13446            sample(param1, «ˇparam2»);
13447        }
13448
13449        fn sample(param1: u8, param2: u8) {}
13450    "});
13451    cx.editor(|editor, _, _| {
13452        assert!(!editor.signature_help_state.is_shown());
13453    });
13454
13455    // When unselecting again, the popover is back if within the brackets.
13456    cx.update_editor(|editor, window, cx| {
13457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13458            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13459        })
13460    });
13461    cx.assert_editor_state(indoc! {"
13462        fn main() {
13463            sample(param1, ˇparam2);
13464        }
13465
13466        fn sample(param1: u8, param2: u8) {}
13467    "});
13468    handle_signature_help_request(&mut cx, mocked_response).await;
13469    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13470        .await;
13471    cx.editor(|editor, _, _| {
13472        assert!(editor.signature_help_state.is_shown());
13473    });
13474
13475    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13476    cx.update_editor(|editor, window, cx| {
13477        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13478            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13479            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13480        })
13481    });
13482    cx.assert_editor_state(indoc! {"
13483        fn main() {
13484            sample(param1, ˇparam2);
13485        }
13486
13487        fn sample(param1: u8, param2: u8) {}
13488    "});
13489
13490    let mocked_response = lsp::SignatureHelp {
13491        signatures: vec![lsp::SignatureInformation {
13492            label: "fn sample(param1: u8, param2: u8)".to_string(),
13493            documentation: None,
13494            parameters: Some(vec![
13495                lsp::ParameterInformation {
13496                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13497                    documentation: None,
13498                },
13499                lsp::ParameterInformation {
13500                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13501                    documentation: None,
13502                },
13503            ]),
13504            active_parameter: None,
13505        }],
13506        active_signature: Some(0),
13507        active_parameter: Some(1),
13508    };
13509    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13510    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13511        .await;
13512    cx.update_editor(|editor, _, cx| {
13513        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13514    });
13515    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13516        .await;
13517    cx.update_editor(|editor, window, cx| {
13518        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13519            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13520        })
13521    });
13522    cx.assert_editor_state(indoc! {"
13523        fn main() {
13524            sample(param1, «ˇparam2»);
13525        }
13526
13527        fn sample(param1: u8, param2: u8) {}
13528    "});
13529    cx.update_editor(|editor, window, cx| {
13530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13531            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13532        })
13533    });
13534    cx.assert_editor_state(indoc! {"
13535        fn main() {
13536            sample(param1, ˇparam2);
13537        }
13538
13539        fn sample(param1: u8, param2: u8) {}
13540    "});
13541    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13542        .await;
13543}
13544
13545#[gpui::test]
13546async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13547    init_test(cx, |_| {});
13548
13549    let mut cx = EditorLspTestContext::new_rust(
13550        lsp::ServerCapabilities {
13551            signature_help_provider: Some(lsp::SignatureHelpOptions {
13552                ..Default::default()
13553            }),
13554            ..Default::default()
13555        },
13556        cx,
13557    )
13558    .await;
13559
13560    cx.set_state(indoc! {"
13561        fn main() {
13562            overloadedˇ
13563        }
13564    "});
13565
13566    cx.update_editor(|editor, window, cx| {
13567        editor.handle_input("(", window, cx);
13568        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13569    });
13570
13571    // Mock response with 3 signatures
13572    let mocked_response = lsp::SignatureHelp {
13573        signatures: vec![
13574            lsp::SignatureInformation {
13575                label: "fn overloaded(x: i32)".to_string(),
13576                documentation: None,
13577                parameters: Some(vec![lsp::ParameterInformation {
13578                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13579                    documentation: None,
13580                }]),
13581                active_parameter: None,
13582            },
13583            lsp::SignatureInformation {
13584                label: "fn overloaded(x: i32, y: i32)".to_string(),
13585                documentation: None,
13586                parameters: Some(vec![
13587                    lsp::ParameterInformation {
13588                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13589                        documentation: None,
13590                    },
13591                    lsp::ParameterInformation {
13592                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13593                        documentation: None,
13594                    },
13595                ]),
13596                active_parameter: None,
13597            },
13598            lsp::SignatureInformation {
13599                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13600                documentation: None,
13601                parameters: Some(vec![
13602                    lsp::ParameterInformation {
13603                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13604                        documentation: None,
13605                    },
13606                    lsp::ParameterInformation {
13607                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13608                        documentation: None,
13609                    },
13610                    lsp::ParameterInformation {
13611                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13612                        documentation: None,
13613                    },
13614                ]),
13615                active_parameter: None,
13616            },
13617        ],
13618        active_signature: Some(1),
13619        active_parameter: Some(0),
13620    };
13621    handle_signature_help_request(&mut cx, mocked_response).await;
13622
13623    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13624        .await;
13625
13626    // Verify we have multiple signatures and the right one is selected
13627    cx.editor(|editor, _, _| {
13628        let popover = editor.signature_help_state.popover().cloned().unwrap();
13629        assert_eq!(popover.signatures.len(), 3);
13630        // active_signature was 1, so that should be the current
13631        assert_eq!(popover.current_signature, 1);
13632        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13633        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13634        assert_eq!(
13635            popover.signatures[2].label,
13636            "fn overloaded(x: i32, y: i32, z: i32)"
13637        );
13638    });
13639
13640    // Test navigation functionality
13641    cx.update_editor(|editor, window, cx| {
13642        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13643    });
13644
13645    cx.editor(|editor, _, _| {
13646        let popover = editor.signature_help_state.popover().cloned().unwrap();
13647        assert_eq!(popover.current_signature, 2);
13648    });
13649
13650    // Test wrap around
13651    cx.update_editor(|editor, window, cx| {
13652        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13653    });
13654
13655    cx.editor(|editor, _, _| {
13656        let popover = editor.signature_help_state.popover().cloned().unwrap();
13657        assert_eq!(popover.current_signature, 0);
13658    });
13659
13660    // Test previous navigation
13661    cx.update_editor(|editor, window, cx| {
13662        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13663    });
13664
13665    cx.editor(|editor, _, _| {
13666        let popover = editor.signature_help_state.popover().cloned().unwrap();
13667        assert_eq!(popover.current_signature, 2);
13668    });
13669}
13670
13671#[gpui::test]
13672async fn test_completion_mode(cx: &mut TestAppContext) {
13673    init_test(cx, |_| {});
13674    let mut cx = EditorLspTestContext::new_rust(
13675        lsp::ServerCapabilities {
13676            completion_provider: Some(lsp::CompletionOptions {
13677                resolve_provider: Some(true),
13678                ..Default::default()
13679            }),
13680            ..Default::default()
13681        },
13682        cx,
13683    )
13684    .await;
13685
13686    struct Run {
13687        run_description: &'static str,
13688        initial_state: String,
13689        buffer_marked_text: String,
13690        completion_label: &'static str,
13691        completion_text: &'static str,
13692        expected_with_insert_mode: String,
13693        expected_with_replace_mode: String,
13694        expected_with_replace_subsequence_mode: String,
13695        expected_with_replace_suffix_mode: String,
13696    }
13697
13698    let runs = [
13699        Run {
13700            run_description: "Start of word matches completion text",
13701            initial_state: "before ediˇ after".into(),
13702            buffer_marked_text: "before <edi|> after".into(),
13703            completion_label: "editor",
13704            completion_text: "editor",
13705            expected_with_insert_mode: "before editorˇ after".into(),
13706            expected_with_replace_mode: "before editorˇ after".into(),
13707            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13708            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13709        },
13710        Run {
13711            run_description: "Accept same text at the middle of the word",
13712            initial_state: "before ediˇtor after".into(),
13713            buffer_marked_text: "before <edi|tor> after".into(),
13714            completion_label: "editor",
13715            completion_text: "editor",
13716            expected_with_insert_mode: "before editorˇtor after".into(),
13717            expected_with_replace_mode: "before editorˇ after".into(),
13718            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13719            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13720        },
13721        Run {
13722            run_description: "End of word matches completion text -- cursor at end",
13723            initial_state: "before torˇ after".into(),
13724            buffer_marked_text: "before <tor|> after".into(),
13725            completion_label: "editor",
13726            completion_text: "editor",
13727            expected_with_insert_mode: "before editorˇ after".into(),
13728            expected_with_replace_mode: "before editorˇ after".into(),
13729            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13730            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13731        },
13732        Run {
13733            run_description: "End of word matches completion text -- cursor at start",
13734            initial_state: "before ˇtor after".into(),
13735            buffer_marked_text: "before <|tor> after".into(),
13736            completion_label: "editor",
13737            completion_text: "editor",
13738            expected_with_insert_mode: "before editorˇtor after".into(),
13739            expected_with_replace_mode: "before editorˇ after".into(),
13740            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13741            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13742        },
13743        Run {
13744            run_description: "Prepend text containing whitespace",
13745            initial_state: "pˇfield: bool".into(),
13746            buffer_marked_text: "<p|field>: bool".into(),
13747            completion_label: "pub ",
13748            completion_text: "pub ",
13749            expected_with_insert_mode: "pub ˇfield: bool".into(),
13750            expected_with_replace_mode: "pub ˇ: bool".into(),
13751            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13752            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13753        },
13754        Run {
13755            run_description: "Add element to start of list",
13756            initial_state: "[element_ˇelement_2]".into(),
13757            buffer_marked_text: "[<element_|element_2>]".into(),
13758            completion_label: "element_1",
13759            completion_text: "element_1",
13760            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13761            expected_with_replace_mode: "[element_1ˇ]".into(),
13762            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13763            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13764        },
13765        Run {
13766            run_description: "Add element to start of list -- first and second elements are equal",
13767            initial_state: "[elˇelement]".into(),
13768            buffer_marked_text: "[<el|element>]".into(),
13769            completion_label: "element",
13770            completion_text: "element",
13771            expected_with_insert_mode: "[elementˇelement]".into(),
13772            expected_with_replace_mode: "[elementˇ]".into(),
13773            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13774            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13775        },
13776        Run {
13777            run_description: "Ends with matching suffix",
13778            initial_state: "SubˇError".into(),
13779            buffer_marked_text: "<Sub|Error>".into(),
13780            completion_label: "SubscriptionError",
13781            completion_text: "SubscriptionError",
13782            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13783            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13784            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13785            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13786        },
13787        Run {
13788            run_description: "Suffix is a subsequence -- contiguous",
13789            initial_state: "SubˇErr".into(),
13790            buffer_marked_text: "<Sub|Err>".into(),
13791            completion_label: "SubscriptionError",
13792            completion_text: "SubscriptionError",
13793            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13794            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13795            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13796            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13797        },
13798        Run {
13799            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13800            initial_state: "Suˇscrirr".into(),
13801            buffer_marked_text: "<Su|scrirr>".into(),
13802            completion_label: "SubscriptionError",
13803            completion_text: "SubscriptionError",
13804            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13805            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13806            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13807            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13808        },
13809        Run {
13810            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13811            initial_state: "foo(indˇix)".into(),
13812            buffer_marked_text: "foo(<ind|ix>)".into(),
13813            completion_label: "node_index",
13814            completion_text: "node_index",
13815            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13816            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13817            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13818            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13819        },
13820        Run {
13821            run_description: "Replace range ends before cursor - should extend to cursor",
13822            initial_state: "before editˇo after".into(),
13823            buffer_marked_text: "before <{ed}>it|o after".into(),
13824            completion_label: "editor",
13825            completion_text: "editor",
13826            expected_with_insert_mode: "before editorˇo after".into(),
13827            expected_with_replace_mode: "before editorˇo after".into(),
13828            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13829            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13830        },
13831        Run {
13832            run_description: "Uses label for suffix matching",
13833            initial_state: "before ediˇtor after".into(),
13834            buffer_marked_text: "before <edi|tor> after".into(),
13835            completion_label: "editor",
13836            completion_text: "editor()",
13837            expected_with_insert_mode: "before editor()ˇtor after".into(),
13838            expected_with_replace_mode: "before editor()ˇ after".into(),
13839            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13840            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13841        },
13842        Run {
13843            run_description: "Case insensitive subsequence and suffix matching",
13844            initial_state: "before EDiˇtoR after".into(),
13845            buffer_marked_text: "before <EDi|toR> after".into(),
13846            completion_label: "editor",
13847            completion_text: "editor",
13848            expected_with_insert_mode: "before editorˇtoR after".into(),
13849            expected_with_replace_mode: "before editorˇ after".into(),
13850            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13851            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13852        },
13853    ];
13854
13855    for run in runs {
13856        let run_variations = [
13857            (LspInsertMode::Insert, run.expected_with_insert_mode),
13858            (LspInsertMode::Replace, run.expected_with_replace_mode),
13859            (
13860                LspInsertMode::ReplaceSubsequence,
13861                run.expected_with_replace_subsequence_mode,
13862            ),
13863            (
13864                LspInsertMode::ReplaceSuffix,
13865                run.expected_with_replace_suffix_mode,
13866            ),
13867        ];
13868
13869        for (lsp_insert_mode, expected_text) in run_variations {
13870            eprintln!(
13871                "run = {:?}, mode = {lsp_insert_mode:.?}",
13872                run.run_description,
13873            );
13874
13875            update_test_language_settings(&mut cx, |settings| {
13876                settings.defaults.completions = Some(CompletionSettingsContent {
13877                    lsp_insert_mode: Some(lsp_insert_mode),
13878                    words: Some(WordsCompletionMode::Disabled),
13879                    words_min_length: Some(0),
13880                    ..Default::default()
13881                });
13882            });
13883
13884            cx.set_state(&run.initial_state);
13885            cx.update_editor(|editor, window, cx| {
13886                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13887            });
13888
13889            let counter = Arc::new(AtomicUsize::new(0));
13890            handle_completion_request_with_insert_and_replace(
13891                &mut cx,
13892                &run.buffer_marked_text,
13893                vec![(run.completion_label, run.completion_text)],
13894                counter.clone(),
13895            )
13896            .await;
13897            cx.condition(|editor, _| editor.context_menu_visible())
13898                .await;
13899            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13900
13901            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13902                editor
13903                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13904                    .unwrap()
13905            });
13906            cx.assert_editor_state(&expected_text);
13907            handle_resolve_completion_request(&mut cx, None).await;
13908            apply_additional_edits.await.unwrap();
13909        }
13910    }
13911}
13912
13913#[gpui::test]
13914async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13915    init_test(cx, |_| {});
13916    let mut cx = EditorLspTestContext::new_rust(
13917        lsp::ServerCapabilities {
13918            completion_provider: Some(lsp::CompletionOptions {
13919                resolve_provider: Some(true),
13920                ..Default::default()
13921            }),
13922            ..Default::default()
13923        },
13924        cx,
13925    )
13926    .await;
13927
13928    let initial_state = "SubˇError";
13929    let buffer_marked_text = "<Sub|Error>";
13930    let completion_text = "SubscriptionError";
13931    let expected_with_insert_mode = "SubscriptionErrorˇError";
13932    let expected_with_replace_mode = "SubscriptionErrorˇ";
13933
13934    update_test_language_settings(&mut cx, |settings| {
13935        settings.defaults.completions = Some(CompletionSettingsContent {
13936            words: Some(WordsCompletionMode::Disabled),
13937            words_min_length: Some(0),
13938            // set the opposite here to ensure that the action is overriding the default behavior
13939            lsp_insert_mode: Some(LspInsertMode::Insert),
13940            ..Default::default()
13941        });
13942    });
13943
13944    cx.set_state(initial_state);
13945    cx.update_editor(|editor, window, cx| {
13946        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13947    });
13948
13949    let counter = Arc::new(AtomicUsize::new(0));
13950    handle_completion_request_with_insert_and_replace(
13951        &mut cx,
13952        buffer_marked_text,
13953        vec![(completion_text, completion_text)],
13954        counter.clone(),
13955    )
13956    .await;
13957    cx.condition(|editor, _| editor.context_menu_visible())
13958        .await;
13959    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13960
13961    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13962        editor
13963            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13964            .unwrap()
13965    });
13966    cx.assert_editor_state(expected_with_replace_mode);
13967    handle_resolve_completion_request(&mut cx, None).await;
13968    apply_additional_edits.await.unwrap();
13969
13970    update_test_language_settings(&mut cx, |settings| {
13971        settings.defaults.completions = Some(CompletionSettingsContent {
13972            words: Some(WordsCompletionMode::Disabled),
13973            words_min_length: Some(0),
13974            // set the opposite here to ensure that the action is overriding the default behavior
13975            lsp_insert_mode: Some(LspInsertMode::Replace),
13976            ..Default::default()
13977        });
13978    });
13979
13980    cx.set_state(initial_state);
13981    cx.update_editor(|editor, window, cx| {
13982        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13983    });
13984    handle_completion_request_with_insert_and_replace(
13985        &mut cx,
13986        buffer_marked_text,
13987        vec![(completion_text, completion_text)],
13988        counter.clone(),
13989    )
13990    .await;
13991    cx.condition(|editor, _| editor.context_menu_visible())
13992        .await;
13993    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13994
13995    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13996        editor
13997            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13998            .unwrap()
13999    });
14000    cx.assert_editor_state(expected_with_insert_mode);
14001    handle_resolve_completion_request(&mut cx, None).await;
14002    apply_additional_edits.await.unwrap();
14003}
14004
14005#[gpui::test]
14006async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14007    init_test(cx, |_| {});
14008    let mut cx = EditorLspTestContext::new_rust(
14009        lsp::ServerCapabilities {
14010            completion_provider: Some(lsp::CompletionOptions {
14011                resolve_provider: Some(true),
14012                ..Default::default()
14013            }),
14014            ..Default::default()
14015        },
14016        cx,
14017    )
14018    .await;
14019
14020    // scenario: surrounding text matches completion text
14021    let completion_text = "to_offset";
14022    let initial_state = indoc! {"
14023        1. buf.to_offˇsuffix
14024        2. buf.to_offˇsuf
14025        3. buf.to_offˇfix
14026        4. buf.to_offˇ
14027        5. into_offˇensive
14028        6. ˇsuffix
14029        7. let ˇ //
14030        8. aaˇzz
14031        9. buf.to_off«zzzzzˇ»suffix
14032        10. buf.«ˇzzzzz»suffix
14033        11. to_off«ˇzzzzz»
14034
14035        buf.to_offˇsuffix  // newest cursor
14036    "};
14037    let completion_marked_buffer = indoc! {"
14038        1. buf.to_offsuffix
14039        2. buf.to_offsuf
14040        3. buf.to_offfix
14041        4. buf.to_off
14042        5. into_offensive
14043        6. suffix
14044        7. let  //
14045        8. aazz
14046        9. buf.to_offzzzzzsuffix
14047        10. buf.zzzzzsuffix
14048        11. to_offzzzzz
14049
14050        buf.<to_off|suffix>  // newest cursor
14051    "};
14052    let expected = indoc! {"
14053        1. buf.to_offsetˇ
14054        2. buf.to_offsetˇsuf
14055        3. buf.to_offsetˇfix
14056        4. buf.to_offsetˇ
14057        5. into_offsetˇensive
14058        6. to_offsetˇsuffix
14059        7. let to_offsetˇ //
14060        8. aato_offsetˇzz
14061        9. buf.to_offsetˇ
14062        10. buf.to_offsetˇsuffix
14063        11. to_offsetˇ
14064
14065        buf.to_offsetˇ  // newest cursor
14066    "};
14067    cx.set_state(initial_state);
14068    cx.update_editor(|editor, window, cx| {
14069        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14070    });
14071    handle_completion_request_with_insert_and_replace(
14072        &mut cx,
14073        completion_marked_buffer,
14074        vec![(completion_text, completion_text)],
14075        Arc::new(AtomicUsize::new(0)),
14076    )
14077    .await;
14078    cx.condition(|editor, _| editor.context_menu_visible())
14079        .await;
14080    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14081        editor
14082            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14083            .unwrap()
14084    });
14085    cx.assert_editor_state(expected);
14086    handle_resolve_completion_request(&mut cx, None).await;
14087    apply_additional_edits.await.unwrap();
14088
14089    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14090    let completion_text = "foo_and_bar";
14091    let initial_state = indoc! {"
14092        1. ooanbˇ
14093        2. zooanbˇ
14094        3. ooanbˇz
14095        4. zooanbˇz
14096        5. ooanˇ
14097        6. oanbˇ
14098
14099        ooanbˇ
14100    "};
14101    let completion_marked_buffer = indoc! {"
14102        1. ooanb
14103        2. zooanb
14104        3. ooanbz
14105        4. zooanbz
14106        5. ooan
14107        6. oanb
14108
14109        <ooanb|>
14110    "};
14111    let expected = indoc! {"
14112        1. foo_and_barˇ
14113        2. zfoo_and_barˇ
14114        3. foo_and_barˇz
14115        4. zfoo_and_barˇz
14116        5. ooanfoo_and_barˇ
14117        6. oanbfoo_and_barˇ
14118
14119        foo_and_barˇ
14120    "};
14121    cx.set_state(initial_state);
14122    cx.update_editor(|editor, window, cx| {
14123        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14124    });
14125    handle_completion_request_with_insert_and_replace(
14126        &mut cx,
14127        completion_marked_buffer,
14128        vec![(completion_text, completion_text)],
14129        Arc::new(AtomicUsize::new(0)),
14130    )
14131    .await;
14132    cx.condition(|editor, _| editor.context_menu_visible())
14133        .await;
14134    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14135        editor
14136            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14137            .unwrap()
14138    });
14139    cx.assert_editor_state(expected);
14140    handle_resolve_completion_request(&mut cx, None).await;
14141    apply_additional_edits.await.unwrap();
14142
14143    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14144    // (expects the same as if it was inserted at the end)
14145    let completion_text = "foo_and_bar";
14146    let initial_state = indoc! {"
14147        1. ooˇanb
14148        2. zooˇanb
14149        3. ooˇanbz
14150        4. zooˇanbz
14151
14152        ooˇanb
14153    "};
14154    let completion_marked_buffer = indoc! {"
14155        1. ooanb
14156        2. zooanb
14157        3. ooanbz
14158        4. zooanbz
14159
14160        <oo|anb>
14161    "};
14162    let expected = indoc! {"
14163        1. foo_and_barˇ
14164        2. zfoo_and_barˇ
14165        3. foo_and_barˇz
14166        4. zfoo_and_barˇz
14167
14168        foo_and_barˇ
14169    "};
14170    cx.set_state(initial_state);
14171    cx.update_editor(|editor, window, cx| {
14172        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14173    });
14174    handle_completion_request_with_insert_and_replace(
14175        &mut cx,
14176        completion_marked_buffer,
14177        vec![(completion_text, completion_text)],
14178        Arc::new(AtomicUsize::new(0)),
14179    )
14180    .await;
14181    cx.condition(|editor, _| editor.context_menu_visible())
14182        .await;
14183    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14184        editor
14185            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14186            .unwrap()
14187    });
14188    cx.assert_editor_state(expected);
14189    handle_resolve_completion_request(&mut cx, None).await;
14190    apply_additional_edits.await.unwrap();
14191}
14192
14193// This used to crash
14194#[gpui::test]
14195async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14196    init_test(cx, |_| {});
14197
14198    let buffer_text = indoc! {"
14199        fn main() {
14200            10.satu;
14201
14202            //
14203            // separate cursors so they open in different excerpts (manually reproducible)
14204            //
14205
14206            10.satu20;
14207        }
14208    "};
14209    let multibuffer_text_with_selections = indoc! {"
14210        fn main() {
14211            10.satuˇ;
14212
14213            //
14214
14215            //
14216
14217            10.satuˇ20;
14218        }
14219    "};
14220    let expected_multibuffer = indoc! {"
14221        fn main() {
14222            10.saturating_sub()ˇ;
14223
14224            //
14225
14226            //
14227
14228            10.saturating_sub()ˇ;
14229        }
14230    "};
14231
14232    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14233    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14234
14235    let fs = FakeFs::new(cx.executor());
14236    fs.insert_tree(
14237        path!("/a"),
14238        json!({
14239            "main.rs": buffer_text,
14240        }),
14241    )
14242    .await;
14243
14244    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14246    language_registry.add(rust_lang());
14247    let mut fake_servers = language_registry.register_fake_lsp(
14248        "Rust",
14249        FakeLspAdapter {
14250            capabilities: lsp::ServerCapabilities {
14251                completion_provider: Some(lsp::CompletionOptions {
14252                    resolve_provider: None,
14253                    ..lsp::CompletionOptions::default()
14254                }),
14255                ..lsp::ServerCapabilities::default()
14256            },
14257            ..FakeLspAdapter::default()
14258        },
14259    );
14260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14261    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14262    let buffer = project
14263        .update(cx, |project, cx| {
14264            project.open_local_buffer(path!("/a/main.rs"), cx)
14265        })
14266        .await
14267        .unwrap();
14268
14269    let multi_buffer = cx.new(|cx| {
14270        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14271        multi_buffer.push_excerpts(
14272            buffer.clone(),
14273            [ExcerptRange::new(0..first_excerpt_end)],
14274            cx,
14275        );
14276        multi_buffer.push_excerpts(
14277            buffer.clone(),
14278            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14279            cx,
14280        );
14281        multi_buffer
14282    });
14283
14284    let editor = workspace
14285        .update(cx, |_, window, cx| {
14286            cx.new(|cx| {
14287                Editor::new(
14288                    EditorMode::Full {
14289                        scale_ui_elements_with_buffer_font_size: false,
14290                        show_active_line_background: false,
14291                        sizing_behavior: SizingBehavior::Default,
14292                    },
14293                    multi_buffer.clone(),
14294                    Some(project.clone()),
14295                    window,
14296                    cx,
14297                )
14298            })
14299        })
14300        .unwrap();
14301
14302    let pane = workspace
14303        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14304        .unwrap();
14305    pane.update_in(cx, |pane, window, cx| {
14306        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14307    });
14308
14309    let fake_server = fake_servers.next().await.unwrap();
14310
14311    editor.update_in(cx, |editor, window, cx| {
14312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14313            s.select_ranges([
14314                Point::new(1, 11)..Point::new(1, 11),
14315                Point::new(7, 11)..Point::new(7, 11),
14316            ])
14317        });
14318
14319        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14320    });
14321
14322    editor.update_in(cx, |editor, window, cx| {
14323        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14324    });
14325
14326    fake_server
14327        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14328            let completion_item = lsp::CompletionItem {
14329                label: "saturating_sub()".into(),
14330                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14331                    lsp::InsertReplaceEdit {
14332                        new_text: "saturating_sub()".to_owned(),
14333                        insert: lsp::Range::new(
14334                            lsp::Position::new(7, 7),
14335                            lsp::Position::new(7, 11),
14336                        ),
14337                        replace: lsp::Range::new(
14338                            lsp::Position::new(7, 7),
14339                            lsp::Position::new(7, 13),
14340                        ),
14341                    },
14342                )),
14343                ..lsp::CompletionItem::default()
14344            };
14345
14346            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14347        })
14348        .next()
14349        .await
14350        .unwrap();
14351
14352    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14353        .await;
14354
14355    editor
14356        .update_in(cx, |editor, window, cx| {
14357            editor
14358                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14359                .unwrap()
14360        })
14361        .await
14362        .unwrap();
14363
14364    editor.update(cx, |editor, cx| {
14365        assert_text_with_selections(editor, expected_multibuffer, cx);
14366    })
14367}
14368
14369#[gpui::test]
14370async fn test_completion(cx: &mut TestAppContext) {
14371    init_test(cx, |_| {});
14372
14373    let mut cx = EditorLspTestContext::new_rust(
14374        lsp::ServerCapabilities {
14375            completion_provider: Some(lsp::CompletionOptions {
14376                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14377                resolve_provider: Some(true),
14378                ..Default::default()
14379            }),
14380            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14381            ..Default::default()
14382        },
14383        cx,
14384    )
14385    .await;
14386    let counter = Arc::new(AtomicUsize::new(0));
14387
14388    cx.set_state(indoc! {"
14389        oneˇ
14390        two
14391        three
14392    "});
14393    cx.simulate_keystroke(".");
14394    handle_completion_request(
14395        indoc! {"
14396            one.|<>
14397            two
14398            three
14399        "},
14400        vec!["first_completion", "second_completion"],
14401        true,
14402        counter.clone(),
14403        &mut cx,
14404    )
14405    .await;
14406    cx.condition(|editor, _| editor.context_menu_visible())
14407        .await;
14408    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14409
14410    let _handler = handle_signature_help_request(
14411        &mut cx,
14412        lsp::SignatureHelp {
14413            signatures: vec![lsp::SignatureInformation {
14414                label: "test signature".to_string(),
14415                documentation: None,
14416                parameters: Some(vec![lsp::ParameterInformation {
14417                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14418                    documentation: None,
14419                }]),
14420                active_parameter: None,
14421            }],
14422            active_signature: None,
14423            active_parameter: None,
14424        },
14425    );
14426    cx.update_editor(|editor, window, cx| {
14427        assert!(
14428            !editor.signature_help_state.is_shown(),
14429            "No signature help was called for"
14430        );
14431        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14432    });
14433    cx.run_until_parked();
14434    cx.update_editor(|editor, _, _| {
14435        assert!(
14436            !editor.signature_help_state.is_shown(),
14437            "No signature help should be shown when completions menu is open"
14438        );
14439    });
14440
14441    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14442        editor.context_menu_next(&Default::default(), window, cx);
14443        editor
14444            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14445            .unwrap()
14446    });
14447    cx.assert_editor_state(indoc! {"
14448        one.second_completionˇ
14449        two
14450        three
14451    "});
14452
14453    handle_resolve_completion_request(
14454        &mut cx,
14455        Some(vec![
14456            (
14457                //This overlaps with the primary completion edit which is
14458                //misbehavior from the LSP spec, test that we filter it out
14459                indoc! {"
14460                    one.second_ˇcompletion
14461                    two
14462                    threeˇ
14463                "},
14464                "overlapping additional edit",
14465            ),
14466            (
14467                indoc! {"
14468                    one.second_completion
14469                    two
14470                    threeˇ
14471                "},
14472                "\nadditional edit",
14473            ),
14474        ]),
14475    )
14476    .await;
14477    apply_additional_edits.await.unwrap();
14478    cx.assert_editor_state(indoc! {"
14479        one.second_completionˇ
14480        two
14481        three
14482        additional edit
14483    "});
14484
14485    cx.set_state(indoc! {"
14486        one.second_completion
14487        twoˇ
14488        threeˇ
14489        additional edit
14490    "});
14491    cx.simulate_keystroke(" ");
14492    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14493    cx.simulate_keystroke("s");
14494    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14495
14496    cx.assert_editor_state(indoc! {"
14497        one.second_completion
14498        two sˇ
14499        three sˇ
14500        additional edit
14501    "});
14502    handle_completion_request(
14503        indoc! {"
14504            one.second_completion
14505            two s
14506            three <s|>
14507            additional edit
14508        "},
14509        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14510        true,
14511        counter.clone(),
14512        &mut cx,
14513    )
14514    .await;
14515    cx.condition(|editor, _| editor.context_menu_visible())
14516        .await;
14517    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14518
14519    cx.simulate_keystroke("i");
14520
14521    handle_completion_request(
14522        indoc! {"
14523            one.second_completion
14524            two si
14525            three <si|>
14526            additional edit
14527        "},
14528        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14529        true,
14530        counter.clone(),
14531        &mut cx,
14532    )
14533    .await;
14534    cx.condition(|editor, _| editor.context_menu_visible())
14535        .await;
14536    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14537
14538    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14539        editor
14540            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14541            .unwrap()
14542    });
14543    cx.assert_editor_state(indoc! {"
14544        one.second_completion
14545        two sixth_completionˇ
14546        three sixth_completionˇ
14547        additional edit
14548    "});
14549
14550    apply_additional_edits.await.unwrap();
14551
14552    update_test_language_settings(&mut cx, |settings| {
14553        settings.defaults.show_completions_on_input = Some(false);
14554    });
14555    cx.set_state("editorˇ");
14556    cx.simulate_keystroke(".");
14557    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14558    cx.simulate_keystrokes("c l o");
14559    cx.assert_editor_state("editor.cloˇ");
14560    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14561    cx.update_editor(|editor, window, cx| {
14562        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14563    });
14564    handle_completion_request(
14565        "editor.<clo|>",
14566        vec!["close", "clobber"],
14567        true,
14568        counter.clone(),
14569        &mut cx,
14570    )
14571    .await;
14572    cx.condition(|editor, _| editor.context_menu_visible())
14573        .await;
14574    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14575
14576    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14577        editor
14578            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14579            .unwrap()
14580    });
14581    cx.assert_editor_state("editor.clobberˇ");
14582    handle_resolve_completion_request(&mut cx, None).await;
14583    apply_additional_edits.await.unwrap();
14584}
14585
14586#[gpui::test]
14587async fn test_completion_reuse(cx: &mut TestAppContext) {
14588    init_test(cx, |_| {});
14589
14590    let mut cx = EditorLspTestContext::new_rust(
14591        lsp::ServerCapabilities {
14592            completion_provider: Some(lsp::CompletionOptions {
14593                trigger_characters: Some(vec![".".to_string()]),
14594                ..Default::default()
14595            }),
14596            ..Default::default()
14597        },
14598        cx,
14599    )
14600    .await;
14601
14602    let counter = Arc::new(AtomicUsize::new(0));
14603    cx.set_state("objˇ");
14604    cx.simulate_keystroke(".");
14605
14606    // Initial completion request returns complete results
14607    let is_incomplete = false;
14608    handle_completion_request(
14609        "obj.|<>",
14610        vec!["a", "ab", "abc"],
14611        is_incomplete,
14612        counter.clone(),
14613        &mut cx,
14614    )
14615    .await;
14616    cx.run_until_parked();
14617    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14618    cx.assert_editor_state("obj.ˇ");
14619    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14620
14621    // Type "a" - filters existing completions
14622    cx.simulate_keystroke("a");
14623    cx.run_until_parked();
14624    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14625    cx.assert_editor_state("obj.aˇ");
14626    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14627
14628    // Type "b" - filters existing completions
14629    cx.simulate_keystroke("b");
14630    cx.run_until_parked();
14631    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14632    cx.assert_editor_state("obj.abˇ");
14633    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14634
14635    // Type "c" - filters existing completions
14636    cx.simulate_keystroke("c");
14637    cx.run_until_parked();
14638    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14639    cx.assert_editor_state("obj.abcˇ");
14640    check_displayed_completions(vec!["abc"], &mut cx);
14641
14642    // Backspace to delete "c" - filters existing completions
14643    cx.update_editor(|editor, window, cx| {
14644        editor.backspace(&Backspace, window, cx);
14645    });
14646    cx.run_until_parked();
14647    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14648    cx.assert_editor_state("obj.abˇ");
14649    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14650
14651    // Moving cursor to the left dismisses menu.
14652    cx.update_editor(|editor, window, cx| {
14653        editor.move_left(&MoveLeft, window, cx);
14654    });
14655    cx.run_until_parked();
14656    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14657    cx.assert_editor_state("obj.aˇb");
14658    cx.update_editor(|editor, _, _| {
14659        assert_eq!(editor.context_menu_visible(), false);
14660    });
14661
14662    // Type "b" - new request
14663    cx.simulate_keystroke("b");
14664    let is_incomplete = false;
14665    handle_completion_request(
14666        "obj.<ab|>a",
14667        vec!["ab", "abc"],
14668        is_incomplete,
14669        counter.clone(),
14670        &mut cx,
14671    )
14672    .await;
14673    cx.run_until_parked();
14674    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14675    cx.assert_editor_state("obj.abˇb");
14676    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14677
14678    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14679    cx.update_editor(|editor, window, cx| {
14680        editor.backspace(&Backspace, window, cx);
14681    });
14682    let is_incomplete = false;
14683    handle_completion_request(
14684        "obj.<a|>b",
14685        vec!["a", "ab", "abc"],
14686        is_incomplete,
14687        counter.clone(),
14688        &mut cx,
14689    )
14690    .await;
14691    cx.run_until_parked();
14692    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14693    cx.assert_editor_state("obj.aˇb");
14694    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14695
14696    // Backspace to delete "a" - dismisses menu.
14697    cx.update_editor(|editor, window, cx| {
14698        editor.backspace(&Backspace, window, cx);
14699    });
14700    cx.run_until_parked();
14701    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14702    cx.assert_editor_state("obj.ˇb");
14703    cx.update_editor(|editor, _, _| {
14704        assert_eq!(editor.context_menu_visible(), false);
14705    });
14706}
14707
14708#[gpui::test]
14709async fn test_word_completion(cx: &mut TestAppContext) {
14710    let lsp_fetch_timeout_ms = 10;
14711    init_test(cx, |language_settings| {
14712        language_settings.defaults.completions = Some(CompletionSettingsContent {
14713            words_min_length: Some(0),
14714            lsp_fetch_timeout_ms: Some(10),
14715            lsp_insert_mode: Some(LspInsertMode::Insert),
14716            ..Default::default()
14717        });
14718    });
14719
14720    let mut cx = EditorLspTestContext::new_rust(
14721        lsp::ServerCapabilities {
14722            completion_provider: Some(lsp::CompletionOptions {
14723                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14724                ..lsp::CompletionOptions::default()
14725            }),
14726            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14727            ..lsp::ServerCapabilities::default()
14728        },
14729        cx,
14730    )
14731    .await;
14732
14733    let throttle_completions = Arc::new(AtomicBool::new(false));
14734
14735    let lsp_throttle_completions = throttle_completions.clone();
14736    let _completion_requests_handler =
14737        cx.lsp
14738            .server
14739            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14740                let lsp_throttle_completions = lsp_throttle_completions.clone();
14741                let cx = cx.clone();
14742                async move {
14743                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14744                        cx.background_executor()
14745                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14746                            .await;
14747                    }
14748                    Ok(Some(lsp::CompletionResponse::Array(vec![
14749                        lsp::CompletionItem {
14750                            label: "first".into(),
14751                            ..lsp::CompletionItem::default()
14752                        },
14753                        lsp::CompletionItem {
14754                            label: "last".into(),
14755                            ..lsp::CompletionItem::default()
14756                        },
14757                    ])))
14758                }
14759            });
14760
14761    cx.set_state(indoc! {"
14762        oneˇ
14763        two
14764        three
14765    "});
14766    cx.simulate_keystroke(".");
14767    cx.executor().run_until_parked();
14768    cx.condition(|editor, _| editor.context_menu_visible())
14769        .await;
14770    cx.update_editor(|editor, window, cx| {
14771        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14772        {
14773            assert_eq!(
14774                completion_menu_entries(menu),
14775                &["first", "last"],
14776                "When LSP server is fast to reply, no fallback word completions are used"
14777            );
14778        } else {
14779            panic!("expected completion menu to be open");
14780        }
14781        editor.cancel(&Cancel, window, cx);
14782    });
14783    cx.executor().run_until_parked();
14784    cx.condition(|editor, _| !editor.context_menu_visible())
14785        .await;
14786
14787    throttle_completions.store(true, atomic::Ordering::Release);
14788    cx.simulate_keystroke(".");
14789    cx.executor()
14790        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14791    cx.executor().run_until_parked();
14792    cx.condition(|editor, _| editor.context_menu_visible())
14793        .await;
14794    cx.update_editor(|editor, _, _| {
14795        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14796        {
14797            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14798                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14799        } else {
14800            panic!("expected completion menu to be open");
14801        }
14802    });
14803}
14804
14805#[gpui::test]
14806async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14807    init_test(cx, |language_settings| {
14808        language_settings.defaults.completions = Some(CompletionSettingsContent {
14809            words: Some(WordsCompletionMode::Enabled),
14810            words_min_length: Some(0),
14811            lsp_insert_mode: Some(LspInsertMode::Insert),
14812            ..Default::default()
14813        });
14814    });
14815
14816    let mut cx = EditorLspTestContext::new_rust(
14817        lsp::ServerCapabilities {
14818            completion_provider: Some(lsp::CompletionOptions {
14819                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14820                ..lsp::CompletionOptions::default()
14821            }),
14822            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14823            ..lsp::ServerCapabilities::default()
14824        },
14825        cx,
14826    )
14827    .await;
14828
14829    let _completion_requests_handler =
14830        cx.lsp
14831            .server
14832            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14833                Ok(Some(lsp::CompletionResponse::Array(vec![
14834                    lsp::CompletionItem {
14835                        label: "first".into(),
14836                        ..lsp::CompletionItem::default()
14837                    },
14838                    lsp::CompletionItem {
14839                        label: "last".into(),
14840                        ..lsp::CompletionItem::default()
14841                    },
14842                ])))
14843            });
14844
14845    cx.set_state(indoc! {"ˇ
14846        first
14847        last
14848        second
14849    "});
14850    cx.simulate_keystroke(".");
14851    cx.executor().run_until_parked();
14852    cx.condition(|editor, _| editor.context_menu_visible())
14853        .await;
14854    cx.update_editor(|editor, _, _| {
14855        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14856        {
14857            assert_eq!(
14858                completion_menu_entries(menu),
14859                &["first", "last", "second"],
14860                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14861            );
14862        } else {
14863            panic!("expected completion menu to be open");
14864        }
14865    });
14866}
14867
14868#[gpui::test]
14869async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14870    init_test(cx, |language_settings| {
14871        language_settings.defaults.completions = Some(CompletionSettingsContent {
14872            words: Some(WordsCompletionMode::Disabled),
14873            words_min_length: Some(0),
14874            lsp_insert_mode: Some(LspInsertMode::Insert),
14875            ..Default::default()
14876        });
14877    });
14878
14879    let mut cx = EditorLspTestContext::new_rust(
14880        lsp::ServerCapabilities {
14881            completion_provider: Some(lsp::CompletionOptions {
14882                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14883                ..lsp::CompletionOptions::default()
14884            }),
14885            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14886            ..lsp::ServerCapabilities::default()
14887        },
14888        cx,
14889    )
14890    .await;
14891
14892    let _completion_requests_handler =
14893        cx.lsp
14894            .server
14895            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14896                panic!("LSP completions should not be queried when dealing with word completions")
14897            });
14898
14899    cx.set_state(indoc! {"ˇ
14900        first
14901        last
14902        second
14903    "});
14904    cx.update_editor(|editor, window, cx| {
14905        editor.show_word_completions(&ShowWordCompletions, window, cx);
14906    });
14907    cx.executor().run_until_parked();
14908    cx.condition(|editor, _| editor.context_menu_visible())
14909        .await;
14910    cx.update_editor(|editor, _, _| {
14911        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14912        {
14913            assert_eq!(
14914                completion_menu_entries(menu),
14915                &["first", "last", "second"],
14916                "`ShowWordCompletions` action should show word completions"
14917            );
14918        } else {
14919            panic!("expected completion menu to be open");
14920        }
14921    });
14922
14923    cx.simulate_keystroke("l");
14924    cx.executor().run_until_parked();
14925    cx.condition(|editor, _| editor.context_menu_visible())
14926        .await;
14927    cx.update_editor(|editor, _, _| {
14928        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14929        {
14930            assert_eq!(
14931                completion_menu_entries(menu),
14932                &["last"],
14933                "After showing word completions, further editing should filter them and not query the LSP"
14934            );
14935        } else {
14936            panic!("expected completion menu to be open");
14937        }
14938    });
14939}
14940
14941#[gpui::test]
14942async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14943    init_test(cx, |language_settings| {
14944        language_settings.defaults.completions = Some(CompletionSettingsContent {
14945            words_min_length: Some(0),
14946            lsp: Some(false),
14947            lsp_insert_mode: Some(LspInsertMode::Insert),
14948            ..Default::default()
14949        });
14950    });
14951
14952    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14953
14954    cx.set_state(indoc! {"ˇ
14955        0_usize
14956        let
14957        33
14958        4.5f32
14959    "});
14960    cx.update_editor(|editor, window, cx| {
14961        editor.show_completions(&ShowCompletions::default(), window, cx);
14962    });
14963    cx.executor().run_until_parked();
14964    cx.condition(|editor, _| editor.context_menu_visible())
14965        .await;
14966    cx.update_editor(|editor, window, cx| {
14967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14968        {
14969            assert_eq!(
14970                completion_menu_entries(menu),
14971                &["let"],
14972                "With no digits in the completion query, no digits should be in the word completions"
14973            );
14974        } else {
14975            panic!("expected completion menu to be open");
14976        }
14977        editor.cancel(&Cancel, window, cx);
14978    });
14979
14980    cx.set_state(indoc! {"14981        0_usize
14982        let
14983        3
14984        33.35f32
14985    "});
14986    cx.update_editor(|editor, window, cx| {
14987        editor.show_completions(&ShowCompletions::default(), window, cx);
14988    });
14989    cx.executor().run_until_parked();
14990    cx.condition(|editor, _| editor.context_menu_visible())
14991        .await;
14992    cx.update_editor(|editor, _, _| {
14993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14994        {
14995            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14996                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14997        } else {
14998            panic!("expected completion menu to be open");
14999        }
15000    });
15001}
15002
15003#[gpui::test]
15004async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15005    init_test(cx, |language_settings| {
15006        language_settings.defaults.completions = Some(CompletionSettingsContent {
15007            words: Some(WordsCompletionMode::Enabled),
15008            words_min_length: Some(3),
15009            lsp_insert_mode: Some(LspInsertMode::Insert),
15010            ..Default::default()
15011        });
15012    });
15013
15014    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15015    cx.set_state(indoc! {"ˇ
15016        wow
15017        wowen
15018        wowser
15019    "});
15020    cx.simulate_keystroke("w");
15021    cx.executor().run_until_parked();
15022    cx.update_editor(|editor, _, _| {
15023        if editor.context_menu.borrow_mut().is_some() {
15024            panic!(
15025                "expected completion menu to be hidden, as words completion threshold is not met"
15026            );
15027        }
15028    });
15029
15030    cx.update_editor(|editor, window, cx| {
15031        editor.show_word_completions(&ShowWordCompletions, window, cx);
15032    });
15033    cx.executor().run_until_parked();
15034    cx.update_editor(|editor, window, cx| {
15035        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15036        {
15037            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");
15038        } else {
15039            panic!("expected completion menu to be open after the word completions are called with an action");
15040        }
15041
15042        editor.cancel(&Cancel, window, cx);
15043    });
15044    cx.update_editor(|editor, _, _| {
15045        if editor.context_menu.borrow_mut().is_some() {
15046            panic!("expected completion menu to be hidden after canceling");
15047        }
15048    });
15049
15050    cx.simulate_keystroke("o");
15051    cx.executor().run_until_parked();
15052    cx.update_editor(|editor, _, _| {
15053        if editor.context_menu.borrow_mut().is_some() {
15054            panic!(
15055                "expected completion menu to be hidden, as words completion threshold is not met still"
15056            );
15057        }
15058    });
15059
15060    cx.simulate_keystroke("w");
15061    cx.executor().run_until_parked();
15062    cx.update_editor(|editor, _, _| {
15063        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15064        {
15065            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15066        } else {
15067            panic!("expected completion menu to be open after the word completions threshold is met");
15068        }
15069    });
15070}
15071
15072#[gpui::test]
15073async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15074    init_test(cx, |language_settings| {
15075        language_settings.defaults.completions = Some(CompletionSettingsContent {
15076            words: Some(WordsCompletionMode::Enabled),
15077            words_min_length: Some(0),
15078            lsp_insert_mode: Some(LspInsertMode::Insert),
15079            ..Default::default()
15080        });
15081    });
15082
15083    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15084    cx.update_editor(|editor, _, _| {
15085        editor.disable_word_completions();
15086    });
15087    cx.set_state(indoc! {"ˇ
15088        wow
15089        wowen
15090        wowser
15091    "});
15092    cx.simulate_keystroke("w");
15093    cx.executor().run_until_parked();
15094    cx.update_editor(|editor, _, _| {
15095        if editor.context_menu.borrow_mut().is_some() {
15096            panic!(
15097                "expected completion menu to be hidden, as words completion are disabled for this editor"
15098            );
15099        }
15100    });
15101
15102    cx.update_editor(|editor, window, cx| {
15103        editor.show_word_completions(&ShowWordCompletions, window, cx);
15104    });
15105    cx.executor().run_until_parked();
15106    cx.update_editor(|editor, _, _| {
15107        if editor.context_menu.borrow_mut().is_some() {
15108            panic!(
15109                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15110            );
15111        }
15112    });
15113}
15114
15115fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15116    let position = || lsp::Position {
15117        line: params.text_document_position.position.line,
15118        character: params.text_document_position.position.character,
15119    };
15120    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15121        range: lsp::Range {
15122            start: position(),
15123            end: position(),
15124        },
15125        new_text: text.to_string(),
15126    }))
15127}
15128
15129#[gpui::test]
15130async fn test_multiline_completion(cx: &mut TestAppContext) {
15131    init_test(cx, |_| {});
15132
15133    let fs = FakeFs::new(cx.executor());
15134    fs.insert_tree(
15135        path!("/a"),
15136        json!({
15137            "main.ts": "a",
15138        }),
15139    )
15140    .await;
15141
15142    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15144    let typescript_language = Arc::new(Language::new(
15145        LanguageConfig {
15146            name: "TypeScript".into(),
15147            matcher: LanguageMatcher {
15148                path_suffixes: vec!["ts".to_string()],
15149                ..LanguageMatcher::default()
15150            },
15151            line_comments: vec!["// ".into()],
15152            ..LanguageConfig::default()
15153        },
15154        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15155    ));
15156    language_registry.add(typescript_language.clone());
15157    let mut fake_servers = language_registry.register_fake_lsp(
15158        "TypeScript",
15159        FakeLspAdapter {
15160            capabilities: lsp::ServerCapabilities {
15161                completion_provider: Some(lsp::CompletionOptions {
15162                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15163                    ..lsp::CompletionOptions::default()
15164                }),
15165                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15166                ..lsp::ServerCapabilities::default()
15167            },
15168            // Emulate vtsls label generation
15169            label_for_completion: Some(Box::new(|item, _| {
15170                let text = if let Some(description) = item
15171                    .label_details
15172                    .as_ref()
15173                    .and_then(|label_details| label_details.description.as_ref())
15174                {
15175                    format!("{} {}", item.label, description)
15176                } else if let Some(detail) = &item.detail {
15177                    format!("{} {}", item.label, detail)
15178                } else {
15179                    item.label.clone()
15180                };
15181                Some(language::CodeLabel::plain(text, None))
15182            })),
15183            ..FakeLspAdapter::default()
15184        },
15185    );
15186    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15187    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15188    let worktree_id = workspace
15189        .update(cx, |workspace, _window, cx| {
15190            workspace.project().update(cx, |project, cx| {
15191                project.worktrees(cx).next().unwrap().read(cx).id()
15192            })
15193        })
15194        .unwrap();
15195    let _buffer = project
15196        .update(cx, |project, cx| {
15197            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15198        })
15199        .await
15200        .unwrap();
15201    let editor = workspace
15202        .update(cx, |workspace, window, cx| {
15203            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15204        })
15205        .unwrap()
15206        .await
15207        .unwrap()
15208        .downcast::<Editor>()
15209        .unwrap();
15210    let fake_server = fake_servers.next().await.unwrap();
15211
15212    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15213    let multiline_label_2 = "a\nb\nc\n";
15214    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15215    let multiline_description = "d\ne\nf\n";
15216    let multiline_detail_2 = "g\nh\ni\n";
15217
15218    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15219        move |params, _| async move {
15220            Ok(Some(lsp::CompletionResponse::Array(vec![
15221                lsp::CompletionItem {
15222                    label: multiline_label.to_string(),
15223                    text_edit: gen_text_edit(&params, "new_text_1"),
15224                    ..lsp::CompletionItem::default()
15225                },
15226                lsp::CompletionItem {
15227                    label: "single line label 1".to_string(),
15228                    detail: Some(multiline_detail.to_string()),
15229                    text_edit: gen_text_edit(&params, "new_text_2"),
15230                    ..lsp::CompletionItem::default()
15231                },
15232                lsp::CompletionItem {
15233                    label: "single line label 2".to_string(),
15234                    label_details: Some(lsp::CompletionItemLabelDetails {
15235                        description: Some(multiline_description.to_string()),
15236                        detail: None,
15237                    }),
15238                    text_edit: gen_text_edit(&params, "new_text_2"),
15239                    ..lsp::CompletionItem::default()
15240                },
15241                lsp::CompletionItem {
15242                    label: multiline_label_2.to_string(),
15243                    detail: Some(multiline_detail_2.to_string()),
15244                    text_edit: gen_text_edit(&params, "new_text_3"),
15245                    ..lsp::CompletionItem::default()
15246                },
15247                lsp::CompletionItem {
15248                    label: "Label with many     spaces and \t but without newlines".to_string(),
15249                    detail: Some(
15250                        "Details with many     spaces and \t but without newlines".to_string(),
15251                    ),
15252                    text_edit: gen_text_edit(&params, "new_text_4"),
15253                    ..lsp::CompletionItem::default()
15254                },
15255            ])))
15256        },
15257    );
15258
15259    editor.update_in(cx, |editor, window, cx| {
15260        cx.focus_self(window);
15261        editor.move_to_end(&MoveToEnd, window, cx);
15262        editor.handle_input(".", window, cx);
15263    });
15264    cx.run_until_parked();
15265    completion_handle.next().await.unwrap();
15266
15267    editor.update(cx, |editor, _| {
15268        assert!(editor.context_menu_visible());
15269        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15270        {
15271            let completion_labels = menu
15272                .completions
15273                .borrow()
15274                .iter()
15275                .map(|c| c.label.text.clone())
15276                .collect::<Vec<_>>();
15277            assert_eq!(
15278                completion_labels,
15279                &[
15280                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15281                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15282                    "single line label 2 d e f ",
15283                    "a b c g h i ",
15284                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15285                ],
15286                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15287            );
15288
15289            for completion in menu
15290                .completions
15291                .borrow()
15292                .iter() {
15293                    assert_eq!(
15294                        completion.label.filter_range,
15295                        0..completion.label.text.len(),
15296                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15297                    );
15298                }
15299        } else {
15300            panic!("expected completion menu to be open");
15301        }
15302    });
15303}
15304
15305#[gpui::test]
15306async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15307    init_test(cx, |_| {});
15308    let mut cx = EditorLspTestContext::new_rust(
15309        lsp::ServerCapabilities {
15310            completion_provider: Some(lsp::CompletionOptions {
15311                trigger_characters: Some(vec![".".to_string()]),
15312                ..Default::default()
15313            }),
15314            ..Default::default()
15315        },
15316        cx,
15317    )
15318    .await;
15319    cx.lsp
15320        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15321            Ok(Some(lsp::CompletionResponse::Array(vec![
15322                lsp::CompletionItem {
15323                    label: "first".into(),
15324                    ..Default::default()
15325                },
15326                lsp::CompletionItem {
15327                    label: "last".into(),
15328                    ..Default::default()
15329                },
15330            ])))
15331        });
15332    cx.set_state("variableˇ");
15333    cx.simulate_keystroke(".");
15334    cx.executor().run_until_parked();
15335
15336    cx.update_editor(|editor, _, _| {
15337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15338        {
15339            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15340        } else {
15341            panic!("expected completion menu to be open");
15342        }
15343    });
15344
15345    cx.update_editor(|editor, window, cx| {
15346        editor.move_page_down(&MovePageDown::default(), window, cx);
15347        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15348        {
15349            assert!(
15350                menu.selected_item == 1,
15351                "expected PageDown to select the last item from the context menu"
15352            );
15353        } else {
15354            panic!("expected completion menu to stay open after PageDown");
15355        }
15356    });
15357
15358    cx.update_editor(|editor, window, cx| {
15359        editor.move_page_up(&MovePageUp::default(), window, cx);
15360        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15361        {
15362            assert!(
15363                menu.selected_item == 0,
15364                "expected PageUp to select the first item from the context menu"
15365            );
15366        } else {
15367            panic!("expected completion menu to stay open after PageUp");
15368        }
15369    });
15370}
15371
15372#[gpui::test]
15373async fn test_as_is_completions(cx: &mut TestAppContext) {
15374    init_test(cx, |_| {});
15375    let mut cx = EditorLspTestContext::new_rust(
15376        lsp::ServerCapabilities {
15377            completion_provider: Some(lsp::CompletionOptions {
15378                ..Default::default()
15379            }),
15380            ..Default::default()
15381        },
15382        cx,
15383    )
15384    .await;
15385    cx.lsp
15386        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15387            Ok(Some(lsp::CompletionResponse::Array(vec![
15388                lsp::CompletionItem {
15389                    label: "unsafe".into(),
15390                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15391                        range: lsp::Range {
15392                            start: lsp::Position {
15393                                line: 1,
15394                                character: 2,
15395                            },
15396                            end: lsp::Position {
15397                                line: 1,
15398                                character: 3,
15399                            },
15400                        },
15401                        new_text: "unsafe".to_string(),
15402                    })),
15403                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15404                    ..Default::default()
15405                },
15406            ])))
15407        });
15408    cx.set_state("fn a() {}\n");
15409    cx.executor().run_until_parked();
15410    cx.update_editor(|editor, window, cx| {
15411        editor.show_completions(
15412            &ShowCompletions {
15413                trigger: Some("\n".into()),
15414            },
15415            window,
15416            cx,
15417        );
15418    });
15419    cx.executor().run_until_parked();
15420
15421    cx.update_editor(|editor, window, cx| {
15422        editor.confirm_completion(&Default::default(), window, cx)
15423    });
15424    cx.executor().run_until_parked();
15425    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15426}
15427
15428#[gpui::test]
15429async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15430    init_test(cx, |_| {});
15431    let language =
15432        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15433    let mut cx = EditorLspTestContext::new(
15434        language,
15435        lsp::ServerCapabilities {
15436            completion_provider: Some(lsp::CompletionOptions {
15437                ..lsp::CompletionOptions::default()
15438            }),
15439            ..lsp::ServerCapabilities::default()
15440        },
15441        cx,
15442    )
15443    .await;
15444
15445    cx.set_state(
15446        "#ifndef BAR_H
15447#define BAR_H
15448
15449#include <stdbool.h>
15450
15451int fn_branch(bool do_branch1, bool do_branch2);
15452
15453#endif // BAR_H
15454ˇ",
15455    );
15456    cx.executor().run_until_parked();
15457    cx.update_editor(|editor, window, cx| {
15458        editor.handle_input("#", window, cx);
15459    });
15460    cx.executor().run_until_parked();
15461    cx.update_editor(|editor, window, cx| {
15462        editor.handle_input("i", window, cx);
15463    });
15464    cx.executor().run_until_parked();
15465    cx.update_editor(|editor, window, cx| {
15466        editor.handle_input("n", window, cx);
15467    });
15468    cx.executor().run_until_parked();
15469    cx.assert_editor_state(
15470        "#ifndef BAR_H
15471#define BAR_H
15472
15473#include <stdbool.h>
15474
15475int fn_branch(bool do_branch1, bool do_branch2);
15476
15477#endif // BAR_H
15478#inˇ",
15479    );
15480
15481    cx.lsp
15482        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15483            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15484                is_incomplete: false,
15485                item_defaults: None,
15486                items: vec![lsp::CompletionItem {
15487                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15488                    label_details: Some(lsp::CompletionItemLabelDetails {
15489                        detail: Some("header".to_string()),
15490                        description: None,
15491                    }),
15492                    label: " include".to_string(),
15493                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15494                        range: lsp::Range {
15495                            start: lsp::Position {
15496                                line: 8,
15497                                character: 1,
15498                            },
15499                            end: lsp::Position {
15500                                line: 8,
15501                                character: 1,
15502                            },
15503                        },
15504                        new_text: "include \"$0\"".to_string(),
15505                    })),
15506                    sort_text: Some("40b67681include".to_string()),
15507                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15508                    filter_text: Some("include".to_string()),
15509                    insert_text: Some("include \"$0\"".to_string()),
15510                    ..lsp::CompletionItem::default()
15511                }],
15512            })))
15513        });
15514    cx.update_editor(|editor, window, cx| {
15515        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15516    });
15517    cx.executor().run_until_parked();
15518    cx.update_editor(|editor, window, cx| {
15519        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15520    });
15521    cx.executor().run_until_parked();
15522    cx.assert_editor_state(
15523        "#ifndef BAR_H
15524#define BAR_H
15525
15526#include <stdbool.h>
15527
15528int fn_branch(bool do_branch1, bool do_branch2);
15529
15530#endif // BAR_H
15531#include \"ˇ\"",
15532    );
15533
15534    cx.lsp
15535        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15536            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15537                is_incomplete: true,
15538                item_defaults: None,
15539                items: vec![lsp::CompletionItem {
15540                    kind: Some(lsp::CompletionItemKind::FILE),
15541                    label: "AGL/".to_string(),
15542                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15543                        range: lsp::Range {
15544                            start: lsp::Position {
15545                                line: 8,
15546                                character: 10,
15547                            },
15548                            end: lsp::Position {
15549                                line: 8,
15550                                character: 11,
15551                            },
15552                        },
15553                        new_text: "AGL/".to_string(),
15554                    })),
15555                    sort_text: Some("40b67681AGL/".to_string()),
15556                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15557                    filter_text: Some("AGL/".to_string()),
15558                    insert_text: Some("AGL/".to_string()),
15559                    ..lsp::CompletionItem::default()
15560                }],
15561            })))
15562        });
15563    cx.update_editor(|editor, window, cx| {
15564        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15565    });
15566    cx.executor().run_until_parked();
15567    cx.update_editor(|editor, window, cx| {
15568        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15569    });
15570    cx.executor().run_until_parked();
15571    cx.assert_editor_state(
15572        r##"#ifndef BAR_H
15573#define BAR_H
15574
15575#include <stdbool.h>
15576
15577int fn_branch(bool do_branch1, bool do_branch2);
15578
15579#endif // BAR_H
15580#include "AGL/ˇ"##,
15581    );
15582
15583    cx.update_editor(|editor, window, cx| {
15584        editor.handle_input("\"", window, cx);
15585    });
15586    cx.executor().run_until_parked();
15587    cx.assert_editor_state(
15588        r##"#ifndef BAR_H
15589#define BAR_H
15590
15591#include <stdbool.h>
15592
15593int fn_branch(bool do_branch1, bool do_branch2);
15594
15595#endif // BAR_H
15596#include "AGL/"ˇ"##,
15597    );
15598}
15599
15600#[gpui::test]
15601async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15602    init_test(cx, |_| {});
15603
15604    let mut cx = EditorLspTestContext::new_rust(
15605        lsp::ServerCapabilities {
15606            completion_provider: Some(lsp::CompletionOptions {
15607                trigger_characters: Some(vec![".".to_string()]),
15608                resolve_provider: Some(true),
15609                ..Default::default()
15610            }),
15611            ..Default::default()
15612        },
15613        cx,
15614    )
15615    .await;
15616
15617    cx.set_state("fn main() { let a = 2ˇ; }");
15618    cx.simulate_keystroke(".");
15619    let completion_item = lsp::CompletionItem {
15620        label: "Some".into(),
15621        kind: Some(lsp::CompletionItemKind::SNIPPET),
15622        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15623        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15624            kind: lsp::MarkupKind::Markdown,
15625            value: "```rust\nSome(2)\n```".to_string(),
15626        })),
15627        deprecated: Some(false),
15628        sort_text: Some("Some".to_string()),
15629        filter_text: Some("Some".to_string()),
15630        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15631        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15632            range: lsp::Range {
15633                start: lsp::Position {
15634                    line: 0,
15635                    character: 22,
15636                },
15637                end: lsp::Position {
15638                    line: 0,
15639                    character: 22,
15640                },
15641            },
15642            new_text: "Some(2)".to_string(),
15643        })),
15644        additional_text_edits: Some(vec![lsp::TextEdit {
15645            range: lsp::Range {
15646                start: lsp::Position {
15647                    line: 0,
15648                    character: 20,
15649                },
15650                end: lsp::Position {
15651                    line: 0,
15652                    character: 22,
15653                },
15654            },
15655            new_text: "".to_string(),
15656        }]),
15657        ..Default::default()
15658    };
15659
15660    let closure_completion_item = completion_item.clone();
15661    let counter = Arc::new(AtomicUsize::new(0));
15662    let counter_clone = counter.clone();
15663    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15664        let task_completion_item = closure_completion_item.clone();
15665        counter_clone.fetch_add(1, atomic::Ordering::Release);
15666        async move {
15667            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15668                is_incomplete: true,
15669                item_defaults: None,
15670                items: vec![task_completion_item],
15671            })))
15672        }
15673    });
15674
15675    cx.condition(|editor, _| editor.context_menu_visible())
15676        .await;
15677    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15678    assert!(request.next().await.is_some());
15679    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15680
15681    cx.simulate_keystrokes("S o m");
15682    cx.condition(|editor, _| editor.context_menu_visible())
15683        .await;
15684    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15685    assert!(request.next().await.is_some());
15686    assert!(request.next().await.is_some());
15687    assert!(request.next().await.is_some());
15688    request.close();
15689    assert!(request.next().await.is_none());
15690    assert_eq!(
15691        counter.load(atomic::Ordering::Acquire),
15692        4,
15693        "With the completions menu open, only one LSP request should happen per input"
15694    );
15695}
15696
15697#[gpui::test]
15698async fn test_toggle_comment(cx: &mut TestAppContext) {
15699    init_test(cx, |_| {});
15700    let mut cx = EditorTestContext::new(cx).await;
15701    let language = Arc::new(Language::new(
15702        LanguageConfig {
15703            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15704            ..Default::default()
15705        },
15706        Some(tree_sitter_rust::LANGUAGE.into()),
15707    ));
15708    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15709
15710    // If multiple selections intersect a line, the line is only toggled once.
15711    cx.set_state(indoc! {"
15712        fn a() {
15713            «//b();
15714            ˇ»// «c();
15715            //ˇ»  d();
15716        }
15717    "});
15718
15719    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15720
15721    cx.assert_editor_state(indoc! {"
15722        fn a() {
15723            «b();
15724            c();
15725            ˇ» d();
15726        }
15727    "});
15728
15729    // The comment prefix is inserted at the same column for every line in a
15730    // selection.
15731    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15732
15733    cx.assert_editor_state(indoc! {"
15734        fn a() {
15735            // «b();
15736            // c();
15737            ˇ»//  d();
15738        }
15739    "});
15740
15741    // If a selection ends at the beginning of a line, that line is not toggled.
15742    cx.set_selections_state(indoc! {"
15743        fn a() {
15744            // b();
15745            «// c();
15746        ˇ»    //  d();
15747        }
15748    "});
15749
15750    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15751
15752    cx.assert_editor_state(indoc! {"
15753        fn a() {
15754            // b();
15755            «c();
15756        ˇ»    //  d();
15757        }
15758    "});
15759
15760    // If a selection span a single line and is empty, the line is toggled.
15761    cx.set_state(indoc! {"
15762        fn a() {
15763            a();
15764            b();
15765        ˇ
15766        }
15767    "});
15768
15769    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15770
15771    cx.assert_editor_state(indoc! {"
15772        fn a() {
15773            a();
15774            b();
15775        //•ˇ
15776        }
15777    "});
15778
15779    // If a selection span multiple lines, empty lines are not toggled.
15780    cx.set_state(indoc! {"
15781        fn a() {
15782            «a();
15783
15784            c();ˇ»
15785        }
15786    "});
15787
15788    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15789
15790    cx.assert_editor_state(indoc! {"
15791        fn a() {
15792            // «a();
15793
15794            // c();ˇ»
15795        }
15796    "});
15797
15798    // If a selection includes multiple comment prefixes, all lines are uncommented.
15799    cx.set_state(indoc! {"
15800        fn a() {
15801            «// a();
15802            /// b();
15803            //! c();ˇ»
15804        }
15805    "});
15806
15807    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15808
15809    cx.assert_editor_state(indoc! {"
15810        fn a() {
15811            «a();
15812            b();
15813            c();ˇ»
15814        }
15815    "});
15816}
15817
15818#[gpui::test]
15819async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15820    init_test(cx, |_| {});
15821    let mut cx = EditorTestContext::new(cx).await;
15822    let language = Arc::new(Language::new(
15823        LanguageConfig {
15824            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15825            ..Default::default()
15826        },
15827        Some(tree_sitter_rust::LANGUAGE.into()),
15828    ));
15829    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15830
15831    let toggle_comments = &ToggleComments {
15832        advance_downwards: false,
15833        ignore_indent: true,
15834    };
15835
15836    // If multiple selections intersect a line, the line is only toggled once.
15837    cx.set_state(indoc! {"
15838        fn a() {
15839        //    «b();
15840        //    c();
15841        //    ˇ» d();
15842        }
15843    "});
15844
15845    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15846
15847    cx.assert_editor_state(indoc! {"
15848        fn a() {
15849            «b();
15850            c();
15851            ˇ» d();
15852        }
15853    "});
15854
15855    // The comment prefix is inserted at the beginning of each line
15856    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15857
15858    cx.assert_editor_state(indoc! {"
15859        fn a() {
15860        //    «b();
15861        //    c();
15862        //    ˇ» d();
15863        }
15864    "});
15865
15866    // If a selection ends at the beginning of a line, that line is not toggled.
15867    cx.set_selections_state(indoc! {"
15868        fn a() {
15869        //    b();
15870        //    «c();
15871        ˇ»//     d();
15872        }
15873    "});
15874
15875    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15876
15877    cx.assert_editor_state(indoc! {"
15878        fn a() {
15879        //    b();
15880            «c();
15881        ˇ»//     d();
15882        }
15883    "});
15884
15885    // If a selection span a single line and is empty, the line is toggled.
15886    cx.set_state(indoc! {"
15887        fn a() {
15888            a();
15889            b();
15890        ˇ
15891        }
15892    "});
15893
15894    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15895
15896    cx.assert_editor_state(indoc! {"
15897        fn a() {
15898            a();
15899            b();
15900        //ˇ
15901        }
15902    "});
15903
15904    // If a selection span multiple lines, empty lines are not toggled.
15905    cx.set_state(indoc! {"
15906        fn a() {
15907            «a();
15908
15909            c();ˇ»
15910        }
15911    "});
15912
15913    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15914
15915    cx.assert_editor_state(indoc! {"
15916        fn a() {
15917        //    «a();
15918
15919        //    c();ˇ»
15920        }
15921    "});
15922
15923    // If a selection includes multiple comment prefixes, all lines are uncommented.
15924    cx.set_state(indoc! {"
15925        fn a() {
15926        //    «a();
15927        ///    b();
15928        //!    c();ˇ»
15929        }
15930    "});
15931
15932    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15933
15934    cx.assert_editor_state(indoc! {"
15935        fn a() {
15936            «a();
15937            b();
15938            c();ˇ»
15939        }
15940    "});
15941}
15942
15943#[gpui::test]
15944async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15945    init_test(cx, |_| {});
15946
15947    let language = Arc::new(Language::new(
15948        LanguageConfig {
15949            line_comments: vec!["// ".into()],
15950            ..Default::default()
15951        },
15952        Some(tree_sitter_rust::LANGUAGE.into()),
15953    ));
15954
15955    let mut cx = EditorTestContext::new(cx).await;
15956
15957    cx.language_registry().add(language.clone());
15958    cx.update_buffer(|buffer, cx| {
15959        buffer.set_language(Some(language), cx);
15960    });
15961
15962    let toggle_comments = &ToggleComments {
15963        advance_downwards: true,
15964        ignore_indent: false,
15965    };
15966
15967    // Single cursor on one line -> advance
15968    // Cursor moves horizontally 3 characters as well on non-blank line
15969    cx.set_state(indoc!(
15970        "fn a() {
15971             ˇdog();
15972             cat();
15973        }"
15974    ));
15975    cx.update_editor(|editor, window, cx| {
15976        editor.toggle_comments(toggle_comments, window, cx);
15977    });
15978    cx.assert_editor_state(indoc!(
15979        "fn a() {
15980             // dog();
15981             catˇ();
15982        }"
15983    ));
15984
15985    // Single selection on one line -> don't advance
15986    cx.set_state(indoc!(
15987        "fn a() {
15988             «dog()ˇ»;
15989             cat();
15990        }"
15991    ));
15992    cx.update_editor(|editor, window, cx| {
15993        editor.toggle_comments(toggle_comments, window, cx);
15994    });
15995    cx.assert_editor_state(indoc!(
15996        "fn a() {
15997             // «dog()ˇ»;
15998             cat();
15999        }"
16000    ));
16001
16002    // Multiple cursors on one line -> advance
16003    cx.set_state(indoc!(
16004        "fn a() {
16005             ˇdˇog();
16006             cat();
16007        }"
16008    ));
16009    cx.update_editor(|editor, window, cx| {
16010        editor.toggle_comments(toggle_comments, window, cx);
16011    });
16012    cx.assert_editor_state(indoc!(
16013        "fn a() {
16014             // dog();
16015             catˇ(ˇ);
16016        }"
16017    ));
16018
16019    // Multiple cursors on one line, with selection -> don't advance
16020    cx.set_state(indoc!(
16021        "fn a() {
16022             ˇdˇog«()ˇ»;
16023             cat();
16024        }"
16025    ));
16026    cx.update_editor(|editor, window, cx| {
16027        editor.toggle_comments(toggle_comments, window, cx);
16028    });
16029    cx.assert_editor_state(indoc!(
16030        "fn a() {
16031             // ˇdˇog«()ˇ»;
16032             cat();
16033        }"
16034    ));
16035
16036    // Single cursor on one line -> advance
16037    // Cursor moves to column 0 on blank line
16038    cx.set_state(indoc!(
16039        "fn a() {
16040             ˇdog();
16041
16042             cat();
16043        }"
16044    ));
16045    cx.update_editor(|editor, window, cx| {
16046        editor.toggle_comments(toggle_comments, window, cx);
16047    });
16048    cx.assert_editor_state(indoc!(
16049        "fn a() {
16050             // dog();
16051        ˇ
16052             cat();
16053        }"
16054    ));
16055
16056    // Single cursor on one line -> advance
16057    // Cursor starts and ends at column 0
16058    cx.set_state(indoc!(
16059        "fn a() {
16060         ˇ    dog();
16061             cat();
16062        }"
16063    ));
16064    cx.update_editor(|editor, window, cx| {
16065        editor.toggle_comments(toggle_comments, window, cx);
16066    });
16067    cx.assert_editor_state(indoc!(
16068        "fn a() {
16069             // dog();
16070         ˇ    cat();
16071        }"
16072    ));
16073}
16074
16075#[gpui::test]
16076async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16077    init_test(cx, |_| {});
16078
16079    let mut cx = EditorTestContext::new(cx).await;
16080
16081    let html_language = Arc::new(
16082        Language::new(
16083            LanguageConfig {
16084                name: "HTML".into(),
16085                block_comment: Some(BlockCommentConfig {
16086                    start: "<!-- ".into(),
16087                    prefix: "".into(),
16088                    end: " -->".into(),
16089                    tab_size: 0,
16090                }),
16091                ..Default::default()
16092            },
16093            Some(tree_sitter_html::LANGUAGE.into()),
16094        )
16095        .with_injection_query(
16096            r#"
16097            (script_element
16098                (raw_text) @injection.content
16099                (#set! injection.language "javascript"))
16100            "#,
16101        )
16102        .unwrap(),
16103    );
16104
16105    let javascript_language = Arc::new(Language::new(
16106        LanguageConfig {
16107            name: "JavaScript".into(),
16108            line_comments: vec!["// ".into()],
16109            ..Default::default()
16110        },
16111        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16112    ));
16113
16114    cx.language_registry().add(html_language.clone());
16115    cx.language_registry().add(javascript_language);
16116    cx.update_buffer(|buffer, cx| {
16117        buffer.set_language(Some(html_language), cx);
16118    });
16119
16120    // Toggle comments for empty selections
16121    cx.set_state(
16122        &r#"
16123            <p>A</p>ˇ
16124            <p>B</p>ˇ
16125            <p>C</p>ˇ
16126        "#
16127        .unindent(),
16128    );
16129    cx.update_editor(|editor, window, cx| {
16130        editor.toggle_comments(&ToggleComments::default(), window, cx)
16131    });
16132    cx.assert_editor_state(
16133        &r#"
16134            <!-- <p>A</p>ˇ -->
16135            <!-- <p>B</p>ˇ -->
16136            <!-- <p>C</p>ˇ -->
16137        "#
16138        .unindent(),
16139    );
16140    cx.update_editor(|editor, window, cx| {
16141        editor.toggle_comments(&ToggleComments::default(), window, cx)
16142    });
16143    cx.assert_editor_state(
16144        &r#"
16145            <p>A</p>ˇ
16146            <p>B</p>ˇ
16147            <p>C</p>ˇ
16148        "#
16149        .unindent(),
16150    );
16151
16152    // Toggle comments for mixture of empty and non-empty selections, where
16153    // multiple selections occupy a given line.
16154    cx.set_state(
16155        &r#"
16156            <p>A«</p>
16157            <p>ˇ»B</p>ˇ
16158            <p>C«</p>
16159            <p>ˇ»D</p>ˇ
16160        "#
16161        .unindent(),
16162    );
16163
16164    cx.update_editor(|editor, window, cx| {
16165        editor.toggle_comments(&ToggleComments::default(), window, cx)
16166    });
16167    cx.assert_editor_state(
16168        &r#"
16169            <!-- <p>A«</p>
16170            <p>ˇ»B</p>ˇ -->
16171            <!-- <p>C«</p>
16172            <p>ˇ»D</p>ˇ -->
16173        "#
16174        .unindent(),
16175    );
16176    cx.update_editor(|editor, window, cx| {
16177        editor.toggle_comments(&ToggleComments::default(), window, cx)
16178    });
16179    cx.assert_editor_state(
16180        &r#"
16181            <p>A«</p>
16182            <p>ˇ»B</p>ˇ
16183            <p>C«</p>
16184            <p>ˇ»D</p>ˇ
16185        "#
16186        .unindent(),
16187    );
16188
16189    // Toggle comments when different languages are active for different
16190    // selections.
16191    cx.set_state(
16192        &r#"
16193            ˇ<script>
16194                ˇvar x = new Y();
16195            ˇ</script>
16196        "#
16197        .unindent(),
16198    );
16199    cx.executor().run_until_parked();
16200    cx.update_editor(|editor, window, cx| {
16201        editor.toggle_comments(&ToggleComments::default(), window, cx)
16202    });
16203    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16204    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16205    cx.assert_editor_state(
16206        &r#"
16207            <!-- ˇ<script> -->
16208                // ˇvar x = new Y();
16209            <!-- ˇ</script> -->
16210        "#
16211        .unindent(),
16212    );
16213}
16214
16215#[gpui::test]
16216fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16217    init_test(cx, |_| {});
16218
16219    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16220    let multibuffer = cx.new(|cx| {
16221        let mut multibuffer = MultiBuffer::new(ReadWrite);
16222        multibuffer.push_excerpts(
16223            buffer.clone(),
16224            [
16225                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16226                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16227            ],
16228            cx,
16229        );
16230        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16231        multibuffer
16232    });
16233
16234    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16235    editor.update_in(cx, |editor, window, cx| {
16236        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16238            s.select_ranges([
16239                Point::new(0, 0)..Point::new(0, 0),
16240                Point::new(1, 0)..Point::new(1, 0),
16241            ])
16242        });
16243
16244        editor.handle_input("X", window, cx);
16245        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16246        assert_eq!(
16247            editor.selections.ranges(&editor.display_snapshot(cx)),
16248            [
16249                Point::new(0, 1)..Point::new(0, 1),
16250                Point::new(1, 1)..Point::new(1, 1),
16251            ]
16252        );
16253
16254        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16256            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16257        });
16258        editor.backspace(&Default::default(), window, cx);
16259        assert_eq!(editor.text(cx), "Xa\nbbb");
16260        assert_eq!(
16261            editor.selections.ranges(&editor.display_snapshot(cx)),
16262            [Point::new(1, 0)..Point::new(1, 0)]
16263        );
16264
16265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16266            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16267        });
16268        editor.backspace(&Default::default(), window, cx);
16269        assert_eq!(editor.text(cx), "X\nbb");
16270        assert_eq!(
16271            editor.selections.ranges(&editor.display_snapshot(cx)),
16272            [Point::new(0, 1)..Point::new(0, 1)]
16273        );
16274    });
16275}
16276
16277#[gpui::test]
16278fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16279    init_test(cx, |_| {});
16280
16281    let markers = vec![('[', ']').into(), ('(', ')').into()];
16282    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16283        indoc! {"
16284            [aaaa
16285            (bbbb]
16286            cccc)",
16287        },
16288        markers.clone(),
16289    );
16290    let excerpt_ranges = markers.into_iter().map(|marker| {
16291        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16292        ExcerptRange::new(context)
16293    });
16294    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16295    let multibuffer = cx.new(|cx| {
16296        let mut multibuffer = MultiBuffer::new(ReadWrite);
16297        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16298        multibuffer
16299    });
16300
16301    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16302    editor.update_in(cx, |editor, window, cx| {
16303        let (expected_text, selection_ranges) = marked_text_ranges(
16304            indoc! {"
16305                aaaa
16306                bˇbbb
16307                bˇbbˇb
16308                cccc"
16309            },
16310            true,
16311        );
16312        assert_eq!(editor.text(cx), expected_text);
16313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16314            s.select_ranges(selection_ranges)
16315        });
16316
16317        editor.handle_input("X", window, cx);
16318
16319        let (expected_text, expected_selections) = marked_text_ranges(
16320            indoc! {"
16321                aaaa
16322                bXˇbbXb
16323                bXˇbbXˇb
16324                cccc"
16325            },
16326            false,
16327        );
16328        assert_eq!(editor.text(cx), expected_text);
16329        assert_eq!(
16330            editor.selections.ranges(&editor.display_snapshot(cx)),
16331            expected_selections
16332        );
16333
16334        editor.newline(&Newline, window, cx);
16335        let (expected_text, expected_selections) = marked_text_ranges(
16336            indoc! {"
16337                aaaa
16338                bX
16339                ˇbbX
16340                b
16341                bX
16342                ˇbbX
16343                ˇb
16344                cccc"
16345            },
16346            false,
16347        );
16348        assert_eq!(editor.text(cx), expected_text);
16349        assert_eq!(
16350            editor.selections.ranges(&editor.display_snapshot(cx)),
16351            expected_selections
16352        );
16353    });
16354}
16355
16356#[gpui::test]
16357fn test_refresh_selections(cx: &mut TestAppContext) {
16358    init_test(cx, |_| {});
16359
16360    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16361    let mut excerpt1_id = None;
16362    let multibuffer = cx.new(|cx| {
16363        let mut multibuffer = MultiBuffer::new(ReadWrite);
16364        excerpt1_id = multibuffer
16365            .push_excerpts(
16366                buffer.clone(),
16367                [
16368                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16369                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16370                ],
16371                cx,
16372            )
16373            .into_iter()
16374            .next();
16375        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16376        multibuffer
16377    });
16378
16379    let editor = cx.add_window(|window, cx| {
16380        let mut editor = build_editor(multibuffer.clone(), window, cx);
16381        let snapshot = editor.snapshot(window, cx);
16382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16383            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16384        });
16385        editor.begin_selection(
16386            Point::new(2, 1).to_display_point(&snapshot),
16387            true,
16388            1,
16389            window,
16390            cx,
16391        );
16392        assert_eq!(
16393            editor.selections.ranges(&editor.display_snapshot(cx)),
16394            [
16395                Point::new(1, 3)..Point::new(1, 3),
16396                Point::new(2, 1)..Point::new(2, 1),
16397            ]
16398        );
16399        editor
16400    });
16401
16402    // Refreshing selections is a no-op when excerpts haven't changed.
16403    _ = editor.update(cx, |editor, window, cx| {
16404        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16405        assert_eq!(
16406            editor.selections.ranges(&editor.display_snapshot(cx)),
16407            [
16408                Point::new(1, 3)..Point::new(1, 3),
16409                Point::new(2, 1)..Point::new(2, 1),
16410            ]
16411        );
16412    });
16413
16414    multibuffer.update(cx, |multibuffer, cx| {
16415        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16416    });
16417    _ = editor.update(cx, |editor, window, cx| {
16418        // Removing an excerpt causes the first selection to become degenerate.
16419        assert_eq!(
16420            editor.selections.ranges(&editor.display_snapshot(cx)),
16421            [
16422                Point::new(0, 0)..Point::new(0, 0),
16423                Point::new(0, 1)..Point::new(0, 1)
16424            ]
16425        );
16426
16427        // Refreshing selections will relocate the first selection to the original buffer
16428        // location.
16429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16430        assert_eq!(
16431            editor.selections.ranges(&editor.display_snapshot(cx)),
16432            [
16433                Point::new(0, 1)..Point::new(0, 1),
16434                Point::new(0, 3)..Point::new(0, 3)
16435            ]
16436        );
16437        assert!(editor.selections.pending_anchor().is_some());
16438    });
16439}
16440
16441#[gpui::test]
16442fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16443    init_test(cx, |_| {});
16444
16445    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16446    let mut excerpt1_id = None;
16447    let multibuffer = cx.new(|cx| {
16448        let mut multibuffer = MultiBuffer::new(ReadWrite);
16449        excerpt1_id = multibuffer
16450            .push_excerpts(
16451                buffer.clone(),
16452                [
16453                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16454                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16455                ],
16456                cx,
16457            )
16458            .into_iter()
16459            .next();
16460        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16461        multibuffer
16462    });
16463
16464    let editor = cx.add_window(|window, cx| {
16465        let mut editor = build_editor(multibuffer.clone(), window, cx);
16466        let snapshot = editor.snapshot(window, cx);
16467        editor.begin_selection(
16468            Point::new(1, 3).to_display_point(&snapshot),
16469            false,
16470            1,
16471            window,
16472            cx,
16473        );
16474        assert_eq!(
16475            editor.selections.ranges(&editor.display_snapshot(cx)),
16476            [Point::new(1, 3)..Point::new(1, 3)]
16477        );
16478        editor
16479    });
16480
16481    multibuffer.update(cx, |multibuffer, cx| {
16482        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16483    });
16484    _ = editor.update(cx, |editor, window, cx| {
16485        assert_eq!(
16486            editor.selections.ranges(&editor.display_snapshot(cx)),
16487            [Point::new(0, 0)..Point::new(0, 0)]
16488        );
16489
16490        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16492        assert_eq!(
16493            editor.selections.ranges(&editor.display_snapshot(cx)),
16494            [Point::new(0, 3)..Point::new(0, 3)]
16495        );
16496        assert!(editor.selections.pending_anchor().is_some());
16497    });
16498}
16499
16500#[gpui::test]
16501async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16502    init_test(cx, |_| {});
16503
16504    let language = Arc::new(
16505        Language::new(
16506            LanguageConfig {
16507                brackets: BracketPairConfig {
16508                    pairs: vec![
16509                        BracketPair {
16510                            start: "{".to_string(),
16511                            end: "}".to_string(),
16512                            close: true,
16513                            surround: true,
16514                            newline: true,
16515                        },
16516                        BracketPair {
16517                            start: "/* ".to_string(),
16518                            end: " */".to_string(),
16519                            close: true,
16520                            surround: true,
16521                            newline: true,
16522                        },
16523                    ],
16524                    ..Default::default()
16525                },
16526                ..Default::default()
16527            },
16528            Some(tree_sitter_rust::LANGUAGE.into()),
16529        )
16530        .with_indents_query("")
16531        .unwrap(),
16532    );
16533
16534    let text = concat!(
16535        "{   }\n",     //
16536        "  x\n",       //
16537        "  /*   */\n", //
16538        "x\n",         //
16539        "{{} }\n",     //
16540    );
16541
16542    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16543    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16544    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16545    editor
16546        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16547        .await;
16548
16549    editor.update_in(cx, |editor, window, cx| {
16550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16551            s.select_display_ranges([
16552                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16553                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16554                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16555            ])
16556        });
16557        editor.newline(&Newline, window, cx);
16558
16559        assert_eq!(
16560            editor.buffer().read(cx).read(cx).text(),
16561            concat!(
16562                "{ \n",    // Suppress rustfmt
16563                "\n",      //
16564                "}\n",     //
16565                "  x\n",   //
16566                "  /* \n", //
16567                "  \n",    //
16568                "  */\n",  //
16569                "x\n",     //
16570                "{{} \n",  //
16571                "}\n",     //
16572            )
16573        );
16574    });
16575}
16576
16577#[gpui::test]
16578fn test_highlighted_ranges(cx: &mut TestAppContext) {
16579    init_test(cx, |_| {});
16580
16581    let editor = cx.add_window(|window, cx| {
16582        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16583        build_editor(buffer, window, cx)
16584    });
16585
16586    _ = editor.update(cx, |editor, window, cx| {
16587        struct Type1;
16588        struct Type2;
16589
16590        let buffer = editor.buffer.read(cx).snapshot(cx);
16591
16592        let anchor_range =
16593            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16594
16595        editor.highlight_background::<Type1>(
16596            &[
16597                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16598                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16599                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16600                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16601            ],
16602            |_| Hsla::red(),
16603            cx,
16604        );
16605        editor.highlight_background::<Type2>(
16606            &[
16607                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16608                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16609                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16610                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16611            ],
16612            |_| Hsla::green(),
16613            cx,
16614        );
16615
16616        let snapshot = editor.snapshot(window, cx);
16617        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16618            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16619            &snapshot,
16620            cx.theme(),
16621        );
16622        assert_eq!(
16623            highlighted_ranges,
16624            &[
16625                (
16626                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16627                    Hsla::green(),
16628                ),
16629                (
16630                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16631                    Hsla::red(),
16632                ),
16633                (
16634                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16635                    Hsla::green(),
16636                ),
16637                (
16638                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16639                    Hsla::red(),
16640                ),
16641            ]
16642        );
16643        assert_eq!(
16644            editor.sorted_background_highlights_in_range(
16645                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16646                &snapshot,
16647                cx.theme(),
16648            ),
16649            &[(
16650                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16651                Hsla::red(),
16652            )]
16653        );
16654    });
16655}
16656
16657#[gpui::test]
16658async fn test_following(cx: &mut TestAppContext) {
16659    init_test(cx, |_| {});
16660
16661    let fs = FakeFs::new(cx.executor());
16662    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16663
16664    let buffer = project.update(cx, |project, cx| {
16665        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16666        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16667    });
16668    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16669    let follower = cx.update(|cx| {
16670        cx.open_window(
16671            WindowOptions {
16672                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16673                    gpui::Point::new(px(0.), px(0.)),
16674                    gpui::Point::new(px(10.), px(80.)),
16675                ))),
16676                ..Default::default()
16677            },
16678            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16679        )
16680        .unwrap()
16681    });
16682
16683    let is_still_following = Rc::new(RefCell::new(true));
16684    let follower_edit_event_count = Rc::new(RefCell::new(0));
16685    let pending_update = Rc::new(RefCell::new(None));
16686    let leader_entity = leader.root(cx).unwrap();
16687    let follower_entity = follower.root(cx).unwrap();
16688    _ = follower.update(cx, {
16689        let update = pending_update.clone();
16690        let is_still_following = is_still_following.clone();
16691        let follower_edit_event_count = follower_edit_event_count.clone();
16692        |_, window, cx| {
16693            cx.subscribe_in(
16694                &leader_entity,
16695                window,
16696                move |_, leader, event, window, cx| {
16697                    leader.read(cx).add_event_to_update_proto(
16698                        event,
16699                        &mut update.borrow_mut(),
16700                        window,
16701                        cx,
16702                    );
16703                },
16704            )
16705            .detach();
16706
16707            cx.subscribe_in(
16708                &follower_entity,
16709                window,
16710                move |_, _, event: &EditorEvent, _window, _cx| {
16711                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16712                        *is_still_following.borrow_mut() = false;
16713                    }
16714
16715                    if let EditorEvent::BufferEdited = event {
16716                        *follower_edit_event_count.borrow_mut() += 1;
16717                    }
16718                },
16719            )
16720            .detach();
16721        }
16722    });
16723
16724    // Update the selections only
16725    _ = leader.update(cx, |leader, window, cx| {
16726        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16727            s.select_ranges([1..1])
16728        });
16729    });
16730    follower
16731        .update(cx, |follower, window, cx| {
16732            follower.apply_update_proto(
16733                &project,
16734                pending_update.borrow_mut().take().unwrap(),
16735                window,
16736                cx,
16737            )
16738        })
16739        .unwrap()
16740        .await
16741        .unwrap();
16742    _ = follower.update(cx, |follower, _, cx| {
16743        assert_eq!(
16744            follower.selections.ranges(&follower.display_snapshot(cx)),
16745            vec![1..1]
16746        );
16747    });
16748    assert!(*is_still_following.borrow());
16749    assert_eq!(*follower_edit_event_count.borrow(), 0);
16750
16751    // Update the scroll position only
16752    _ = leader.update(cx, |leader, window, cx| {
16753        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16754    });
16755    follower
16756        .update(cx, |follower, window, cx| {
16757            follower.apply_update_proto(
16758                &project,
16759                pending_update.borrow_mut().take().unwrap(),
16760                window,
16761                cx,
16762            )
16763        })
16764        .unwrap()
16765        .await
16766        .unwrap();
16767    assert_eq!(
16768        follower
16769            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16770            .unwrap(),
16771        gpui::Point::new(1.5, 3.5)
16772    );
16773    assert!(*is_still_following.borrow());
16774    assert_eq!(*follower_edit_event_count.borrow(), 0);
16775
16776    // Update the selections and scroll position. The follower's scroll position is updated
16777    // via autoscroll, not via the leader's exact scroll position.
16778    _ = leader.update(cx, |leader, window, cx| {
16779        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16780            s.select_ranges([0..0])
16781        });
16782        leader.request_autoscroll(Autoscroll::newest(), cx);
16783        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16784    });
16785    follower
16786        .update(cx, |follower, window, cx| {
16787            follower.apply_update_proto(
16788                &project,
16789                pending_update.borrow_mut().take().unwrap(),
16790                window,
16791                cx,
16792            )
16793        })
16794        .unwrap()
16795        .await
16796        .unwrap();
16797    _ = follower.update(cx, |follower, _, cx| {
16798        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16799        assert_eq!(
16800            follower.selections.ranges(&follower.display_snapshot(cx)),
16801            vec![0..0]
16802        );
16803    });
16804    assert!(*is_still_following.borrow());
16805
16806    // Creating a pending selection that precedes another selection
16807    _ = leader.update(cx, |leader, window, cx| {
16808        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16809            s.select_ranges([1..1])
16810        });
16811        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16812    });
16813    follower
16814        .update(cx, |follower, window, cx| {
16815            follower.apply_update_proto(
16816                &project,
16817                pending_update.borrow_mut().take().unwrap(),
16818                window,
16819                cx,
16820            )
16821        })
16822        .unwrap()
16823        .await
16824        .unwrap();
16825    _ = follower.update(cx, |follower, _, cx| {
16826        assert_eq!(
16827            follower.selections.ranges(&follower.display_snapshot(cx)),
16828            vec![0..0, 1..1]
16829        );
16830    });
16831    assert!(*is_still_following.borrow());
16832
16833    // Extend the pending selection so that it surrounds another selection
16834    _ = leader.update(cx, |leader, window, cx| {
16835        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16836    });
16837    follower
16838        .update(cx, |follower, window, cx| {
16839            follower.apply_update_proto(
16840                &project,
16841                pending_update.borrow_mut().take().unwrap(),
16842                window,
16843                cx,
16844            )
16845        })
16846        .unwrap()
16847        .await
16848        .unwrap();
16849    _ = follower.update(cx, |follower, _, cx| {
16850        assert_eq!(
16851            follower.selections.ranges(&follower.display_snapshot(cx)),
16852            vec![0..2]
16853        );
16854    });
16855
16856    // Scrolling locally breaks the follow
16857    _ = follower.update(cx, |follower, window, cx| {
16858        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16859        follower.set_scroll_anchor(
16860            ScrollAnchor {
16861                anchor: top_anchor,
16862                offset: gpui::Point::new(0.0, 0.5),
16863            },
16864            window,
16865            cx,
16866        );
16867    });
16868    assert!(!(*is_still_following.borrow()));
16869}
16870
16871#[gpui::test]
16872async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16873    init_test(cx, |_| {});
16874
16875    let fs = FakeFs::new(cx.executor());
16876    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16877    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16878    let pane = workspace
16879        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16880        .unwrap();
16881
16882    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16883
16884    let leader = pane.update_in(cx, |_, window, cx| {
16885        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16886        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16887    });
16888
16889    // Start following the editor when it has no excerpts.
16890    let mut state_message =
16891        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16892    let workspace_entity = workspace.root(cx).unwrap();
16893    let follower_1 = cx
16894        .update_window(*workspace.deref(), |_, window, cx| {
16895            Editor::from_state_proto(
16896                workspace_entity,
16897                ViewId {
16898                    creator: CollaboratorId::PeerId(PeerId::default()),
16899                    id: 0,
16900                },
16901                &mut state_message,
16902                window,
16903                cx,
16904            )
16905        })
16906        .unwrap()
16907        .unwrap()
16908        .await
16909        .unwrap();
16910
16911    let update_message = Rc::new(RefCell::new(None));
16912    follower_1.update_in(cx, {
16913        let update = update_message.clone();
16914        |_, window, cx| {
16915            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16916                leader.read(cx).add_event_to_update_proto(
16917                    event,
16918                    &mut update.borrow_mut(),
16919                    window,
16920                    cx,
16921                );
16922            })
16923            .detach();
16924        }
16925    });
16926
16927    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16928        (
16929            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16930            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16931        )
16932    });
16933
16934    // Insert some excerpts.
16935    leader.update(cx, |leader, cx| {
16936        leader.buffer.update(cx, |multibuffer, cx| {
16937            multibuffer.set_excerpts_for_path(
16938                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16939                buffer_1.clone(),
16940                vec![
16941                    Point::row_range(0..3),
16942                    Point::row_range(1..6),
16943                    Point::row_range(12..15),
16944                ],
16945                0,
16946                cx,
16947            );
16948            multibuffer.set_excerpts_for_path(
16949                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16950                buffer_2.clone(),
16951                vec![Point::row_range(0..6), Point::row_range(8..12)],
16952                0,
16953                cx,
16954            );
16955        });
16956    });
16957
16958    // Apply the update of adding the excerpts.
16959    follower_1
16960        .update_in(cx, |follower, window, cx| {
16961            follower.apply_update_proto(
16962                &project,
16963                update_message.borrow().clone().unwrap(),
16964                window,
16965                cx,
16966            )
16967        })
16968        .await
16969        .unwrap();
16970    assert_eq!(
16971        follower_1.update(cx, |editor, cx| editor.text(cx)),
16972        leader.update(cx, |editor, cx| editor.text(cx))
16973    );
16974    update_message.borrow_mut().take();
16975
16976    // Start following separately after it already has excerpts.
16977    let mut state_message =
16978        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16979    let workspace_entity = workspace.root(cx).unwrap();
16980    let follower_2 = cx
16981        .update_window(*workspace.deref(), |_, window, cx| {
16982            Editor::from_state_proto(
16983                workspace_entity,
16984                ViewId {
16985                    creator: CollaboratorId::PeerId(PeerId::default()),
16986                    id: 0,
16987                },
16988                &mut state_message,
16989                window,
16990                cx,
16991            )
16992        })
16993        .unwrap()
16994        .unwrap()
16995        .await
16996        .unwrap();
16997    assert_eq!(
16998        follower_2.update(cx, |editor, cx| editor.text(cx)),
16999        leader.update(cx, |editor, cx| editor.text(cx))
17000    );
17001
17002    // Remove some excerpts.
17003    leader.update(cx, |leader, cx| {
17004        leader.buffer.update(cx, |multibuffer, cx| {
17005            let excerpt_ids = multibuffer.excerpt_ids();
17006            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17007            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17008        });
17009    });
17010
17011    // Apply the update of removing the excerpts.
17012    follower_1
17013        .update_in(cx, |follower, window, cx| {
17014            follower.apply_update_proto(
17015                &project,
17016                update_message.borrow().clone().unwrap(),
17017                window,
17018                cx,
17019            )
17020        })
17021        .await
17022        .unwrap();
17023    follower_2
17024        .update_in(cx, |follower, window, cx| {
17025            follower.apply_update_proto(
17026                &project,
17027                update_message.borrow().clone().unwrap(),
17028                window,
17029                cx,
17030            )
17031        })
17032        .await
17033        .unwrap();
17034    update_message.borrow_mut().take();
17035    assert_eq!(
17036        follower_1.update(cx, |editor, cx| editor.text(cx)),
17037        leader.update(cx, |editor, cx| editor.text(cx))
17038    );
17039}
17040
17041#[gpui::test]
17042async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17043    init_test(cx, |_| {});
17044
17045    let mut cx = EditorTestContext::new(cx).await;
17046    let lsp_store =
17047        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17048
17049    cx.set_state(indoc! {"
17050        ˇfn func(abc def: i32) -> u32 {
17051        }
17052    "});
17053
17054    cx.update(|_, cx| {
17055        lsp_store.update(cx, |lsp_store, cx| {
17056            lsp_store
17057                .update_diagnostics(
17058                    LanguageServerId(0),
17059                    lsp::PublishDiagnosticsParams {
17060                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17061                        version: None,
17062                        diagnostics: vec![
17063                            lsp::Diagnostic {
17064                                range: lsp::Range::new(
17065                                    lsp::Position::new(0, 11),
17066                                    lsp::Position::new(0, 12),
17067                                ),
17068                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17069                                ..Default::default()
17070                            },
17071                            lsp::Diagnostic {
17072                                range: lsp::Range::new(
17073                                    lsp::Position::new(0, 12),
17074                                    lsp::Position::new(0, 15),
17075                                ),
17076                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17077                                ..Default::default()
17078                            },
17079                            lsp::Diagnostic {
17080                                range: lsp::Range::new(
17081                                    lsp::Position::new(0, 25),
17082                                    lsp::Position::new(0, 28),
17083                                ),
17084                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17085                                ..Default::default()
17086                            },
17087                        ],
17088                    },
17089                    None,
17090                    DiagnosticSourceKind::Pushed,
17091                    &[],
17092                    cx,
17093                )
17094                .unwrap()
17095        });
17096    });
17097
17098    executor.run_until_parked();
17099
17100    cx.update_editor(|editor, window, cx| {
17101        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17102    });
17103
17104    cx.assert_editor_state(indoc! {"
17105        fn func(abc def: i32) -> ˇu32 {
17106        }
17107    "});
17108
17109    cx.update_editor(|editor, window, cx| {
17110        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17111    });
17112
17113    cx.assert_editor_state(indoc! {"
17114        fn func(abc ˇdef: i32) -> u32 {
17115        }
17116    "});
17117
17118    cx.update_editor(|editor, window, cx| {
17119        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17120    });
17121
17122    cx.assert_editor_state(indoc! {"
17123        fn func(abcˇ def: i32) -> u32 {
17124        }
17125    "});
17126
17127    cx.update_editor(|editor, window, cx| {
17128        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17129    });
17130
17131    cx.assert_editor_state(indoc! {"
17132        fn func(abc def: i32) -> ˇu32 {
17133        }
17134    "});
17135}
17136
17137#[gpui::test]
17138async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17139    init_test(cx, |_| {});
17140
17141    let mut cx = EditorTestContext::new(cx).await;
17142
17143    let diff_base = r#"
17144        use some::mod;
17145
17146        const A: u32 = 42;
17147
17148        fn main() {
17149            println!("hello");
17150
17151            println!("world");
17152        }
17153        "#
17154    .unindent();
17155
17156    // Edits are modified, removed, modified, added
17157    cx.set_state(
17158        &r#"
17159        use some::modified;
17160
17161        ˇ
17162        fn main() {
17163            println!("hello there");
17164
17165            println!("around the");
17166            println!("world");
17167        }
17168        "#
17169        .unindent(),
17170    );
17171
17172    cx.set_head_text(&diff_base);
17173    executor.run_until_parked();
17174
17175    cx.update_editor(|editor, window, cx| {
17176        //Wrap around the bottom of the buffer
17177        for _ in 0..3 {
17178            editor.go_to_next_hunk(&GoToHunk, window, cx);
17179        }
17180    });
17181
17182    cx.assert_editor_state(
17183        &r#"
17184        ˇuse some::modified;
17185
17186
17187        fn main() {
17188            println!("hello there");
17189
17190            println!("around the");
17191            println!("world");
17192        }
17193        "#
17194        .unindent(),
17195    );
17196
17197    cx.update_editor(|editor, window, cx| {
17198        //Wrap around the top of the buffer
17199        for _ in 0..2 {
17200            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17201        }
17202    });
17203
17204    cx.assert_editor_state(
17205        &r#"
17206        use some::modified;
17207
17208
17209        fn main() {
17210        ˇ    println!("hello there");
17211
17212            println!("around the");
17213            println!("world");
17214        }
17215        "#
17216        .unindent(),
17217    );
17218
17219    cx.update_editor(|editor, window, cx| {
17220        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17221    });
17222
17223    cx.assert_editor_state(
17224        &r#"
17225        use some::modified;
17226
17227        ˇ
17228        fn main() {
17229            println!("hello there");
17230
17231            println!("around the");
17232            println!("world");
17233        }
17234        "#
17235        .unindent(),
17236    );
17237
17238    cx.update_editor(|editor, window, cx| {
17239        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17240    });
17241
17242    cx.assert_editor_state(
17243        &r#"
17244        ˇuse some::modified;
17245
17246
17247        fn main() {
17248            println!("hello there");
17249
17250            println!("around the");
17251            println!("world");
17252        }
17253        "#
17254        .unindent(),
17255    );
17256
17257    cx.update_editor(|editor, window, cx| {
17258        for _ in 0..2 {
17259            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17260        }
17261    });
17262
17263    cx.assert_editor_state(
17264        &r#"
17265        use some::modified;
17266
17267
17268        fn main() {
17269        ˇ    println!("hello there");
17270
17271            println!("around the");
17272            println!("world");
17273        }
17274        "#
17275        .unindent(),
17276    );
17277
17278    cx.update_editor(|editor, window, cx| {
17279        editor.fold(&Fold, window, cx);
17280    });
17281
17282    cx.update_editor(|editor, window, cx| {
17283        editor.go_to_next_hunk(&GoToHunk, window, cx);
17284    });
17285
17286    cx.assert_editor_state(
17287        &r#"
17288        ˇuse some::modified;
17289
17290
17291        fn main() {
17292            println!("hello there");
17293
17294            println!("around the");
17295            println!("world");
17296        }
17297        "#
17298        .unindent(),
17299    );
17300}
17301
17302#[test]
17303fn test_split_words() {
17304    fn split(text: &str) -> Vec<&str> {
17305        split_words(text).collect()
17306    }
17307
17308    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17309    assert_eq!(split("hello_world"), &["hello_", "world"]);
17310    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17311    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17312    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17313    assert_eq!(split("helloworld"), &["helloworld"]);
17314
17315    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17316}
17317
17318#[gpui::test]
17319async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17320    init_test(cx, |_| {});
17321
17322    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17323    let mut assert = |before, after| {
17324        let _state_context = cx.set_state(before);
17325        cx.run_until_parked();
17326        cx.update_editor(|editor, window, cx| {
17327            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17328        });
17329        cx.run_until_parked();
17330        cx.assert_editor_state(after);
17331    };
17332
17333    // Outside bracket jumps to outside of matching bracket
17334    assert("console.logˇ(var);", "console.log(var)ˇ;");
17335    assert("console.log(var)ˇ;", "console.logˇ(var);");
17336
17337    // Inside bracket jumps to inside of matching bracket
17338    assert("console.log(ˇvar);", "console.log(varˇ);");
17339    assert("console.log(varˇ);", "console.log(ˇvar);");
17340
17341    // When outside a bracket and inside, favor jumping to the inside bracket
17342    assert(
17343        "console.log('foo', [1, 2, 3]ˇ);",
17344        "console.log(ˇ'foo', [1, 2, 3]);",
17345    );
17346    assert(
17347        "console.log(ˇ'foo', [1, 2, 3]);",
17348        "console.log('foo', [1, 2, 3]ˇ);",
17349    );
17350
17351    // Bias forward if two options are equally likely
17352    assert(
17353        "let result = curried_fun()ˇ();",
17354        "let result = curried_fun()()ˇ;",
17355    );
17356
17357    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17358    assert(
17359        indoc! {"
17360            function test() {
17361                console.log('test')ˇ
17362            }"},
17363        indoc! {"
17364            function test() {
17365                console.logˇ('test')
17366            }"},
17367    );
17368}
17369
17370#[gpui::test]
17371async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17372    init_test(cx, |_| {});
17373
17374    let fs = FakeFs::new(cx.executor());
17375    fs.insert_tree(
17376        path!("/a"),
17377        json!({
17378            "main.rs": "fn main() { let a = 5; }",
17379            "other.rs": "// Test file",
17380        }),
17381    )
17382    .await;
17383    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17384
17385    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17386    language_registry.add(Arc::new(Language::new(
17387        LanguageConfig {
17388            name: "Rust".into(),
17389            matcher: LanguageMatcher {
17390                path_suffixes: vec!["rs".to_string()],
17391                ..Default::default()
17392            },
17393            brackets: BracketPairConfig {
17394                pairs: vec![BracketPair {
17395                    start: "{".to_string(),
17396                    end: "}".to_string(),
17397                    close: true,
17398                    surround: true,
17399                    newline: true,
17400                }],
17401                disabled_scopes_by_bracket_ix: Vec::new(),
17402            },
17403            ..Default::default()
17404        },
17405        Some(tree_sitter_rust::LANGUAGE.into()),
17406    )));
17407    let mut fake_servers = language_registry.register_fake_lsp(
17408        "Rust",
17409        FakeLspAdapter {
17410            capabilities: lsp::ServerCapabilities {
17411                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17412                    first_trigger_character: "{".to_string(),
17413                    more_trigger_character: None,
17414                }),
17415                ..Default::default()
17416            },
17417            ..Default::default()
17418        },
17419    );
17420
17421    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17422
17423    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17424
17425    let worktree_id = workspace
17426        .update(cx, |workspace, _, cx| {
17427            workspace.project().update(cx, |project, cx| {
17428                project.worktrees(cx).next().unwrap().read(cx).id()
17429            })
17430        })
17431        .unwrap();
17432
17433    let buffer = project
17434        .update(cx, |project, cx| {
17435            project.open_local_buffer(path!("/a/main.rs"), cx)
17436        })
17437        .await
17438        .unwrap();
17439    let editor_handle = workspace
17440        .update(cx, |workspace, window, cx| {
17441            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17442        })
17443        .unwrap()
17444        .await
17445        .unwrap()
17446        .downcast::<Editor>()
17447        .unwrap();
17448
17449    cx.executor().start_waiting();
17450    let fake_server = fake_servers.next().await.unwrap();
17451
17452    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17453        |params, _| async move {
17454            assert_eq!(
17455                params.text_document_position.text_document.uri,
17456                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17457            );
17458            assert_eq!(
17459                params.text_document_position.position,
17460                lsp::Position::new(0, 21),
17461            );
17462
17463            Ok(Some(vec![lsp::TextEdit {
17464                new_text: "]".to_string(),
17465                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17466            }]))
17467        },
17468    );
17469
17470    editor_handle.update_in(cx, |editor, window, cx| {
17471        window.focus(&editor.focus_handle(cx));
17472        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17473            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17474        });
17475        editor.handle_input("{", window, cx);
17476    });
17477
17478    cx.executor().run_until_parked();
17479
17480    buffer.update(cx, |buffer, _| {
17481        assert_eq!(
17482            buffer.text(),
17483            "fn main() { let a = {5}; }",
17484            "No extra braces from on type formatting should appear in the buffer"
17485        )
17486    });
17487}
17488
17489#[gpui::test(iterations = 20, seeds(31))]
17490async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17491    init_test(cx, |_| {});
17492
17493    let mut cx = EditorLspTestContext::new_rust(
17494        lsp::ServerCapabilities {
17495            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17496                first_trigger_character: ".".to_string(),
17497                more_trigger_character: None,
17498            }),
17499            ..Default::default()
17500        },
17501        cx,
17502    )
17503    .await;
17504
17505    cx.update_buffer(|buffer, _| {
17506        // This causes autoindent to be async.
17507        buffer.set_sync_parse_timeout(Duration::ZERO)
17508    });
17509
17510    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17511    cx.simulate_keystroke("\n");
17512    cx.run_until_parked();
17513
17514    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17515    let mut request =
17516        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17517            let buffer_cloned = buffer_cloned.clone();
17518            async move {
17519                buffer_cloned.update(&mut cx, |buffer, _| {
17520                    assert_eq!(
17521                        buffer.text(),
17522                        "fn c() {\n    d()\n        .\n}\n",
17523                        "OnTypeFormatting should triggered after autoindent applied"
17524                    )
17525                })?;
17526
17527                Ok(Some(vec![]))
17528            }
17529        });
17530
17531    cx.simulate_keystroke(".");
17532    cx.run_until_parked();
17533
17534    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17535    assert!(request.next().await.is_some());
17536    request.close();
17537    assert!(request.next().await.is_none());
17538}
17539
17540#[gpui::test]
17541async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17542    init_test(cx, |_| {});
17543
17544    let fs = FakeFs::new(cx.executor());
17545    fs.insert_tree(
17546        path!("/a"),
17547        json!({
17548            "main.rs": "fn main() { let a = 5; }",
17549            "other.rs": "// Test file",
17550        }),
17551    )
17552    .await;
17553
17554    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17555
17556    let server_restarts = Arc::new(AtomicUsize::new(0));
17557    let closure_restarts = Arc::clone(&server_restarts);
17558    let language_server_name = "test language server";
17559    let language_name: LanguageName = "Rust".into();
17560
17561    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17562    language_registry.add(Arc::new(Language::new(
17563        LanguageConfig {
17564            name: language_name.clone(),
17565            matcher: LanguageMatcher {
17566                path_suffixes: vec!["rs".to_string()],
17567                ..Default::default()
17568            },
17569            ..Default::default()
17570        },
17571        Some(tree_sitter_rust::LANGUAGE.into()),
17572    )));
17573    let mut fake_servers = language_registry.register_fake_lsp(
17574        "Rust",
17575        FakeLspAdapter {
17576            name: language_server_name,
17577            initialization_options: Some(json!({
17578                "testOptionValue": true
17579            })),
17580            initializer: Some(Box::new(move |fake_server| {
17581                let task_restarts = Arc::clone(&closure_restarts);
17582                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17583                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17584                    futures::future::ready(Ok(()))
17585                });
17586            })),
17587            ..Default::default()
17588        },
17589    );
17590
17591    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17592    let _buffer = project
17593        .update(cx, |project, cx| {
17594            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17595        })
17596        .await
17597        .unwrap();
17598    let _fake_server = fake_servers.next().await.unwrap();
17599    update_test_language_settings(cx, |language_settings| {
17600        language_settings.languages.0.insert(
17601            language_name.clone().0,
17602            LanguageSettingsContent {
17603                tab_size: NonZeroU32::new(8),
17604                ..Default::default()
17605            },
17606        );
17607    });
17608    cx.executor().run_until_parked();
17609    assert_eq!(
17610        server_restarts.load(atomic::Ordering::Acquire),
17611        0,
17612        "Should not restart LSP server on an unrelated change"
17613    );
17614
17615    update_test_project_settings(cx, |project_settings| {
17616        project_settings.lsp.insert(
17617            "Some other server name".into(),
17618            LspSettings {
17619                binary: None,
17620                settings: None,
17621                initialization_options: Some(json!({
17622                    "some other init value": false
17623                })),
17624                enable_lsp_tasks: false,
17625                fetch: None,
17626            },
17627        );
17628    });
17629    cx.executor().run_until_parked();
17630    assert_eq!(
17631        server_restarts.load(atomic::Ordering::Acquire),
17632        0,
17633        "Should not restart LSP server on an unrelated LSP settings change"
17634    );
17635
17636    update_test_project_settings(cx, |project_settings| {
17637        project_settings.lsp.insert(
17638            language_server_name.into(),
17639            LspSettings {
17640                binary: None,
17641                settings: None,
17642                initialization_options: Some(json!({
17643                    "anotherInitValue": false
17644                })),
17645                enable_lsp_tasks: false,
17646                fetch: None,
17647            },
17648        );
17649    });
17650    cx.executor().run_until_parked();
17651    assert_eq!(
17652        server_restarts.load(atomic::Ordering::Acquire),
17653        1,
17654        "Should restart LSP server on a related LSP settings change"
17655    );
17656
17657    update_test_project_settings(cx, |project_settings| {
17658        project_settings.lsp.insert(
17659            language_server_name.into(),
17660            LspSettings {
17661                binary: None,
17662                settings: None,
17663                initialization_options: Some(json!({
17664                    "anotherInitValue": false
17665                })),
17666                enable_lsp_tasks: false,
17667                fetch: None,
17668            },
17669        );
17670    });
17671    cx.executor().run_until_parked();
17672    assert_eq!(
17673        server_restarts.load(atomic::Ordering::Acquire),
17674        1,
17675        "Should not restart LSP server on a related LSP settings change that is the same"
17676    );
17677
17678    update_test_project_settings(cx, |project_settings| {
17679        project_settings.lsp.insert(
17680            language_server_name.into(),
17681            LspSettings {
17682                binary: None,
17683                settings: None,
17684                initialization_options: None,
17685                enable_lsp_tasks: false,
17686                fetch: None,
17687            },
17688        );
17689    });
17690    cx.executor().run_until_parked();
17691    assert_eq!(
17692        server_restarts.load(atomic::Ordering::Acquire),
17693        2,
17694        "Should restart LSP server on another related LSP settings change"
17695    );
17696}
17697
17698#[gpui::test]
17699async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17700    init_test(cx, |_| {});
17701
17702    let mut cx = EditorLspTestContext::new_rust(
17703        lsp::ServerCapabilities {
17704            completion_provider: Some(lsp::CompletionOptions {
17705                trigger_characters: Some(vec![".".to_string()]),
17706                resolve_provider: Some(true),
17707                ..Default::default()
17708            }),
17709            ..Default::default()
17710        },
17711        cx,
17712    )
17713    .await;
17714
17715    cx.set_state("fn main() { let a = 2ˇ; }");
17716    cx.simulate_keystroke(".");
17717    let completion_item = lsp::CompletionItem {
17718        label: "some".into(),
17719        kind: Some(lsp::CompletionItemKind::SNIPPET),
17720        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17721        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17722            kind: lsp::MarkupKind::Markdown,
17723            value: "```rust\nSome(2)\n```".to_string(),
17724        })),
17725        deprecated: Some(false),
17726        sort_text: Some("fffffff2".to_string()),
17727        filter_text: Some("some".to_string()),
17728        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17729        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17730            range: lsp::Range {
17731                start: lsp::Position {
17732                    line: 0,
17733                    character: 22,
17734                },
17735                end: lsp::Position {
17736                    line: 0,
17737                    character: 22,
17738                },
17739            },
17740            new_text: "Some(2)".to_string(),
17741        })),
17742        additional_text_edits: Some(vec![lsp::TextEdit {
17743            range: lsp::Range {
17744                start: lsp::Position {
17745                    line: 0,
17746                    character: 20,
17747                },
17748                end: lsp::Position {
17749                    line: 0,
17750                    character: 22,
17751                },
17752            },
17753            new_text: "".to_string(),
17754        }]),
17755        ..Default::default()
17756    };
17757
17758    let closure_completion_item = completion_item.clone();
17759    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17760        let task_completion_item = closure_completion_item.clone();
17761        async move {
17762            Ok(Some(lsp::CompletionResponse::Array(vec![
17763                task_completion_item,
17764            ])))
17765        }
17766    });
17767
17768    request.next().await;
17769
17770    cx.condition(|editor, _| editor.context_menu_visible())
17771        .await;
17772    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17773        editor
17774            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17775            .unwrap()
17776    });
17777    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17778
17779    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17780        let task_completion_item = completion_item.clone();
17781        async move { Ok(task_completion_item) }
17782    })
17783    .next()
17784    .await
17785    .unwrap();
17786    apply_additional_edits.await.unwrap();
17787    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17788}
17789
17790#[gpui::test]
17791async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17792    init_test(cx, |_| {});
17793
17794    let mut cx = EditorLspTestContext::new_rust(
17795        lsp::ServerCapabilities {
17796            completion_provider: Some(lsp::CompletionOptions {
17797                trigger_characters: Some(vec![".".to_string()]),
17798                resolve_provider: Some(true),
17799                ..Default::default()
17800            }),
17801            ..Default::default()
17802        },
17803        cx,
17804    )
17805    .await;
17806
17807    cx.set_state("fn main() { let a = 2ˇ; }");
17808    cx.simulate_keystroke(".");
17809
17810    let item1 = lsp::CompletionItem {
17811        label: "method id()".to_string(),
17812        filter_text: Some("id".to_string()),
17813        detail: None,
17814        documentation: None,
17815        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17816            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17817            new_text: ".id".to_string(),
17818        })),
17819        ..lsp::CompletionItem::default()
17820    };
17821
17822    let item2 = lsp::CompletionItem {
17823        label: "other".to_string(),
17824        filter_text: Some("other".to_string()),
17825        detail: None,
17826        documentation: None,
17827        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17828            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17829            new_text: ".other".to_string(),
17830        })),
17831        ..lsp::CompletionItem::default()
17832    };
17833
17834    let item1 = item1.clone();
17835    cx.set_request_handler::<lsp::request::Completion, _, _>({
17836        let item1 = item1.clone();
17837        move |_, _, _| {
17838            let item1 = item1.clone();
17839            let item2 = item2.clone();
17840            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17841        }
17842    })
17843    .next()
17844    .await;
17845
17846    cx.condition(|editor, _| editor.context_menu_visible())
17847        .await;
17848    cx.update_editor(|editor, _, _| {
17849        let context_menu = editor.context_menu.borrow_mut();
17850        let context_menu = context_menu
17851            .as_ref()
17852            .expect("Should have the context menu deployed");
17853        match context_menu {
17854            CodeContextMenu::Completions(completions_menu) => {
17855                let completions = completions_menu.completions.borrow_mut();
17856                assert_eq!(
17857                    completions
17858                        .iter()
17859                        .map(|completion| &completion.label.text)
17860                        .collect::<Vec<_>>(),
17861                    vec!["method id()", "other"]
17862                )
17863            }
17864            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17865        }
17866    });
17867
17868    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17869        let item1 = item1.clone();
17870        move |_, item_to_resolve, _| {
17871            let item1 = item1.clone();
17872            async move {
17873                if item1 == item_to_resolve {
17874                    Ok(lsp::CompletionItem {
17875                        label: "method id()".to_string(),
17876                        filter_text: Some("id".to_string()),
17877                        detail: Some("Now resolved!".to_string()),
17878                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17879                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17880                            range: lsp::Range::new(
17881                                lsp::Position::new(0, 22),
17882                                lsp::Position::new(0, 22),
17883                            ),
17884                            new_text: ".id".to_string(),
17885                        })),
17886                        ..lsp::CompletionItem::default()
17887                    })
17888                } else {
17889                    Ok(item_to_resolve)
17890                }
17891            }
17892        }
17893    })
17894    .next()
17895    .await
17896    .unwrap();
17897    cx.run_until_parked();
17898
17899    cx.update_editor(|editor, window, cx| {
17900        editor.context_menu_next(&Default::default(), window, cx);
17901    });
17902
17903    cx.update_editor(|editor, _, _| {
17904        let context_menu = editor.context_menu.borrow_mut();
17905        let context_menu = context_menu
17906            .as_ref()
17907            .expect("Should have the context menu deployed");
17908        match context_menu {
17909            CodeContextMenu::Completions(completions_menu) => {
17910                let completions = completions_menu.completions.borrow_mut();
17911                assert_eq!(
17912                    completions
17913                        .iter()
17914                        .map(|completion| &completion.label.text)
17915                        .collect::<Vec<_>>(),
17916                    vec!["method id() Now resolved!", "other"],
17917                    "Should update first completion label, but not second as the filter text did not match."
17918                );
17919            }
17920            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17921        }
17922    });
17923}
17924
17925#[gpui::test]
17926async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17927    init_test(cx, |_| {});
17928    let mut cx = EditorLspTestContext::new_rust(
17929        lsp::ServerCapabilities {
17930            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17931            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17932            completion_provider: Some(lsp::CompletionOptions {
17933                resolve_provider: Some(true),
17934                ..Default::default()
17935            }),
17936            ..Default::default()
17937        },
17938        cx,
17939    )
17940    .await;
17941    cx.set_state(indoc! {"
17942        struct TestStruct {
17943            field: i32
17944        }
17945
17946        fn mainˇ() {
17947            let unused_var = 42;
17948            let test_struct = TestStruct { field: 42 };
17949        }
17950    "});
17951    let symbol_range = cx.lsp_range(indoc! {"
17952        struct TestStruct {
17953            field: i32
17954        }
17955
17956        «fn main»() {
17957            let unused_var = 42;
17958            let test_struct = TestStruct { field: 42 };
17959        }
17960    "});
17961    let mut hover_requests =
17962        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17963            Ok(Some(lsp::Hover {
17964                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17965                    kind: lsp::MarkupKind::Markdown,
17966                    value: "Function documentation".to_string(),
17967                }),
17968                range: Some(symbol_range),
17969            }))
17970        });
17971
17972    // Case 1: Test that code action menu hide hover popover
17973    cx.dispatch_action(Hover);
17974    hover_requests.next().await;
17975    cx.condition(|editor, _| editor.hover_state.visible()).await;
17976    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17977        move |_, _, _| async move {
17978            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17979                lsp::CodeAction {
17980                    title: "Remove unused variable".to_string(),
17981                    kind: Some(CodeActionKind::QUICKFIX),
17982                    edit: Some(lsp::WorkspaceEdit {
17983                        changes: Some(
17984                            [(
17985                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17986                                vec![lsp::TextEdit {
17987                                    range: lsp::Range::new(
17988                                        lsp::Position::new(5, 4),
17989                                        lsp::Position::new(5, 27),
17990                                    ),
17991                                    new_text: "".to_string(),
17992                                }],
17993                            )]
17994                            .into_iter()
17995                            .collect(),
17996                        ),
17997                        ..Default::default()
17998                    }),
17999                    ..Default::default()
18000                },
18001            )]))
18002        },
18003    );
18004    cx.update_editor(|editor, window, cx| {
18005        editor.toggle_code_actions(
18006            &ToggleCodeActions {
18007                deployed_from: None,
18008                quick_launch: false,
18009            },
18010            window,
18011            cx,
18012        );
18013    });
18014    code_action_requests.next().await;
18015    cx.run_until_parked();
18016    cx.condition(|editor, _| editor.context_menu_visible())
18017        .await;
18018    cx.update_editor(|editor, _, _| {
18019        assert!(
18020            !editor.hover_state.visible(),
18021            "Hover popover should be hidden when code action menu is shown"
18022        );
18023        // Hide code actions
18024        editor.context_menu.take();
18025    });
18026
18027    // Case 2: Test that code completions hide hover popover
18028    cx.dispatch_action(Hover);
18029    hover_requests.next().await;
18030    cx.condition(|editor, _| editor.hover_state.visible()).await;
18031    let counter = Arc::new(AtomicUsize::new(0));
18032    let mut completion_requests =
18033        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18034            let counter = counter.clone();
18035            async move {
18036                counter.fetch_add(1, atomic::Ordering::Release);
18037                Ok(Some(lsp::CompletionResponse::Array(vec![
18038                    lsp::CompletionItem {
18039                        label: "main".into(),
18040                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18041                        detail: Some("() -> ()".to_string()),
18042                        ..Default::default()
18043                    },
18044                    lsp::CompletionItem {
18045                        label: "TestStruct".into(),
18046                        kind: Some(lsp::CompletionItemKind::STRUCT),
18047                        detail: Some("struct TestStruct".to_string()),
18048                        ..Default::default()
18049                    },
18050                ])))
18051            }
18052        });
18053    cx.update_editor(|editor, window, cx| {
18054        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
18055    });
18056    completion_requests.next().await;
18057    cx.condition(|editor, _| editor.context_menu_visible())
18058        .await;
18059    cx.update_editor(|editor, _, _| {
18060        assert!(
18061            !editor.hover_state.visible(),
18062            "Hover popover should be hidden when completion menu is shown"
18063        );
18064    });
18065}
18066
18067#[gpui::test]
18068async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18069    init_test(cx, |_| {});
18070
18071    let mut cx = EditorLspTestContext::new_rust(
18072        lsp::ServerCapabilities {
18073            completion_provider: Some(lsp::CompletionOptions {
18074                trigger_characters: Some(vec![".".to_string()]),
18075                resolve_provider: Some(true),
18076                ..Default::default()
18077            }),
18078            ..Default::default()
18079        },
18080        cx,
18081    )
18082    .await;
18083
18084    cx.set_state("fn main() { let a = 2ˇ; }");
18085    cx.simulate_keystroke(".");
18086
18087    let unresolved_item_1 = lsp::CompletionItem {
18088        label: "id".to_string(),
18089        filter_text: Some("id".to_string()),
18090        detail: None,
18091        documentation: None,
18092        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18093            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18094            new_text: ".id".to_string(),
18095        })),
18096        ..lsp::CompletionItem::default()
18097    };
18098    let resolved_item_1 = lsp::CompletionItem {
18099        additional_text_edits: Some(vec![lsp::TextEdit {
18100            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18101            new_text: "!!".to_string(),
18102        }]),
18103        ..unresolved_item_1.clone()
18104    };
18105    let unresolved_item_2 = lsp::CompletionItem {
18106        label: "other".to_string(),
18107        filter_text: Some("other".to_string()),
18108        detail: None,
18109        documentation: None,
18110        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18111            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18112            new_text: ".other".to_string(),
18113        })),
18114        ..lsp::CompletionItem::default()
18115    };
18116    let resolved_item_2 = lsp::CompletionItem {
18117        additional_text_edits: Some(vec![lsp::TextEdit {
18118            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18119            new_text: "??".to_string(),
18120        }]),
18121        ..unresolved_item_2.clone()
18122    };
18123
18124    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18125    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18126    cx.lsp
18127        .server
18128        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18129            let unresolved_item_1 = unresolved_item_1.clone();
18130            let resolved_item_1 = resolved_item_1.clone();
18131            let unresolved_item_2 = unresolved_item_2.clone();
18132            let resolved_item_2 = resolved_item_2.clone();
18133            let resolve_requests_1 = resolve_requests_1.clone();
18134            let resolve_requests_2 = resolve_requests_2.clone();
18135            move |unresolved_request, _| {
18136                let unresolved_item_1 = unresolved_item_1.clone();
18137                let resolved_item_1 = resolved_item_1.clone();
18138                let unresolved_item_2 = unresolved_item_2.clone();
18139                let resolved_item_2 = resolved_item_2.clone();
18140                let resolve_requests_1 = resolve_requests_1.clone();
18141                let resolve_requests_2 = resolve_requests_2.clone();
18142                async move {
18143                    if unresolved_request == unresolved_item_1 {
18144                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18145                        Ok(resolved_item_1.clone())
18146                    } else if unresolved_request == unresolved_item_2 {
18147                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18148                        Ok(resolved_item_2.clone())
18149                    } else {
18150                        panic!("Unexpected completion item {unresolved_request:?}")
18151                    }
18152                }
18153            }
18154        })
18155        .detach();
18156
18157    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18158        let unresolved_item_1 = unresolved_item_1.clone();
18159        let unresolved_item_2 = unresolved_item_2.clone();
18160        async move {
18161            Ok(Some(lsp::CompletionResponse::Array(vec![
18162                unresolved_item_1,
18163                unresolved_item_2,
18164            ])))
18165        }
18166    })
18167    .next()
18168    .await;
18169
18170    cx.condition(|editor, _| editor.context_menu_visible())
18171        .await;
18172    cx.update_editor(|editor, _, _| {
18173        let context_menu = editor.context_menu.borrow_mut();
18174        let context_menu = context_menu
18175            .as_ref()
18176            .expect("Should have the context menu deployed");
18177        match context_menu {
18178            CodeContextMenu::Completions(completions_menu) => {
18179                let completions = completions_menu.completions.borrow_mut();
18180                assert_eq!(
18181                    completions
18182                        .iter()
18183                        .map(|completion| &completion.label.text)
18184                        .collect::<Vec<_>>(),
18185                    vec!["id", "other"]
18186                )
18187            }
18188            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18189        }
18190    });
18191    cx.run_until_parked();
18192
18193    cx.update_editor(|editor, window, cx| {
18194        editor.context_menu_next(&ContextMenuNext, window, cx);
18195    });
18196    cx.run_until_parked();
18197    cx.update_editor(|editor, window, cx| {
18198        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18199    });
18200    cx.run_until_parked();
18201    cx.update_editor(|editor, window, cx| {
18202        editor.context_menu_next(&ContextMenuNext, window, cx);
18203    });
18204    cx.run_until_parked();
18205    cx.update_editor(|editor, window, cx| {
18206        editor
18207            .compose_completion(&ComposeCompletion::default(), window, cx)
18208            .expect("No task returned")
18209    })
18210    .await
18211    .expect("Completion failed");
18212    cx.run_until_parked();
18213
18214    cx.update_editor(|editor, _, cx| {
18215        assert_eq!(
18216            resolve_requests_1.load(atomic::Ordering::Acquire),
18217            1,
18218            "Should always resolve once despite multiple selections"
18219        );
18220        assert_eq!(
18221            resolve_requests_2.load(atomic::Ordering::Acquire),
18222            1,
18223            "Should always resolve once after multiple selections and applying the completion"
18224        );
18225        assert_eq!(
18226            editor.text(cx),
18227            "fn main() { let a = ??.other; }",
18228            "Should use resolved data when applying the completion"
18229        );
18230    });
18231}
18232
18233#[gpui::test]
18234async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18235    init_test(cx, |_| {});
18236
18237    let item_0 = lsp::CompletionItem {
18238        label: "abs".into(),
18239        insert_text: Some("abs".into()),
18240        data: Some(json!({ "very": "special"})),
18241        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18242        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18243            lsp::InsertReplaceEdit {
18244                new_text: "abs".to_string(),
18245                insert: lsp::Range::default(),
18246                replace: lsp::Range::default(),
18247            },
18248        )),
18249        ..lsp::CompletionItem::default()
18250    };
18251    let items = iter::once(item_0.clone())
18252        .chain((11..51).map(|i| lsp::CompletionItem {
18253            label: format!("item_{}", i),
18254            insert_text: Some(format!("item_{}", i)),
18255            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18256            ..lsp::CompletionItem::default()
18257        }))
18258        .collect::<Vec<_>>();
18259
18260    let default_commit_characters = vec!["?".to_string()];
18261    let default_data = json!({ "default": "data"});
18262    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18263    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18264    let default_edit_range = lsp::Range {
18265        start: lsp::Position {
18266            line: 0,
18267            character: 5,
18268        },
18269        end: lsp::Position {
18270            line: 0,
18271            character: 5,
18272        },
18273    };
18274
18275    let mut cx = EditorLspTestContext::new_rust(
18276        lsp::ServerCapabilities {
18277            completion_provider: Some(lsp::CompletionOptions {
18278                trigger_characters: Some(vec![".".to_string()]),
18279                resolve_provider: Some(true),
18280                ..Default::default()
18281            }),
18282            ..Default::default()
18283        },
18284        cx,
18285    )
18286    .await;
18287
18288    cx.set_state("fn main() { let a = 2ˇ; }");
18289    cx.simulate_keystroke(".");
18290
18291    let completion_data = default_data.clone();
18292    let completion_characters = default_commit_characters.clone();
18293    let completion_items = items.clone();
18294    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18295        let default_data = completion_data.clone();
18296        let default_commit_characters = completion_characters.clone();
18297        let items = completion_items.clone();
18298        async move {
18299            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18300                items,
18301                item_defaults: Some(lsp::CompletionListItemDefaults {
18302                    data: Some(default_data.clone()),
18303                    commit_characters: Some(default_commit_characters.clone()),
18304                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18305                        default_edit_range,
18306                    )),
18307                    insert_text_format: Some(default_insert_text_format),
18308                    insert_text_mode: Some(default_insert_text_mode),
18309                }),
18310                ..lsp::CompletionList::default()
18311            })))
18312        }
18313    })
18314    .next()
18315    .await;
18316
18317    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18318    cx.lsp
18319        .server
18320        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18321            let closure_resolved_items = resolved_items.clone();
18322            move |item_to_resolve, _| {
18323                let closure_resolved_items = closure_resolved_items.clone();
18324                async move {
18325                    closure_resolved_items.lock().push(item_to_resolve.clone());
18326                    Ok(item_to_resolve)
18327                }
18328            }
18329        })
18330        .detach();
18331
18332    cx.condition(|editor, _| editor.context_menu_visible())
18333        .await;
18334    cx.run_until_parked();
18335    cx.update_editor(|editor, _, _| {
18336        let menu = editor.context_menu.borrow_mut();
18337        match menu.as_ref().expect("should have the completions menu") {
18338            CodeContextMenu::Completions(completions_menu) => {
18339                assert_eq!(
18340                    completions_menu
18341                        .entries
18342                        .borrow()
18343                        .iter()
18344                        .map(|mat| mat.string.clone())
18345                        .collect::<Vec<String>>(),
18346                    items
18347                        .iter()
18348                        .map(|completion| completion.label.clone())
18349                        .collect::<Vec<String>>()
18350                );
18351            }
18352            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18353        }
18354    });
18355    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18356    // with 4 from the end.
18357    assert_eq!(
18358        *resolved_items.lock(),
18359        [&items[0..16], &items[items.len() - 4..items.len()]]
18360            .concat()
18361            .iter()
18362            .cloned()
18363            .map(|mut item| {
18364                if item.data.is_none() {
18365                    item.data = Some(default_data.clone());
18366                }
18367                item
18368            })
18369            .collect::<Vec<lsp::CompletionItem>>(),
18370        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18371    );
18372    resolved_items.lock().clear();
18373
18374    cx.update_editor(|editor, window, cx| {
18375        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18376    });
18377    cx.run_until_parked();
18378    // Completions that have already been resolved are skipped.
18379    assert_eq!(
18380        *resolved_items.lock(),
18381        items[items.len() - 17..items.len() - 4]
18382            .iter()
18383            .cloned()
18384            .map(|mut item| {
18385                if item.data.is_none() {
18386                    item.data = Some(default_data.clone());
18387                }
18388                item
18389            })
18390            .collect::<Vec<lsp::CompletionItem>>()
18391    );
18392    resolved_items.lock().clear();
18393}
18394
18395#[gpui::test]
18396async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18397    init_test(cx, |_| {});
18398
18399    let mut cx = EditorLspTestContext::new(
18400        Language::new(
18401            LanguageConfig {
18402                matcher: LanguageMatcher {
18403                    path_suffixes: vec!["jsx".into()],
18404                    ..Default::default()
18405                },
18406                overrides: [(
18407                    "element".into(),
18408                    LanguageConfigOverride {
18409                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18410                        ..Default::default()
18411                    },
18412                )]
18413                .into_iter()
18414                .collect(),
18415                ..Default::default()
18416            },
18417            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18418        )
18419        .with_override_query("(jsx_self_closing_element) @element")
18420        .unwrap(),
18421        lsp::ServerCapabilities {
18422            completion_provider: Some(lsp::CompletionOptions {
18423                trigger_characters: Some(vec![":".to_string()]),
18424                ..Default::default()
18425            }),
18426            ..Default::default()
18427        },
18428        cx,
18429    )
18430    .await;
18431
18432    cx.lsp
18433        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18434            Ok(Some(lsp::CompletionResponse::Array(vec![
18435                lsp::CompletionItem {
18436                    label: "bg-blue".into(),
18437                    ..Default::default()
18438                },
18439                lsp::CompletionItem {
18440                    label: "bg-red".into(),
18441                    ..Default::default()
18442                },
18443                lsp::CompletionItem {
18444                    label: "bg-yellow".into(),
18445                    ..Default::default()
18446                },
18447            ])))
18448        });
18449
18450    cx.set_state(r#"<p class="bgˇ" />"#);
18451
18452    // Trigger completion when typing a dash, because the dash is an extra
18453    // word character in the 'element' scope, which contains the cursor.
18454    cx.simulate_keystroke("-");
18455    cx.executor().run_until_parked();
18456    cx.update_editor(|editor, _, _| {
18457        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18458        {
18459            assert_eq!(
18460                completion_menu_entries(menu),
18461                &["bg-blue", "bg-red", "bg-yellow"]
18462            );
18463        } else {
18464            panic!("expected completion menu to be open");
18465        }
18466    });
18467
18468    cx.simulate_keystroke("l");
18469    cx.executor().run_until_parked();
18470    cx.update_editor(|editor, _, _| {
18471        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18472        {
18473            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18474        } else {
18475            panic!("expected completion menu to be open");
18476        }
18477    });
18478
18479    // When filtering completions, consider the character after the '-' to
18480    // be the start of a subword.
18481    cx.set_state(r#"<p class="yelˇ" />"#);
18482    cx.simulate_keystroke("l");
18483    cx.executor().run_until_parked();
18484    cx.update_editor(|editor, _, _| {
18485        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18486        {
18487            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18488        } else {
18489            panic!("expected completion menu to be open");
18490        }
18491    });
18492}
18493
18494fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18495    let entries = menu.entries.borrow();
18496    entries.iter().map(|mat| mat.string.clone()).collect()
18497}
18498
18499#[gpui::test]
18500async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18501    init_test(cx, |settings| {
18502        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18503    });
18504
18505    let fs = FakeFs::new(cx.executor());
18506    fs.insert_file(path!("/file.ts"), Default::default()).await;
18507
18508    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18509    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18510
18511    language_registry.add(Arc::new(Language::new(
18512        LanguageConfig {
18513            name: "TypeScript".into(),
18514            matcher: LanguageMatcher {
18515                path_suffixes: vec!["ts".to_string()],
18516                ..Default::default()
18517            },
18518            ..Default::default()
18519        },
18520        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18521    )));
18522    update_test_language_settings(cx, |settings| {
18523        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18524    });
18525
18526    let test_plugin = "test_plugin";
18527    let _ = language_registry.register_fake_lsp(
18528        "TypeScript",
18529        FakeLspAdapter {
18530            prettier_plugins: vec![test_plugin],
18531            ..Default::default()
18532        },
18533    );
18534
18535    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18536    let buffer = project
18537        .update(cx, |project, cx| {
18538            project.open_local_buffer(path!("/file.ts"), cx)
18539        })
18540        .await
18541        .unwrap();
18542
18543    let buffer_text = "one\ntwo\nthree\n";
18544    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18545    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18546    editor.update_in(cx, |editor, window, cx| {
18547        editor.set_text(buffer_text, window, cx)
18548    });
18549
18550    editor
18551        .update_in(cx, |editor, window, cx| {
18552            editor.perform_format(
18553                project.clone(),
18554                FormatTrigger::Manual,
18555                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18556                window,
18557                cx,
18558            )
18559        })
18560        .unwrap()
18561        .await;
18562    assert_eq!(
18563        editor.update(cx, |editor, cx| editor.text(cx)),
18564        buffer_text.to_string() + prettier_format_suffix,
18565        "Test prettier formatting was not applied to the original buffer text",
18566    );
18567
18568    update_test_language_settings(cx, |settings| {
18569        settings.defaults.formatter = Some(FormatterList::default())
18570    });
18571    let format = editor.update_in(cx, |editor, window, cx| {
18572        editor.perform_format(
18573            project.clone(),
18574            FormatTrigger::Manual,
18575            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18576            window,
18577            cx,
18578        )
18579    });
18580    format.await.unwrap();
18581    assert_eq!(
18582        editor.update(cx, |editor, cx| editor.text(cx)),
18583        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18584        "Autoformatting (via test prettier) was not applied to the original buffer text",
18585    );
18586}
18587
18588#[gpui::test]
18589async fn test_addition_reverts(cx: &mut TestAppContext) {
18590    init_test(cx, |_| {});
18591    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18592    let base_text = indoc! {r#"
18593        struct Row;
18594        struct Row1;
18595        struct Row2;
18596
18597        struct Row4;
18598        struct Row5;
18599        struct Row6;
18600
18601        struct Row8;
18602        struct Row9;
18603        struct Row10;"#};
18604
18605    // When addition hunks are not adjacent to carets, no hunk revert is performed
18606    assert_hunk_revert(
18607        indoc! {r#"struct Row;
18608                   struct Row1;
18609                   struct Row1.1;
18610                   struct Row1.2;
18611                   struct Row2;ˇ
18612
18613                   struct Row4;
18614                   struct Row5;
18615                   struct Row6;
18616
18617                   struct Row8;
18618                   ˇstruct Row9;
18619                   struct Row9.1;
18620                   struct Row9.2;
18621                   struct Row9.3;
18622                   struct Row10;"#},
18623        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18624        indoc! {r#"struct Row;
18625                   struct Row1;
18626                   struct Row1.1;
18627                   struct Row1.2;
18628                   struct Row2;ˇ
18629
18630                   struct Row4;
18631                   struct Row5;
18632                   struct Row6;
18633
18634                   struct Row8;
18635                   ˇstruct Row9;
18636                   struct Row9.1;
18637                   struct Row9.2;
18638                   struct Row9.3;
18639                   struct Row10;"#},
18640        base_text,
18641        &mut cx,
18642    );
18643    // Same for selections
18644    assert_hunk_revert(
18645        indoc! {r#"struct Row;
18646                   struct Row1;
18647                   struct Row2;
18648                   struct Row2.1;
18649                   struct Row2.2;
18650                   «ˇ
18651                   struct Row4;
18652                   struct» Row5;
18653                   «struct Row6;
18654                   ˇ»
18655                   struct Row9.1;
18656                   struct Row9.2;
18657                   struct Row9.3;
18658                   struct Row8;
18659                   struct Row9;
18660                   struct Row10;"#},
18661        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18662        indoc! {r#"struct Row;
18663                   struct Row1;
18664                   struct Row2;
18665                   struct Row2.1;
18666                   struct Row2.2;
18667                   «ˇ
18668                   struct Row4;
18669                   struct» Row5;
18670                   «struct Row6;
18671                   ˇ»
18672                   struct Row9.1;
18673                   struct Row9.2;
18674                   struct Row9.3;
18675                   struct Row8;
18676                   struct Row9;
18677                   struct Row10;"#},
18678        base_text,
18679        &mut cx,
18680    );
18681
18682    // When carets and selections intersect the addition hunks, those are reverted.
18683    // Adjacent carets got merged.
18684    assert_hunk_revert(
18685        indoc! {r#"struct Row;
18686                   ˇ// something on the top
18687                   struct Row1;
18688                   struct Row2;
18689                   struct Roˇw3.1;
18690                   struct Row2.2;
18691                   struct Row2.3;ˇ
18692
18693                   struct Row4;
18694                   struct ˇRow5.1;
18695                   struct Row5.2;
18696                   struct «Rowˇ»5.3;
18697                   struct Row5;
18698                   struct Row6;
18699                   ˇ
18700                   struct Row9.1;
18701                   struct «Rowˇ»9.2;
18702                   struct «ˇRow»9.3;
18703                   struct Row8;
18704                   struct Row9;
18705                   «ˇ// something on bottom»
18706                   struct Row10;"#},
18707        vec![
18708            DiffHunkStatusKind::Added,
18709            DiffHunkStatusKind::Added,
18710            DiffHunkStatusKind::Added,
18711            DiffHunkStatusKind::Added,
18712            DiffHunkStatusKind::Added,
18713        ],
18714        indoc! {r#"struct Row;
18715                   ˇstruct Row1;
18716                   struct Row2;
18717                   ˇ
18718                   struct Row4;
18719                   ˇstruct Row5;
18720                   struct Row6;
18721                   ˇ
18722                   ˇstruct Row8;
18723                   struct Row9;
18724                   ˇstruct Row10;"#},
18725        base_text,
18726        &mut cx,
18727    );
18728}
18729
18730#[gpui::test]
18731async fn test_modification_reverts(cx: &mut TestAppContext) {
18732    init_test(cx, |_| {});
18733    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18734    let base_text = indoc! {r#"
18735        struct Row;
18736        struct Row1;
18737        struct Row2;
18738
18739        struct Row4;
18740        struct Row5;
18741        struct Row6;
18742
18743        struct Row8;
18744        struct Row9;
18745        struct Row10;"#};
18746
18747    // Modification hunks behave the same as the addition ones.
18748    assert_hunk_revert(
18749        indoc! {r#"struct Row;
18750                   struct Row1;
18751                   struct Row33;
18752                   ˇ
18753                   struct Row4;
18754                   struct Row5;
18755                   struct Row6;
18756                   ˇ
18757                   struct Row99;
18758                   struct Row9;
18759                   struct Row10;"#},
18760        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18761        indoc! {r#"struct Row;
18762                   struct Row1;
18763                   struct Row33;
18764                   ˇ
18765                   struct Row4;
18766                   struct Row5;
18767                   struct Row6;
18768                   ˇ
18769                   struct Row99;
18770                   struct Row9;
18771                   struct Row10;"#},
18772        base_text,
18773        &mut cx,
18774    );
18775    assert_hunk_revert(
18776        indoc! {r#"struct Row;
18777                   struct Row1;
18778                   struct Row33;
18779                   «ˇ
18780                   struct Row4;
18781                   struct» Row5;
18782                   «struct Row6;
18783                   ˇ»
18784                   struct Row99;
18785                   struct Row9;
18786                   struct Row10;"#},
18787        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18788        indoc! {r#"struct Row;
18789                   struct Row1;
18790                   struct Row33;
18791                   «ˇ
18792                   struct Row4;
18793                   struct» Row5;
18794                   «struct Row6;
18795                   ˇ»
18796                   struct Row99;
18797                   struct Row9;
18798                   struct Row10;"#},
18799        base_text,
18800        &mut cx,
18801    );
18802
18803    assert_hunk_revert(
18804        indoc! {r#"ˇstruct Row1.1;
18805                   struct Row1;
18806                   «ˇstr»uct Row22;
18807
18808                   struct ˇRow44;
18809                   struct Row5;
18810                   struct «Rˇ»ow66;ˇ
18811
18812                   «struˇ»ct Row88;
18813                   struct Row9;
18814                   struct Row1011;ˇ"#},
18815        vec![
18816            DiffHunkStatusKind::Modified,
18817            DiffHunkStatusKind::Modified,
18818            DiffHunkStatusKind::Modified,
18819            DiffHunkStatusKind::Modified,
18820            DiffHunkStatusKind::Modified,
18821            DiffHunkStatusKind::Modified,
18822        ],
18823        indoc! {r#"struct Row;
18824                   ˇstruct Row1;
18825                   struct Row2;
18826                   ˇ
18827                   struct Row4;
18828                   ˇstruct Row5;
18829                   struct Row6;
18830                   ˇ
18831                   struct Row8;
18832                   ˇstruct Row9;
18833                   struct Row10;ˇ"#},
18834        base_text,
18835        &mut cx,
18836    );
18837}
18838
18839#[gpui::test]
18840async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18841    init_test(cx, |_| {});
18842    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18843    let base_text = indoc! {r#"
18844        one
18845
18846        two
18847        three
18848        "#};
18849
18850    cx.set_head_text(base_text);
18851    cx.set_state("\nˇ\n");
18852    cx.executor().run_until_parked();
18853    cx.update_editor(|editor, _window, cx| {
18854        editor.expand_selected_diff_hunks(cx);
18855    });
18856    cx.executor().run_until_parked();
18857    cx.update_editor(|editor, window, cx| {
18858        editor.backspace(&Default::default(), window, cx);
18859    });
18860    cx.run_until_parked();
18861    cx.assert_state_with_diff(
18862        indoc! {r#"
18863
18864        - two
18865        - threeˇ
18866        +
18867        "#}
18868        .to_string(),
18869    );
18870}
18871
18872#[gpui::test]
18873async fn test_deletion_reverts(cx: &mut TestAppContext) {
18874    init_test(cx, |_| {});
18875    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18876    let base_text = indoc! {r#"struct Row;
18877struct Row1;
18878struct Row2;
18879
18880struct Row4;
18881struct Row5;
18882struct Row6;
18883
18884struct Row8;
18885struct Row9;
18886struct Row10;"#};
18887
18888    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18889    assert_hunk_revert(
18890        indoc! {r#"struct Row;
18891                   struct Row2;
18892
18893                   ˇstruct Row4;
18894                   struct Row5;
18895                   struct Row6;
18896                   ˇ
18897                   struct Row8;
18898                   struct Row10;"#},
18899        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18900        indoc! {r#"struct Row;
18901                   struct Row2;
18902
18903                   ˇstruct Row4;
18904                   struct Row5;
18905                   struct Row6;
18906                   ˇ
18907                   struct Row8;
18908                   struct Row10;"#},
18909        base_text,
18910        &mut cx,
18911    );
18912    assert_hunk_revert(
18913        indoc! {r#"struct Row;
18914                   struct Row2;
18915
18916                   «ˇstruct Row4;
18917                   struct» Row5;
18918                   «struct Row6;
18919                   ˇ»
18920                   struct Row8;
18921                   struct Row10;"#},
18922        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18923        indoc! {r#"struct Row;
18924                   struct Row2;
18925
18926                   «ˇstruct Row4;
18927                   struct» Row5;
18928                   «struct Row6;
18929                   ˇ»
18930                   struct Row8;
18931                   struct Row10;"#},
18932        base_text,
18933        &mut cx,
18934    );
18935
18936    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18937    assert_hunk_revert(
18938        indoc! {r#"struct Row;
18939                   ˇstruct Row2;
18940
18941                   struct Row4;
18942                   struct Row5;
18943                   struct Row6;
18944
18945                   struct Row8;ˇ
18946                   struct Row10;"#},
18947        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18948        indoc! {r#"struct Row;
18949                   struct Row1;
18950                   ˇstruct Row2;
18951
18952                   struct Row4;
18953                   struct Row5;
18954                   struct Row6;
18955
18956                   struct Row8;ˇ
18957                   struct Row9;
18958                   struct Row10;"#},
18959        base_text,
18960        &mut cx,
18961    );
18962    assert_hunk_revert(
18963        indoc! {r#"struct Row;
18964                   struct Row2«ˇ;
18965                   struct Row4;
18966                   struct» Row5;
18967                   «struct Row6;
18968
18969                   struct Row8;ˇ»
18970                   struct Row10;"#},
18971        vec![
18972            DiffHunkStatusKind::Deleted,
18973            DiffHunkStatusKind::Deleted,
18974            DiffHunkStatusKind::Deleted,
18975        ],
18976        indoc! {r#"struct Row;
18977                   struct Row1;
18978                   struct Row2«ˇ;
18979
18980                   struct Row4;
18981                   struct» Row5;
18982                   «struct Row6;
18983
18984                   struct Row8;ˇ»
18985                   struct Row9;
18986                   struct Row10;"#},
18987        base_text,
18988        &mut cx,
18989    );
18990}
18991
18992#[gpui::test]
18993async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18994    init_test(cx, |_| {});
18995
18996    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18997    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18998    let base_text_3 =
18999        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19000
19001    let text_1 = edit_first_char_of_every_line(base_text_1);
19002    let text_2 = edit_first_char_of_every_line(base_text_2);
19003    let text_3 = edit_first_char_of_every_line(base_text_3);
19004
19005    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19006    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19007    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19008
19009    let multibuffer = cx.new(|cx| {
19010        let mut multibuffer = MultiBuffer::new(ReadWrite);
19011        multibuffer.push_excerpts(
19012            buffer_1.clone(),
19013            [
19014                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19015                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19016                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19017            ],
19018            cx,
19019        );
19020        multibuffer.push_excerpts(
19021            buffer_2.clone(),
19022            [
19023                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19024                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19025                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19026            ],
19027            cx,
19028        );
19029        multibuffer.push_excerpts(
19030            buffer_3.clone(),
19031            [
19032                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19033                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19034                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19035            ],
19036            cx,
19037        );
19038        multibuffer
19039    });
19040
19041    let fs = FakeFs::new(cx.executor());
19042    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19043    let (editor, cx) = cx
19044        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19045    editor.update_in(cx, |editor, _window, cx| {
19046        for (buffer, diff_base) in [
19047            (buffer_1.clone(), base_text_1),
19048            (buffer_2.clone(), base_text_2),
19049            (buffer_3.clone(), base_text_3),
19050        ] {
19051            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19052            editor
19053                .buffer
19054                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19055        }
19056    });
19057    cx.executor().run_until_parked();
19058
19059    editor.update_in(cx, |editor, window, cx| {
19060        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}");
19061        editor.select_all(&SelectAll, window, cx);
19062        editor.git_restore(&Default::default(), window, cx);
19063    });
19064    cx.executor().run_until_parked();
19065
19066    // When all ranges are selected, all buffer hunks are reverted.
19067    editor.update(cx, |editor, cx| {
19068        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");
19069    });
19070    buffer_1.update(cx, |buffer, _| {
19071        assert_eq!(buffer.text(), base_text_1);
19072    });
19073    buffer_2.update(cx, |buffer, _| {
19074        assert_eq!(buffer.text(), base_text_2);
19075    });
19076    buffer_3.update(cx, |buffer, _| {
19077        assert_eq!(buffer.text(), base_text_3);
19078    });
19079
19080    editor.update_in(cx, |editor, window, cx| {
19081        editor.undo(&Default::default(), window, cx);
19082    });
19083
19084    editor.update_in(cx, |editor, window, cx| {
19085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19086            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19087        });
19088        editor.git_restore(&Default::default(), window, cx);
19089    });
19090
19091    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19092    // but not affect buffer_2 and its related excerpts.
19093    editor.update(cx, |editor, cx| {
19094        assert_eq!(
19095            editor.text(cx),
19096            "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}"
19097        );
19098    });
19099    buffer_1.update(cx, |buffer, _| {
19100        assert_eq!(buffer.text(), base_text_1);
19101    });
19102    buffer_2.update(cx, |buffer, _| {
19103        assert_eq!(
19104            buffer.text(),
19105            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19106        );
19107    });
19108    buffer_3.update(cx, |buffer, _| {
19109        assert_eq!(
19110            buffer.text(),
19111            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19112        );
19113    });
19114
19115    fn edit_first_char_of_every_line(text: &str) -> String {
19116        text.split('\n')
19117            .map(|line| format!("X{}", &line[1..]))
19118            .collect::<Vec<_>>()
19119            .join("\n")
19120    }
19121}
19122
19123#[gpui::test]
19124async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19125    init_test(cx, |_| {});
19126
19127    let cols = 4;
19128    let rows = 10;
19129    let sample_text_1 = sample_text(rows, cols, 'a');
19130    assert_eq!(
19131        sample_text_1,
19132        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19133    );
19134    let sample_text_2 = sample_text(rows, cols, 'l');
19135    assert_eq!(
19136        sample_text_2,
19137        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19138    );
19139    let sample_text_3 = sample_text(rows, cols, 'v');
19140    assert_eq!(
19141        sample_text_3,
19142        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19143    );
19144
19145    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19146    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19147    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19148
19149    let multi_buffer = cx.new(|cx| {
19150        let mut multibuffer = MultiBuffer::new(ReadWrite);
19151        multibuffer.push_excerpts(
19152            buffer_1.clone(),
19153            [
19154                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19155                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19156                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19157            ],
19158            cx,
19159        );
19160        multibuffer.push_excerpts(
19161            buffer_2.clone(),
19162            [
19163                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19164                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19165                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19166            ],
19167            cx,
19168        );
19169        multibuffer.push_excerpts(
19170            buffer_3.clone(),
19171            [
19172                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19173                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19174                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19175            ],
19176            cx,
19177        );
19178        multibuffer
19179    });
19180
19181    let fs = FakeFs::new(cx.executor());
19182    fs.insert_tree(
19183        "/a",
19184        json!({
19185            "main.rs": sample_text_1,
19186            "other.rs": sample_text_2,
19187            "lib.rs": sample_text_3,
19188        }),
19189    )
19190    .await;
19191    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19192    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19193    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19194    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19195        Editor::new(
19196            EditorMode::full(),
19197            multi_buffer,
19198            Some(project.clone()),
19199            window,
19200            cx,
19201        )
19202    });
19203    let multibuffer_item_id = workspace
19204        .update(cx, |workspace, window, cx| {
19205            assert!(
19206                workspace.active_item(cx).is_none(),
19207                "active item should be None before the first item is added"
19208            );
19209            workspace.add_item_to_active_pane(
19210                Box::new(multi_buffer_editor.clone()),
19211                None,
19212                true,
19213                window,
19214                cx,
19215            );
19216            let active_item = workspace
19217                .active_item(cx)
19218                .expect("should have an active item after adding the multi buffer");
19219            assert_eq!(
19220                active_item.buffer_kind(cx),
19221                ItemBufferKind::Multibuffer,
19222                "A multi buffer was expected to active after adding"
19223            );
19224            active_item.item_id()
19225        })
19226        .unwrap();
19227    cx.executor().run_until_parked();
19228
19229    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19230        editor.change_selections(
19231            SelectionEffects::scroll(Autoscroll::Next),
19232            window,
19233            cx,
19234            |s| s.select_ranges(Some(1..2)),
19235        );
19236        editor.open_excerpts(&OpenExcerpts, window, cx);
19237    });
19238    cx.executor().run_until_parked();
19239    let first_item_id = workspace
19240        .update(cx, |workspace, window, cx| {
19241            let active_item = workspace
19242                .active_item(cx)
19243                .expect("should have an active item after navigating into the 1st buffer");
19244            let first_item_id = active_item.item_id();
19245            assert_ne!(
19246                first_item_id, multibuffer_item_id,
19247                "Should navigate into the 1st buffer and activate it"
19248            );
19249            assert_eq!(
19250                active_item.buffer_kind(cx),
19251                ItemBufferKind::Singleton,
19252                "New active item should be a singleton buffer"
19253            );
19254            assert_eq!(
19255                active_item
19256                    .act_as::<Editor>(cx)
19257                    .expect("should have navigated into an editor for the 1st buffer")
19258                    .read(cx)
19259                    .text(cx),
19260                sample_text_1
19261            );
19262
19263            workspace
19264                .go_back(workspace.active_pane().downgrade(), window, cx)
19265                .detach_and_log_err(cx);
19266
19267            first_item_id
19268        })
19269        .unwrap();
19270    cx.executor().run_until_parked();
19271    workspace
19272        .update(cx, |workspace, _, cx| {
19273            let active_item = workspace
19274                .active_item(cx)
19275                .expect("should have an active item after navigating back");
19276            assert_eq!(
19277                active_item.item_id(),
19278                multibuffer_item_id,
19279                "Should navigate back to the multi buffer"
19280            );
19281            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19282        })
19283        .unwrap();
19284
19285    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19286        editor.change_selections(
19287            SelectionEffects::scroll(Autoscroll::Next),
19288            window,
19289            cx,
19290            |s| s.select_ranges(Some(39..40)),
19291        );
19292        editor.open_excerpts(&OpenExcerpts, window, cx);
19293    });
19294    cx.executor().run_until_parked();
19295    let second_item_id = workspace
19296        .update(cx, |workspace, window, cx| {
19297            let active_item = workspace
19298                .active_item(cx)
19299                .expect("should have an active item after navigating into the 2nd buffer");
19300            let second_item_id = active_item.item_id();
19301            assert_ne!(
19302                second_item_id, multibuffer_item_id,
19303                "Should navigate away from the multibuffer"
19304            );
19305            assert_ne!(
19306                second_item_id, first_item_id,
19307                "Should navigate into the 2nd buffer and activate it"
19308            );
19309            assert_eq!(
19310                active_item.buffer_kind(cx),
19311                ItemBufferKind::Singleton,
19312                "New active item should be a singleton buffer"
19313            );
19314            assert_eq!(
19315                active_item
19316                    .act_as::<Editor>(cx)
19317                    .expect("should have navigated into an editor")
19318                    .read(cx)
19319                    .text(cx),
19320                sample_text_2
19321            );
19322
19323            workspace
19324                .go_back(workspace.active_pane().downgrade(), window, cx)
19325                .detach_and_log_err(cx);
19326
19327            second_item_id
19328        })
19329        .unwrap();
19330    cx.executor().run_until_parked();
19331    workspace
19332        .update(cx, |workspace, _, cx| {
19333            let active_item = workspace
19334                .active_item(cx)
19335                .expect("should have an active item after navigating back from the 2nd buffer");
19336            assert_eq!(
19337                active_item.item_id(),
19338                multibuffer_item_id,
19339                "Should navigate back from the 2nd buffer to the multi buffer"
19340            );
19341            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19342        })
19343        .unwrap();
19344
19345    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19346        editor.change_selections(
19347            SelectionEffects::scroll(Autoscroll::Next),
19348            window,
19349            cx,
19350            |s| s.select_ranges(Some(70..70)),
19351        );
19352        editor.open_excerpts(&OpenExcerpts, window, cx);
19353    });
19354    cx.executor().run_until_parked();
19355    workspace
19356        .update(cx, |workspace, window, cx| {
19357            let active_item = workspace
19358                .active_item(cx)
19359                .expect("should have an active item after navigating into the 3rd buffer");
19360            let third_item_id = active_item.item_id();
19361            assert_ne!(
19362                third_item_id, multibuffer_item_id,
19363                "Should navigate into the 3rd buffer and activate it"
19364            );
19365            assert_ne!(third_item_id, first_item_id);
19366            assert_ne!(third_item_id, second_item_id);
19367            assert_eq!(
19368                active_item.buffer_kind(cx),
19369                ItemBufferKind::Singleton,
19370                "New active item should be a singleton buffer"
19371            );
19372            assert_eq!(
19373                active_item
19374                    .act_as::<Editor>(cx)
19375                    .expect("should have navigated into an editor")
19376                    .read(cx)
19377                    .text(cx),
19378                sample_text_3
19379            );
19380
19381            workspace
19382                .go_back(workspace.active_pane().downgrade(), window, cx)
19383                .detach_and_log_err(cx);
19384        })
19385        .unwrap();
19386    cx.executor().run_until_parked();
19387    workspace
19388        .update(cx, |workspace, _, cx| {
19389            let active_item = workspace
19390                .active_item(cx)
19391                .expect("should have an active item after navigating back from the 3rd buffer");
19392            assert_eq!(
19393                active_item.item_id(),
19394                multibuffer_item_id,
19395                "Should navigate back from the 3rd buffer to the multi buffer"
19396            );
19397            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19398        })
19399        .unwrap();
19400}
19401
19402#[gpui::test]
19403async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19404    init_test(cx, |_| {});
19405
19406    let mut cx = EditorTestContext::new(cx).await;
19407
19408    let diff_base = r#"
19409        use some::mod;
19410
19411        const A: u32 = 42;
19412
19413        fn main() {
19414            println!("hello");
19415
19416            println!("world");
19417        }
19418        "#
19419    .unindent();
19420
19421    cx.set_state(
19422        &r#"
19423        use some::modified;
19424
19425        ˇ
19426        fn main() {
19427            println!("hello there");
19428
19429            println!("around the");
19430            println!("world");
19431        }
19432        "#
19433        .unindent(),
19434    );
19435
19436    cx.set_head_text(&diff_base);
19437    executor.run_until_parked();
19438
19439    cx.update_editor(|editor, window, cx| {
19440        editor.go_to_next_hunk(&GoToHunk, window, cx);
19441        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19442    });
19443    executor.run_until_parked();
19444    cx.assert_state_with_diff(
19445        r#"
19446          use some::modified;
19447
19448
19449          fn main() {
19450        -     println!("hello");
19451        + ˇ    println!("hello there");
19452
19453              println!("around the");
19454              println!("world");
19455          }
19456        "#
19457        .unindent(),
19458    );
19459
19460    cx.update_editor(|editor, window, cx| {
19461        for _ in 0..2 {
19462            editor.go_to_next_hunk(&GoToHunk, window, cx);
19463            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19464        }
19465    });
19466    executor.run_until_parked();
19467    cx.assert_state_with_diff(
19468        r#"
19469        - use some::mod;
19470        + ˇuse some::modified;
19471
19472
19473          fn main() {
19474        -     println!("hello");
19475        +     println!("hello there");
19476
19477        +     println!("around the");
19478              println!("world");
19479          }
19480        "#
19481        .unindent(),
19482    );
19483
19484    cx.update_editor(|editor, window, cx| {
19485        editor.go_to_next_hunk(&GoToHunk, window, cx);
19486        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19487    });
19488    executor.run_until_parked();
19489    cx.assert_state_with_diff(
19490        r#"
19491        - use some::mod;
19492        + use some::modified;
19493
19494        - const A: u32 = 42;
19495          ˇ
19496          fn main() {
19497        -     println!("hello");
19498        +     println!("hello there");
19499
19500        +     println!("around the");
19501              println!("world");
19502          }
19503        "#
19504        .unindent(),
19505    );
19506
19507    cx.update_editor(|editor, window, cx| {
19508        editor.cancel(&Cancel, window, cx);
19509    });
19510
19511    cx.assert_state_with_diff(
19512        r#"
19513          use some::modified;
19514
19515          ˇ
19516          fn main() {
19517              println!("hello there");
19518
19519              println!("around the");
19520              println!("world");
19521          }
19522        "#
19523        .unindent(),
19524    );
19525}
19526
19527#[gpui::test]
19528async fn test_diff_base_change_with_expanded_diff_hunks(
19529    executor: BackgroundExecutor,
19530    cx: &mut TestAppContext,
19531) {
19532    init_test(cx, |_| {});
19533
19534    let mut cx = EditorTestContext::new(cx).await;
19535
19536    let diff_base = r#"
19537        use some::mod1;
19538        use some::mod2;
19539
19540        const A: u32 = 42;
19541        const B: u32 = 42;
19542        const C: u32 = 42;
19543
19544        fn main() {
19545            println!("hello");
19546
19547            println!("world");
19548        }
19549        "#
19550    .unindent();
19551
19552    cx.set_state(
19553        &r#"
19554        use some::mod2;
19555
19556        const A: u32 = 42;
19557        const C: u32 = 42;
19558
19559        fn main(ˇ) {
19560            //println!("hello");
19561
19562            println!("world");
19563            //
19564            //
19565        }
19566        "#
19567        .unindent(),
19568    );
19569
19570    cx.set_head_text(&diff_base);
19571    executor.run_until_parked();
19572
19573    cx.update_editor(|editor, window, cx| {
19574        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19575    });
19576    executor.run_until_parked();
19577    cx.assert_state_with_diff(
19578        r#"
19579        - use some::mod1;
19580          use some::mod2;
19581
19582          const A: u32 = 42;
19583        - const B: u32 = 42;
19584          const C: u32 = 42;
19585
19586          fn main(ˇ) {
19587        -     println!("hello");
19588        +     //println!("hello");
19589
19590              println!("world");
19591        +     //
19592        +     //
19593          }
19594        "#
19595        .unindent(),
19596    );
19597
19598    cx.set_head_text("new diff base!");
19599    executor.run_until_parked();
19600    cx.assert_state_with_diff(
19601        r#"
19602        - new diff base!
19603        + use some::mod2;
19604        +
19605        + const A: u32 = 42;
19606        + const C: u32 = 42;
19607        +
19608        + fn main(ˇ) {
19609        +     //println!("hello");
19610        +
19611        +     println!("world");
19612        +     //
19613        +     //
19614        + }
19615        "#
19616        .unindent(),
19617    );
19618}
19619
19620#[gpui::test]
19621async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19622    init_test(cx, |_| {});
19623
19624    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19625    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19626    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19627    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19628    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19629    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19630
19631    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19632    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19633    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19634
19635    let multi_buffer = cx.new(|cx| {
19636        let mut multibuffer = MultiBuffer::new(ReadWrite);
19637        multibuffer.push_excerpts(
19638            buffer_1.clone(),
19639            [
19640                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19641                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19642                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19643            ],
19644            cx,
19645        );
19646        multibuffer.push_excerpts(
19647            buffer_2.clone(),
19648            [
19649                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19650                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19651                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19652            ],
19653            cx,
19654        );
19655        multibuffer.push_excerpts(
19656            buffer_3.clone(),
19657            [
19658                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19659                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19660                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19661            ],
19662            cx,
19663        );
19664        multibuffer
19665    });
19666
19667    let editor =
19668        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19669    editor
19670        .update(cx, |editor, _window, cx| {
19671            for (buffer, diff_base) in [
19672                (buffer_1.clone(), file_1_old),
19673                (buffer_2.clone(), file_2_old),
19674                (buffer_3.clone(), file_3_old),
19675            ] {
19676                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19677                editor
19678                    .buffer
19679                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19680            }
19681        })
19682        .unwrap();
19683
19684    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19685    cx.run_until_parked();
19686
19687    cx.assert_editor_state(
19688        &"
19689            ˇaaa
19690            ccc
19691            ddd
19692
19693            ggg
19694            hhh
19695
19696
19697            lll
19698            mmm
19699            NNN
19700
19701            qqq
19702            rrr
19703
19704            uuu
19705            111
19706            222
19707            333
19708
19709            666
19710            777
19711
19712            000
19713            !!!"
19714        .unindent(),
19715    );
19716
19717    cx.update_editor(|editor, window, cx| {
19718        editor.select_all(&SelectAll, window, cx);
19719        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19720    });
19721    cx.executor().run_until_parked();
19722
19723    cx.assert_state_with_diff(
19724        "
19725            «aaa
19726          - bbb
19727            ccc
19728            ddd
19729
19730            ggg
19731            hhh
19732
19733
19734            lll
19735            mmm
19736          - nnn
19737          + NNN
19738
19739            qqq
19740            rrr
19741
19742            uuu
19743            111
19744            222
19745            333
19746
19747          + 666
19748            777
19749
19750            000
19751            !!!ˇ»"
19752            .unindent(),
19753    );
19754}
19755
19756#[gpui::test]
19757async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19758    init_test(cx, |_| {});
19759
19760    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19761    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19762
19763    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19764    let multi_buffer = cx.new(|cx| {
19765        let mut multibuffer = MultiBuffer::new(ReadWrite);
19766        multibuffer.push_excerpts(
19767            buffer.clone(),
19768            [
19769                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19770                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19771                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19772            ],
19773            cx,
19774        );
19775        multibuffer
19776    });
19777
19778    let editor =
19779        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19780    editor
19781        .update(cx, |editor, _window, cx| {
19782            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19783            editor
19784                .buffer
19785                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19786        })
19787        .unwrap();
19788
19789    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19790    cx.run_until_parked();
19791
19792    cx.update_editor(|editor, window, cx| {
19793        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19794    });
19795    cx.executor().run_until_parked();
19796
19797    // When the start of a hunk coincides with the start of its excerpt,
19798    // the hunk is expanded. When the start of a hunk is earlier than
19799    // the start of its excerpt, the hunk is not expanded.
19800    cx.assert_state_with_diff(
19801        "
19802            ˇaaa
19803          - bbb
19804          + BBB
19805
19806          - ddd
19807          - eee
19808          + DDD
19809          + EEE
19810            fff
19811
19812            iii
19813        "
19814        .unindent(),
19815    );
19816}
19817
19818#[gpui::test]
19819async fn test_edits_around_expanded_insertion_hunks(
19820    executor: BackgroundExecutor,
19821    cx: &mut TestAppContext,
19822) {
19823    init_test(cx, |_| {});
19824
19825    let mut cx = EditorTestContext::new(cx).await;
19826
19827    let diff_base = r#"
19828        use some::mod1;
19829        use some::mod2;
19830
19831        const A: u32 = 42;
19832
19833        fn main() {
19834            println!("hello");
19835
19836            println!("world");
19837        }
19838        "#
19839    .unindent();
19840    executor.run_until_parked();
19841    cx.set_state(
19842        &r#"
19843        use some::mod1;
19844        use some::mod2;
19845
19846        const A: u32 = 42;
19847        const B: u32 = 42;
19848        const C: u32 = 42;
19849        ˇ
19850
19851        fn main() {
19852            println!("hello");
19853
19854            println!("world");
19855        }
19856        "#
19857        .unindent(),
19858    );
19859
19860    cx.set_head_text(&diff_base);
19861    executor.run_until_parked();
19862
19863    cx.update_editor(|editor, window, cx| {
19864        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19865    });
19866    executor.run_until_parked();
19867
19868    cx.assert_state_with_diff(
19869        r#"
19870        use some::mod1;
19871        use some::mod2;
19872
19873        const A: u32 = 42;
19874      + const B: u32 = 42;
19875      + const C: u32 = 42;
19876      + ˇ
19877
19878        fn main() {
19879            println!("hello");
19880
19881            println!("world");
19882        }
19883      "#
19884        .unindent(),
19885    );
19886
19887    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19888    executor.run_until_parked();
19889
19890    cx.assert_state_with_diff(
19891        r#"
19892        use some::mod1;
19893        use some::mod2;
19894
19895        const A: u32 = 42;
19896      + const B: u32 = 42;
19897      + const C: u32 = 42;
19898      + const D: u32 = 42;
19899      + ˇ
19900
19901        fn main() {
19902            println!("hello");
19903
19904            println!("world");
19905        }
19906      "#
19907        .unindent(),
19908    );
19909
19910    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19911    executor.run_until_parked();
19912
19913    cx.assert_state_with_diff(
19914        r#"
19915        use some::mod1;
19916        use some::mod2;
19917
19918        const A: u32 = 42;
19919      + const B: u32 = 42;
19920      + const C: u32 = 42;
19921      + const D: u32 = 42;
19922      + const E: u32 = 42;
19923      + ˇ
19924
19925        fn main() {
19926            println!("hello");
19927
19928            println!("world");
19929        }
19930      "#
19931        .unindent(),
19932    );
19933
19934    cx.update_editor(|editor, window, cx| {
19935        editor.delete_line(&DeleteLine, window, cx);
19936    });
19937    executor.run_until_parked();
19938
19939    cx.assert_state_with_diff(
19940        r#"
19941        use some::mod1;
19942        use some::mod2;
19943
19944        const A: u32 = 42;
19945      + const B: u32 = 42;
19946      + const C: u32 = 42;
19947      + const D: u32 = 42;
19948      + const E: u32 = 42;
19949        ˇ
19950        fn main() {
19951            println!("hello");
19952
19953            println!("world");
19954        }
19955      "#
19956        .unindent(),
19957    );
19958
19959    cx.update_editor(|editor, window, cx| {
19960        editor.move_up(&MoveUp, window, cx);
19961        editor.delete_line(&DeleteLine, window, cx);
19962        editor.move_up(&MoveUp, window, cx);
19963        editor.delete_line(&DeleteLine, window, cx);
19964        editor.move_up(&MoveUp, window, cx);
19965        editor.delete_line(&DeleteLine, window, cx);
19966    });
19967    executor.run_until_parked();
19968    cx.assert_state_with_diff(
19969        r#"
19970        use some::mod1;
19971        use some::mod2;
19972
19973        const A: u32 = 42;
19974      + const B: u32 = 42;
19975        ˇ
19976        fn main() {
19977            println!("hello");
19978
19979            println!("world");
19980        }
19981      "#
19982        .unindent(),
19983    );
19984
19985    cx.update_editor(|editor, window, cx| {
19986        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19987        editor.delete_line(&DeleteLine, window, cx);
19988    });
19989    executor.run_until_parked();
19990    cx.assert_state_with_diff(
19991        r#"
19992        ˇ
19993        fn main() {
19994            println!("hello");
19995
19996            println!("world");
19997        }
19998      "#
19999        .unindent(),
20000    );
20001}
20002
20003#[gpui::test]
20004async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20005    init_test(cx, |_| {});
20006
20007    let mut cx = EditorTestContext::new(cx).await;
20008    cx.set_head_text(indoc! { "
20009        one
20010        two
20011        three
20012        four
20013        five
20014        "
20015    });
20016    cx.set_state(indoc! { "
20017        one
20018        ˇthree
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    cx.assert_state_with_diff(
20026        indoc! { "
20027        one
20028      - two
20029        ˇthree
20030      - four
20031        five
20032    "}
20033        .to_string(),
20034    );
20035    cx.update_editor(|editor, window, cx| {
20036        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20037    });
20038
20039    cx.assert_state_with_diff(
20040        indoc! { "
20041        one
20042        ˇthree
20043        five
20044    "}
20045        .to_string(),
20046    );
20047
20048    cx.set_state(indoc! { "
20049        one
20050        ˇTWO
20051        three
20052        four
20053        five
20054    "});
20055    cx.run_until_parked();
20056    cx.update_editor(|editor, window, cx| {
20057        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20058    });
20059
20060    cx.assert_state_with_diff(
20061        indoc! { "
20062            one
20063          - two
20064          + ˇTWO
20065            three
20066            four
20067            five
20068        "}
20069        .to_string(),
20070    );
20071    cx.update_editor(|editor, window, cx| {
20072        editor.move_up(&Default::default(), window, cx);
20073        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20074    });
20075    cx.assert_state_with_diff(
20076        indoc! { "
20077            one
20078            ˇTWO
20079            three
20080            four
20081            five
20082        "}
20083        .to_string(),
20084    );
20085}
20086
20087#[gpui::test]
20088async fn test_edits_around_expanded_deletion_hunks(
20089    executor: BackgroundExecutor,
20090    cx: &mut TestAppContext,
20091) {
20092    init_test(cx, |_| {});
20093
20094    let mut cx = EditorTestContext::new(cx).await;
20095
20096    let diff_base = r#"
20097        use some::mod1;
20098        use some::mod2;
20099
20100        const A: u32 = 42;
20101        const B: u32 = 42;
20102        const C: u32 = 42;
20103
20104
20105        fn main() {
20106            println!("hello");
20107
20108            println!("world");
20109        }
20110    "#
20111    .unindent();
20112    executor.run_until_parked();
20113    cx.set_state(
20114        &r#"
20115        use some::mod1;
20116        use some::mod2;
20117
20118        ˇconst B: u32 = 42;
20119        const C: u32 = 42;
20120
20121
20122        fn main() {
20123            println!("hello");
20124
20125            println!("world");
20126        }
20127        "#
20128        .unindent(),
20129    );
20130
20131    cx.set_head_text(&diff_base);
20132    executor.run_until_parked();
20133
20134    cx.update_editor(|editor, window, cx| {
20135        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20136    });
20137    executor.run_until_parked();
20138
20139    cx.assert_state_with_diff(
20140        r#"
20141        use some::mod1;
20142        use some::mod2;
20143
20144      - const A: u32 = 42;
20145        ˇconst B: u32 = 42;
20146        const C: u32 = 42;
20147
20148
20149        fn main() {
20150            println!("hello");
20151
20152            println!("world");
20153        }
20154      "#
20155        .unindent(),
20156    );
20157
20158    cx.update_editor(|editor, window, cx| {
20159        editor.delete_line(&DeleteLine, window, cx);
20160    });
20161    executor.run_until_parked();
20162    cx.assert_state_with_diff(
20163        r#"
20164        use some::mod1;
20165        use some::mod2;
20166
20167      - const A: u32 = 42;
20168      - const B: u32 = 42;
20169        ˇconst C: u32 = 42;
20170
20171
20172        fn main() {
20173            println!("hello");
20174
20175            println!("world");
20176        }
20177      "#
20178        .unindent(),
20179    );
20180
20181    cx.update_editor(|editor, window, cx| {
20182        editor.delete_line(&DeleteLine, window, cx);
20183    });
20184    executor.run_until_parked();
20185    cx.assert_state_with_diff(
20186        r#"
20187        use some::mod1;
20188        use some::mod2;
20189
20190      - const A: u32 = 42;
20191      - const B: u32 = 42;
20192      - const C: u32 = 42;
20193        ˇ
20194
20195        fn main() {
20196            println!("hello");
20197
20198            println!("world");
20199        }
20200      "#
20201        .unindent(),
20202    );
20203
20204    cx.update_editor(|editor, window, cx| {
20205        editor.handle_input("replacement", window, cx);
20206    });
20207    executor.run_until_parked();
20208    cx.assert_state_with_diff(
20209        r#"
20210        use some::mod1;
20211        use some::mod2;
20212
20213      - const A: u32 = 42;
20214      - const B: u32 = 42;
20215      - const C: u32 = 42;
20216      -
20217      + replacementˇ
20218
20219        fn main() {
20220            println!("hello");
20221
20222            println!("world");
20223        }
20224      "#
20225        .unindent(),
20226    );
20227}
20228
20229#[gpui::test]
20230async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20231    init_test(cx, |_| {});
20232
20233    let mut cx = EditorTestContext::new(cx).await;
20234
20235    let base_text = r#"
20236        one
20237        two
20238        three
20239        four
20240        five
20241    "#
20242    .unindent();
20243    executor.run_until_parked();
20244    cx.set_state(
20245        &r#"
20246        one
20247        two
20248        fˇour
20249        five
20250        "#
20251        .unindent(),
20252    );
20253
20254    cx.set_head_text(&base_text);
20255    executor.run_until_parked();
20256
20257    cx.update_editor(|editor, window, cx| {
20258        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20259    });
20260    executor.run_until_parked();
20261
20262    cx.assert_state_with_diff(
20263        r#"
20264          one
20265          two
20266        - three
20267          fˇour
20268          five
20269        "#
20270        .unindent(),
20271    );
20272
20273    cx.update_editor(|editor, window, cx| {
20274        editor.backspace(&Backspace, window, cx);
20275        editor.backspace(&Backspace, window, cx);
20276    });
20277    executor.run_until_parked();
20278    cx.assert_state_with_diff(
20279        r#"
20280          one
20281          two
20282        - threeˇ
20283        - four
20284        + our
20285          five
20286        "#
20287        .unindent(),
20288    );
20289}
20290
20291#[gpui::test]
20292async fn test_edit_after_expanded_modification_hunk(
20293    executor: BackgroundExecutor,
20294    cx: &mut TestAppContext,
20295) {
20296    init_test(cx, |_| {});
20297
20298    let mut cx = EditorTestContext::new(cx).await;
20299
20300    let diff_base = r#"
20301        use some::mod1;
20302        use some::mod2;
20303
20304        const A: u32 = 42;
20305        const B: u32 = 42;
20306        const C: u32 = 42;
20307        const D: u32 = 42;
20308
20309
20310        fn main() {
20311            println!("hello");
20312
20313            println!("world");
20314        }"#
20315    .unindent();
20316
20317    cx.set_state(
20318        &r#"
20319        use some::mod1;
20320        use some::mod2;
20321
20322        const A: u32 = 42;
20323        const B: u32 = 42;
20324        const C: u32 = 43ˇ
20325        const D: u32 = 42;
20326
20327
20328        fn main() {
20329            println!("hello");
20330
20331            println!("world");
20332        }"#
20333        .unindent(),
20334    );
20335
20336    cx.set_head_text(&diff_base);
20337    executor.run_until_parked();
20338    cx.update_editor(|editor, window, cx| {
20339        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20340    });
20341    executor.run_until_parked();
20342
20343    cx.assert_state_with_diff(
20344        r#"
20345        use some::mod1;
20346        use some::mod2;
20347
20348        const A: u32 = 42;
20349        const B: u32 = 42;
20350      - const C: u32 = 42;
20351      + const C: u32 = 43ˇ
20352        const D: u32 = 42;
20353
20354
20355        fn main() {
20356            println!("hello");
20357
20358            println!("world");
20359        }"#
20360        .unindent(),
20361    );
20362
20363    cx.update_editor(|editor, window, cx| {
20364        editor.handle_input("\nnew_line\n", window, cx);
20365    });
20366    executor.run_until_parked();
20367
20368    cx.assert_state_with_diff(
20369        r#"
20370        use some::mod1;
20371        use some::mod2;
20372
20373        const A: u32 = 42;
20374        const B: u32 = 42;
20375      - const C: u32 = 42;
20376      + const C: u32 = 43
20377      + new_line
20378      + ˇ
20379        const D: u32 = 42;
20380
20381
20382        fn main() {
20383            println!("hello");
20384
20385            println!("world");
20386        }"#
20387        .unindent(),
20388    );
20389}
20390
20391#[gpui::test]
20392async fn test_stage_and_unstage_added_file_hunk(
20393    executor: BackgroundExecutor,
20394    cx: &mut TestAppContext,
20395) {
20396    init_test(cx, |_| {});
20397
20398    let mut cx = EditorTestContext::new(cx).await;
20399    cx.update_editor(|editor, _, cx| {
20400        editor.set_expand_all_diff_hunks(cx);
20401    });
20402
20403    let working_copy = r#"
20404            ˇfn main() {
20405                println!("hello, world!");
20406            }
20407        "#
20408    .unindent();
20409
20410    cx.set_state(&working_copy);
20411    executor.run_until_parked();
20412
20413    cx.assert_state_with_diff(
20414        r#"
20415            + ˇfn main() {
20416            +     println!("hello, world!");
20417            + }
20418        "#
20419        .unindent(),
20420    );
20421    cx.assert_index_text(None);
20422
20423    cx.update_editor(|editor, window, cx| {
20424        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20425    });
20426    executor.run_until_parked();
20427    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20428    cx.assert_state_with_diff(
20429        r#"
20430            + ˇfn main() {
20431            +     println!("hello, world!");
20432            + }
20433        "#
20434        .unindent(),
20435    );
20436
20437    cx.update_editor(|editor, window, cx| {
20438        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20439    });
20440    executor.run_until_parked();
20441    cx.assert_index_text(None);
20442}
20443
20444async fn setup_indent_guides_editor(
20445    text: &str,
20446    cx: &mut TestAppContext,
20447) -> (BufferId, EditorTestContext) {
20448    init_test(cx, |_| {});
20449
20450    let mut cx = EditorTestContext::new(cx).await;
20451
20452    let buffer_id = cx.update_editor(|editor, window, cx| {
20453        editor.set_text(text, window, cx);
20454        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20455
20456        buffer_ids[0]
20457    });
20458
20459    (buffer_id, cx)
20460}
20461
20462fn assert_indent_guides(
20463    range: Range<u32>,
20464    expected: Vec<IndentGuide>,
20465    active_indices: Option<Vec<usize>>,
20466    cx: &mut EditorTestContext,
20467) {
20468    let indent_guides = cx.update_editor(|editor, window, cx| {
20469        let snapshot = editor.snapshot(window, cx).display_snapshot;
20470        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20471            editor,
20472            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20473            true,
20474            &snapshot,
20475            cx,
20476        );
20477
20478        indent_guides.sort_by(|a, b| {
20479            a.depth.cmp(&b.depth).then(
20480                a.start_row
20481                    .cmp(&b.start_row)
20482                    .then(a.end_row.cmp(&b.end_row)),
20483            )
20484        });
20485        indent_guides
20486    });
20487
20488    if let Some(expected) = active_indices {
20489        let active_indices = cx.update_editor(|editor, window, cx| {
20490            let snapshot = editor.snapshot(window, cx).display_snapshot;
20491            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20492        });
20493
20494        assert_eq!(
20495            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20496            expected,
20497            "Active indent guide indices do not match"
20498        );
20499    }
20500
20501    assert_eq!(indent_guides, expected, "Indent guides do not match");
20502}
20503
20504fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20505    IndentGuide {
20506        buffer_id,
20507        start_row: MultiBufferRow(start_row),
20508        end_row: MultiBufferRow(end_row),
20509        depth,
20510        tab_size: 4,
20511        settings: IndentGuideSettings {
20512            enabled: true,
20513            line_width: 1,
20514            active_line_width: 1,
20515            coloring: IndentGuideColoring::default(),
20516            background_coloring: IndentGuideBackgroundColoring::default(),
20517        },
20518    }
20519}
20520
20521#[gpui::test]
20522async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20523    let (buffer_id, mut cx) = setup_indent_guides_editor(
20524        &"
20525        fn main() {
20526            let a = 1;
20527        }"
20528        .unindent(),
20529        cx,
20530    )
20531    .await;
20532
20533    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20534}
20535
20536#[gpui::test]
20537async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20538    let (buffer_id, mut cx) = setup_indent_guides_editor(
20539        &"
20540        fn main() {
20541            let a = 1;
20542            let b = 2;
20543        }"
20544        .unindent(),
20545        cx,
20546    )
20547    .await;
20548
20549    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20550}
20551
20552#[gpui::test]
20553async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20554    let (buffer_id, mut cx) = setup_indent_guides_editor(
20555        &"
20556        fn main() {
20557            let a = 1;
20558            if a == 3 {
20559                let b = 2;
20560            } else {
20561                let c = 3;
20562            }
20563        }"
20564        .unindent(),
20565        cx,
20566    )
20567    .await;
20568
20569    assert_indent_guides(
20570        0..8,
20571        vec![
20572            indent_guide(buffer_id, 1, 6, 0),
20573            indent_guide(buffer_id, 3, 3, 1),
20574            indent_guide(buffer_id, 5, 5, 1),
20575        ],
20576        None,
20577        &mut cx,
20578    );
20579}
20580
20581#[gpui::test]
20582async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20583    let (buffer_id, mut cx) = setup_indent_guides_editor(
20584        &"
20585        fn main() {
20586            let a = 1;
20587                let b = 2;
20588            let c = 3;
20589        }"
20590        .unindent(),
20591        cx,
20592    )
20593    .await;
20594
20595    assert_indent_guides(
20596        0..5,
20597        vec![
20598            indent_guide(buffer_id, 1, 3, 0),
20599            indent_guide(buffer_id, 2, 2, 1),
20600        ],
20601        None,
20602        &mut cx,
20603    );
20604}
20605
20606#[gpui::test]
20607async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20608    let (buffer_id, mut cx) = setup_indent_guides_editor(
20609        &"
20610        fn main() {
20611            let a = 1;
20612
20613            let c = 3;
20614        }"
20615        .unindent(),
20616        cx,
20617    )
20618    .await;
20619
20620    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20621}
20622
20623#[gpui::test]
20624async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20625    let (buffer_id, mut cx) = setup_indent_guides_editor(
20626        &"
20627        fn main() {
20628            let a = 1;
20629
20630            let c = 3;
20631
20632            if a == 3 {
20633                let b = 2;
20634            } else {
20635                let c = 3;
20636            }
20637        }"
20638        .unindent(),
20639        cx,
20640    )
20641    .await;
20642
20643    assert_indent_guides(
20644        0..11,
20645        vec![
20646            indent_guide(buffer_id, 1, 9, 0),
20647            indent_guide(buffer_id, 6, 6, 1),
20648            indent_guide(buffer_id, 8, 8, 1),
20649        ],
20650        None,
20651        &mut cx,
20652    );
20653}
20654
20655#[gpui::test]
20656async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20657    let (buffer_id, mut cx) = setup_indent_guides_editor(
20658        &"
20659        fn main() {
20660            let a = 1;
20661
20662            let c = 3;
20663
20664            if a == 3 {
20665                let b = 2;
20666            } else {
20667                let c = 3;
20668            }
20669        }"
20670        .unindent(),
20671        cx,
20672    )
20673    .await;
20674
20675    assert_indent_guides(
20676        1..11,
20677        vec![
20678            indent_guide(buffer_id, 1, 9, 0),
20679            indent_guide(buffer_id, 6, 6, 1),
20680            indent_guide(buffer_id, 8, 8, 1),
20681        ],
20682        None,
20683        &mut cx,
20684    );
20685}
20686
20687#[gpui::test]
20688async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20689    let (buffer_id, mut cx) = setup_indent_guides_editor(
20690        &"
20691        fn main() {
20692            let a = 1;
20693
20694            let c = 3;
20695
20696            if a == 3 {
20697                let b = 2;
20698            } else {
20699                let c = 3;
20700            }
20701        }"
20702        .unindent(),
20703        cx,
20704    )
20705    .await;
20706
20707    assert_indent_guides(
20708        1..10,
20709        vec![
20710            indent_guide(buffer_id, 1, 9, 0),
20711            indent_guide(buffer_id, 6, 6, 1),
20712            indent_guide(buffer_id, 8, 8, 1),
20713        ],
20714        None,
20715        &mut cx,
20716    );
20717}
20718
20719#[gpui::test]
20720async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20721    let (buffer_id, mut cx) = setup_indent_guides_editor(
20722        &"
20723        fn main() {
20724            if a {
20725                b(
20726                    c,
20727                    d,
20728                )
20729            } else {
20730                e(
20731                    f
20732                )
20733            }
20734        }"
20735        .unindent(),
20736        cx,
20737    )
20738    .await;
20739
20740    assert_indent_guides(
20741        0..11,
20742        vec![
20743            indent_guide(buffer_id, 1, 10, 0),
20744            indent_guide(buffer_id, 2, 5, 1),
20745            indent_guide(buffer_id, 7, 9, 1),
20746            indent_guide(buffer_id, 3, 4, 2),
20747            indent_guide(buffer_id, 8, 8, 2),
20748        ],
20749        None,
20750        &mut cx,
20751    );
20752
20753    cx.update_editor(|editor, window, cx| {
20754        editor.fold_at(MultiBufferRow(2), window, cx);
20755        assert_eq!(
20756            editor.display_text(cx),
20757            "
20758            fn main() {
20759                if a {
20760                    b(⋯
20761                    )
20762                } else {
20763                    e(
20764                        f
20765                    )
20766                }
20767            }"
20768            .unindent()
20769        );
20770    });
20771
20772    assert_indent_guides(
20773        0..11,
20774        vec![
20775            indent_guide(buffer_id, 1, 10, 0),
20776            indent_guide(buffer_id, 2, 5, 1),
20777            indent_guide(buffer_id, 7, 9, 1),
20778            indent_guide(buffer_id, 8, 8, 2),
20779        ],
20780        None,
20781        &mut cx,
20782    );
20783}
20784
20785#[gpui::test]
20786async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20787    let (buffer_id, mut cx) = setup_indent_guides_editor(
20788        &"
20789        block1
20790            block2
20791                block3
20792                    block4
20793            block2
20794        block1
20795        block1"
20796            .unindent(),
20797        cx,
20798    )
20799    .await;
20800
20801    assert_indent_guides(
20802        1..10,
20803        vec![
20804            indent_guide(buffer_id, 1, 4, 0),
20805            indent_guide(buffer_id, 2, 3, 1),
20806            indent_guide(buffer_id, 3, 3, 2),
20807        ],
20808        None,
20809        &mut cx,
20810    );
20811}
20812
20813#[gpui::test]
20814async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20815    let (buffer_id, mut cx) = setup_indent_guides_editor(
20816        &"
20817        block1
20818            block2
20819                block3
20820
20821        block1
20822        block1"
20823            .unindent(),
20824        cx,
20825    )
20826    .await;
20827
20828    assert_indent_guides(
20829        0..6,
20830        vec![
20831            indent_guide(buffer_id, 1, 2, 0),
20832            indent_guide(buffer_id, 2, 2, 1),
20833        ],
20834        None,
20835        &mut cx,
20836    );
20837}
20838
20839#[gpui::test]
20840async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20841    let (buffer_id, mut cx) = setup_indent_guides_editor(
20842        &"
20843        function component() {
20844        \treturn (
20845        \t\t\t
20846        \t\t<div>
20847        \t\t\t<abc></abc>
20848        \t\t</div>
20849        \t)
20850        }"
20851        .unindent(),
20852        cx,
20853    )
20854    .await;
20855
20856    assert_indent_guides(
20857        0..8,
20858        vec![
20859            indent_guide(buffer_id, 1, 6, 0),
20860            indent_guide(buffer_id, 2, 5, 1),
20861            indent_guide(buffer_id, 4, 4, 2),
20862        ],
20863        None,
20864        &mut cx,
20865    );
20866}
20867
20868#[gpui::test]
20869async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20870    let (buffer_id, mut cx) = setup_indent_guides_editor(
20871        &"
20872        function component() {
20873        \treturn (
20874        \t
20875        \t\t<div>
20876        \t\t\t<abc></abc>
20877        \t\t</div>
20878        \t)
20879        }"
20880        .unindent(),
20881        cx,
20882    )
20883    .await;
20884
20885    assert_indent_guides(
20886        0..8,
20887        vec![
20888            indent_guide(buffer_id, 1, 6, 0),
20889            indent_guide(buffer_id, 2, 5, 1),
20890            indent_guide(buffer_id, 4, 4, 2),
20891        ],
20892        None,
20893        &mut cx,
20894    );
20895}
20896
20897#[gpui::test]
20898async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20899    let (buffer_id, mut cx) = setup_indent_guides_editor(
20900        &"
20901        block1
20902
20903
20904
20905            block2
20906        "
20907        .unindent(),
20908        cx,
20909    )
20910    .await;
20911
20912    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20913}
20914
20915#[gpui::test]
20916async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20917    let (buffer_id, mut cx) = setup_indent_guides_editor(
20918        &"
20919        def a:
20920        \tb = 3
20921        \tif True:
20922        \t\tc = 4
20923        \t\td = 5
20924        \tprint(b)
20925        "
20926        .unindent(),
20927        cx,
20928    )
20929    .await;
20930
20931    assert_indent_guides(
20932        0..6,
20933        vec![
20934            indent_guide(buffer_id, 1, 5, 0),
20935            indent_guide(buffer_id, 3, 4, 1),
20936        ],
20937        None,
20938        &mut cx,
20939    );
20940}
20941
20942#[gpui::test]
20943async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20944    let (buffer_id, mut cx) = setup_indent_guides_editor(
20945        &"
20946    fn main() {
20947        let a = 1;
20948    }"
20949        .unindent(),
20950        cx,
20951    )
20952    .await;
20953
20954    cx.update_editor(|editor, window, cx| {
20955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20956            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20957        });
20958    });
20959
20960    assert_indent_guides(
20961        0..3,
20962        vec![indent_guide(buffer_id, 1, 1, 0)],
20963        Some(vec![0]),
20964        &mut cx,
20965    );
20966}
20967
20968#[gpui::test]
20969async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20970    let (buffer_id, mut cx) = setup_indent_guides_editor(
20971        &"
20972    fn main() {
20973        if 1 == 2 {
20974            let a = 1;
20975        }
20976    }"
20977        .unindent(),
20978        cx,
20979    )
20980    .await;
20981
20982    cx.update_editor(|editor, window, cx| {
20983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20984            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20985        });
20986    });
20987
20988    assert_indent_guides(
20989        0..4,
20990        vec![
20991            indent_guide(buffer_id, 1, 3, 0),
20992            indent_guide(buffer_id, 2, 2, 1),
20993        ],
20994        Some(vec![1]),
20995        &mut cx,
20996    );
20997
20998    cx.update_editor(|editor, window, cx| {
20999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21000            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21001        });
21002    });
21003
21004    assert_indent_guides(
21005        0..4,
21006        vec![
21007            indent_guide(buffer_id, 1, 3, 0),
21008            indent_guide(buffer_id, 2, 2, 1),
21009        ],
21010        Some(vec![1]),
21011        &mut cx,
21012    );
21013
21014    cx.update_editor(|editor, window, cx| {
21015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21016            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21017        });
21018    });
21019
21020    assert_indent_guides(
21021        0..4,
21022        vec![
21023            indent_guide(buffer_id, 1, 3, 0),
21024            indent_guide(buffer_id, 2, 2, 1),
21025        ],
21026        Some(vec![0]),
21027        &mut cx,
21028    );
21029}
21030
21031#[gpui::test]
21032async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21033    let (buffer_id, mut cx) = setup_indent_guides_editor(
21034        &"
21035    fn main() {
21036        let a = 1;
21037
21038        let b = 2;
21039    }"
21040        .unindent(),
21041        cx,
21042    )
21043    .await;
21044
21045    cx.update_editor(|editor, window, cx| {
21046        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21047            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21048        });
21049    });
21050
21051    assert_indent_guides(
21052        0..5,
21053        vec![indent_guide(buffer_id, 1, 3, 0)],
21054        Some(vec![0]),
21055        &mut cx,
21056    );
21057}
21058
21059#[gpui::test]
21060async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21061    let (buffer_id, mut cx) = setup_indent_guides_editor(
21062        &"
21063    def m:
21064        a = 1
21065        pass"
21066            .unindent(),
21067        cx,
21068    )
21069    .await;
21070
21071    cx.update_editor(|editor, window, cx| {
21072        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21073            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21074        });
21075    });
21076
21077    assert_indent_guides(
21078        0..3,
21079        vec![indent_guide(buffer_id, 1, 2, 0)],
21080        Some(vec![0]),
21081        &mut cx,
21082    );
21083}
21084
21085#[gpui::test]
21086async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21087    init_test(cx, |_| {});
21088    let mut cx = EditorTestContext::new(cx).await;
21089    let text = indoc! {
21090        "
21091        impl A {
21092            fn b() {
21093                0;
21094                3;
21095                5;
21096                6;
21097                7;
21098            }
21099        }
21100        "
21101    };
21102    let base_text = indoc! {
21103        "
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
21121    cx.update_editor(|editor, window, cx| {
21122        editor.set_text(text, window, cx);
21123
21124        editor.buffer().update(cx, |multibuffer, cx| {
21125            let buffer = multibuffer.as_singleton().unwrap();
21126            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21127
21128            multibuffer.set_all_diff_hunks_expanded(cx);
21129            multibuffer.add_diff(diff, cx);
21130
21131            buffer.read(cx).remote_id()
21132        })
21133    });
21134    cx.run_until_parked();
21135
21136    cx.assert_state_with_diff(
21137        indoc! { "
21138          impl A {
21139              fn b() {
21140                  0;
21141        -         1;
21142        -         2;
21143                  3;
21144        -         4;
21145        -     }
21146        -     fn c() {
21147                  5;
21148                  6;
21149                  7;
21150              }
21151          }
21152          ˇ"
21153        }
21154        .to_string(),
21155    );
21156
21157    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21158        editor
21159            .snapshot(window, cx)
21160            .buffer_snapshot()
21161            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21162            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21163            .collect::<Vec<_>>()
21164    });
21165    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21166    assert_eq!(
21167        actual_guides,
21168        vec![
21169            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21170            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21171            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21172        ]
21173    );
21174}
21175
21176#[gpui::test]
21177async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21178    init_test(cx, |_| {});
21179    let mut cx = EditorTestContext::new(cx).await;
21180
21181    let diff_base = r#"
21182        a
21183        b
21184        c
21185        "#
21186    .unindent();
21187
21188    cx.set_state(
21189        &r#"
21190        ˇA
21191        b
21192        C
21193        "#
21194        .unindent(),
21195    );
21196    cx.set_head_text(&diff_base);
21197    cx.update_editor(|editor, window, cx| {
21198        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21199    });
21200    executor.run_until_parked();
21201
21202    let both_hunks_expanded = r#"
21203        - a
21204        + ˇA
21205          b
21206        - c
21207        + C
21208        "#
21209    .unindent();
21210
21211    cx.assert_state_with_diff(both_hunks_expanded.clone());
21212
21213    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21214        let snapshot = editor.snapshot(window, cx);
21215        let hunks = editor
21216            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21217            .collect::<Vec<_>>();
21218        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21219        let buffer_id = hunks[0].buffer_id;
21220        hunks
21221            .into_iter()
21222            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21223            .collect::<Vec<_>>()
21224    });
21225    assert_eq!(hunk_ranges.len(), 2);
21226
21227    cx.update_editor(|editor, _, cx| {
21228        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21229    });
21230    executor.run_until_parked();
21231
21232    let second_hunk_expanded = r#"
21233          ˇA
21234          b
21235        - c
21236        + C
21237        "#
21238    .unindent();
21239
21240    cx.assert_state_with_diff(second_hunk_expanded);
21241
21242    cx.update_editor(|editor, _, cx| {
21243        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21244    });
21245    executor.run_until_parked();
21246
21247    cx.assert_state_with_diff(both_hunks_expanded.clone());
21248
21249    cx.update_editor(|editor, _, cx| {
21250        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21251    });
21252    executor.run_until_parked();
21253
21254    let first_hunk_expanded = r#"
21255        - a
21256        + ˇA
21257          b
21258          C
21259        "#
21260    .unindent();
21261
21262    cx.assert_state_with_diff(first_hunk_expanded);
21263
21264    cx.update_editor(|editor, _, cx| {
21265        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21266    });
21267    executor.run_until_parked();
21268
21269    cx.assert_state_with_diff(both_hunks_expanded);
21270
21271    cx.set_state(
21272        &r#"
21273        ˇA
21274        b
21275        "#
21276        .unindent(),
21277    );
21278    cx.run_until_parked();
21279
21280    // TODO this cursor position seems bad
21281    cx.assert_state_with_diff(
21282        r#"
21283        - ˇa
21284        + A
21285          b
21286        "#
21287        .unindent(),
21288    );
21289
21290    cx.update_editor(|editor, window, cx| {
21291        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21292    });
21293
21294    cx.assert_state_with_diff(
21295        r#"
21296            - ˇa
21297            + A
21298              b
21299            - c
21300            "#
21301        .unindent(),
21302    );
21303
21304    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21305        let snapshot = editor.snapshot(window, cx);
21306        let hunks = editor
21307            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21308            .collect::<Vec<_>>();
21309        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21310        let buffer_id = hunks[0].buffer_id;
21311        hunks
21312            .into_iter()
21313            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21314            .collect::<Vec<_>>()
21315    });
21316    assert_eq!(hunk_ranges.len(), 2);
21317
21318    cx.update_editor(|editor, _, cx| {
21319        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21320    });
21321    executor.run_until_parked();
21322
21323    cx.assert_state_with_diff(
21324        r#"
21325        - ˇa
21326        + A
21327          b
21328        "#
21329        .unindent(),
21330    );
21331}
21332
21333#[gpui::test]
21334async fn test_toggle_deletion_hunk_at_start_of_file(
21335    executor: BackgroundExecutor,
21336    cx: &mut TestAppContext,
21337) {
21338    init_test(cx, |_| {});
21339    let mut cx = EditorTestContext::new(cx).await;
21340
21341    let diff_base = r#"
21342        a
21343        b
21344        c
21345        "#
21346    .unindent();
21347
21348    cx.set_state(
21349        &r#"
21350        ˇb
21351        c
21352        "#
21353        .unindent(),
21354    );
21355    cx.set_head_text(&diff_base);
21356    cx.update_editor(|editor, window, cx| {
21357        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21358    });
21359    executor.run_until_parked();
21360
21361    let hunk_expanded = r#"
21362        - a
21363          ˇb
21364          c
21365        "#
21366    .unindent();
21367
21368    cx.assert_state_with_diff(hunk_expanded.clone());
21369
21370    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21371        let snapshot = editor.snapshot(window, cx);
21372        let hunks = editor
21373            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21374            .collect::<Vec<_>>();
21375        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21376        let buffer_id = hunks[0].buffer_id;
21377        hunks
21378            .into_iter()
21379            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21380            .collect::<Vec<_>>()
21381    });
21382    assert_eq!(hunk_ranges.len(), 1);
21383
21384    cx.update_editor(|editor, _, cx| {
21385        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21386    });
21387    executor.run_until_parked();
21388
21389    let hunk_collapsed = r#"
21390          ˇb
21391          c
21392        "#
21393    .unindent();
21394
21395    cx.assert_state_with_diff(hunk_collapsed);
21396
21397    cx.update_editor(|editor, _, cx| {
21398        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21399    });
21400    executor.run_until_parked();
21401
21402    cx.assert_state_with_diff(hunk_expanded);
21403}
21404
21405#[gpui::test]
21406async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21407    init_test(cx, |_| {});
21408
21409    let fs = FakeFs::new(cx.executor());
21410    fs.insert_tree(
21411        path!("/test"),
21412        json!({
21413            ".git": {},
21414            "file-1": "ONE\n",
21415            "file-2": "TWO\n",
21416            "file-3": "THREE\n",
21417        }),
21418    )
21419    .await;
21420
21421    fs.set_head_for_repo(
21422        path!("/test/.git").as_ref(),
21423        &[
21424            ("file-1", "one\n".into()),
21425            ("file-2", "two\n".into()),
21426            ("file-3", "three\n".into()),
21427        ],
21428        "deadbeef",
21429    );
21430
21431    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21432    let mut buffers = vec![];
21433    for i in 1..=3 {
21434        let buffer = project
21435            .update(cx, |project, cx| {
21436                let path = format!(path!("/test/file-{}"), i);
21437                project.open_local_buffer(path, cx)
21438            })
21439            .await
21440            .unwrap();
21441        buffers.push(buffer);
21442    }
21443
21444    let multibuffer = cx.new(|cx| {
21445        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21446        multibuffer.set_all_diff_hunks_expanded(cx);
21447        for buffer in &buffers {
21448            let snapshot = buffer.read(cx).snapshot();
21449            multibuffer.set_excerpts_for_path(
21450                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21451                buffer.clone(),
21452                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21453                2,
21454                cx,
21455            );
21456        }
21457        multibuffer
21458    });
21459
21460    let editor = cx.add_window(|window, cx| {
21461        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21462    });
21463    cx.run_until_parked();
21464
21465    let snapshot = editor
21466        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21467        .unwrap();
21468    let hunks = snapshot
21469        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21470        .map(|hunk| match hunk {
21471            DisplayDiffHunk::Unfolded {
21472                display_row_range, ..
21473            } => display_row_range,
21474            DisplayDiffHunk::Folded { .. } => unreachable!(),
21475        })
21476        .collect::<Vec<_>>();
21477    assert_eq!(
21478        hunks,
21479        [
21480            DisplayRow(2)..DisplayRow(4),
21481            DisplayRow(7)..DisplayRow(9),
21482            DisplayRow(12)..DisplayRow(14),
21483        ]
21484    );
21485}
21486
21487#[gpui::test]
21488async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21489    init_test(cx, |_| {});
21490
21491    let mut cx = EditorTestContext::new(cx).await;
21492    cx.set_head_text(indoc! { "
21493        one
21494        two
21495        three
21496        four
21497        five
21498        "
21499    });
21500    cx.set_index_text(indoc! { "
21501        one
21502        two
21503        three
21504        four
21505        five
21506        "
21507    });
21508    cx.set_state(indoc! {"
21509        one
21510        TWO
21511        ˇTHREE
21512        FOUR
21513        five
21514    "});
21515    cx.run_until_parked();
21516    cx.update_editor(|editor, window, cx| {
21517        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21518    });
21519    cx.run_until_parked();
21520    cx.assert_index_text(Some(indoc! {"
21521        one
21522        TWO
21523        THREE
21524        FOUR
21525        five
21526    "}));
21527    cx.set_state(indoc! { "
21528        one
21529        TWO
21530        ˇTHREE-HUNDRED
21531        FOUR
21532        five
21533    "});
21534    cx.run_until_parked();
21535    cx.update_editor(|editor, window, cx| {
21536        let snapshot = editor.snapshot(window, cx);
21537        let hunks = editor
21538            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21539            .collect::<Vec<_>>();
21540        assert_eq!(hunks.len(), 1);
21541        assert_eq!(
21542            hunks[0].status(),
21543            DiffHunkStatus {
21544                kind: DiffHunkStatusKind::Modified,
21545                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21546            }
21547        );
21548
21549        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21550    });
21551    cx.run_until_parked();
21552    cx.assert_index_text(Some(indoc! {"
21553        one
21554        TWO
21555        THREE-HUNDRED
21556        FOUR
21557        five
21558    "}));
21559}
21560
21561#[gpui::test]
21562fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21563    init_test(cx, |_| {});
21564
21565    let editor = cx.add_window(|window, cx| {
21566        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21567        build_editor(buffer, window, cx)
21568    });
21569
21570    let render_args = Arc::new(Mutex::new(None));
21571    let snapshot = editor
21572        .update(cx, |editor, window, cx| {
21573            let snapshot = editor.buffer().read(cx).snapshot(cx);
21574            let range =
21575                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21576
21577            struct RenderArgs {
21578                row: MultiBufferRow,
21579                folded: bool,
21580                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21581            }
21582
21583            let crease = Crease::inline(
21584                range,
21585                FoldPlaceholder::test(),
21586                {
21587                    let toggle_callback = render_args.clone();
21588                    move |row, folded, callback, _window, _cx| {
21589                        *toggle_callback.lock() = Some(RenderArgs {
21590                            row,
21591                            folded,
21592                            callback,
21593                        });
21594                        div()
21595                    }
21596                },
21597                |_row, _folded, _window, _cx| div(),
21598            );
21599
21600            editor.insert_creases(Some(crease), cx);
21601            let snapshot = editor.snapshot(window, cx);
21602            let _div =
21603                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21604            snapshot
21605        })
21606        .unwrap();
21607
21608    let render_args = render_args.lock().take().unwrap();
21609    assert_eq!(render_args.row, MultiBufferRow(1));
21610    assert!(!render_args.folded);
21611    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21612
21613    cx.update_window(*editor, |_, window, cx| {
21614        (render_args.callback)(true, window, cx)
21615    })
21616    .unwrap();
21617    let snapshot = editor
21618        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21619        .unwrap();
21620    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21621
21622    cx.update_window(*editor, |_, window, cx| {
21623        (render_args.callback)(false, window, cx)
21624    })
21625    .unwrap();
21626    let snapshot = editor
21627        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21628        .unwrap();
21629    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21630}
21631
21632#[gpui::test]
21633async fn test_input_text(cx: &mut TestAppContext) {
21634    init_test(cx, |_| {});
21635    let mut cx = EditorTestContext::new(cx).await;
21636
21637    cx.set_state(
21638        &r#"ˇone
21639        two
21640
21641        three
21642        fourˇ
21643        five
21644
21645        siˇx"#
21646            .unindent(),
21647    );
21648
21649    cx.dispatch_action(HandleInput(String::new()));
21650    cx.assert_editor_state(
21651        &r#"ˇone
21652        two
21653
21654        three
21655        fourˇ
21656        five
21657
21658        siˇx"#
21659            .unindent(),
21660    );
21661
21662    cx.dispatch_action(HandleInput("AAAA".to_string()));
21663    cx.assert_editor_state(
21664        &r#"AAAAˇone
21665        two
21666
21667        three
21668        fourAAAAˇ
21669        five
21670
21671        siAAAAˇx"#
21672            .unindent(),
21673    );
21674}
21675
21676#[gpui::test]
21677async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21678    init_test(cx, |_| {});
21679
21680    let mut cx = EditorTestContext::new(cx).await;
21681    cx.set_state(
21682        r#"let foo = 1;
21683let foo = 2;
21684let foo = 3;
21685let fooˇ = 4;
21686let foo = 5;
21687let foo = 6;
21688let foo = 7;
21689let foo = 8;
21690let foo = 9;
21691let foo = 10;
21692let foo = 11;
21693let foo = 12;
21694let foo = 13;
21695let foo = 14;
21696let foo = 15;"#,
21697    );
21698
21699    cx.update_editor(|e, window, cx| {
21700        assert_eq!(
21701            e.next_scroll_position,
21702            NextScrollCursorCenterTopBottom::Center,
21703            "Default next scroll direction is center",
21704        );
21705
21706        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21707        assert_eq!(
21708            e.next_scroll_position,
21709            NextScrollCursorCenterTopBottom::Top,
21710            "After center, next scroll direction should be top",
21711        );
21712
21713        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21714        assert_eq!(
21715            e.next_scroll_position,
21716            NextScrollCursorCenterTopBottom::Bottom,
21717            "After top, next scroll direction should be bottom",
21718        );
21719
21720        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21721        assert_eq!(
21722            e.next_scroll_position,
21723            NextScrollCursorCenterTopBottom::Center,
21724            "After bottom, scrolling should start over",
21725        );
21726
21727        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21728        assert_eq!(
21729            e.next_scroll_position,
21730            NextScrollCursorCenterTopBottom::Top,
21731            "Scrolling continues if retriggered fast enough"
21732        );
21733    });
21734
21735    cx.executor()
21736        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21737    cx.executor().run_until_parked();
21738    cx.update_editor(|e, _, _| {
21739        assert_eq!(
21740            e.next_scroll_position,
21741            NextScrollCursorCenterTopBottom::Center,
21742            "If scrolling is not triggered fast enough, it should reset"
21743        );
21744    });
21745}
21746
21747#[gpui::test]
21748async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21749    init_test(cx, |_| {});
21750    let mut cx = EditorLspTestContext::new_rust(
21751        lsp::ServerCapabilities {
21752            definition_provider: Some(lsp::OneOf::Left(true)),
21753            references_provider: Some(lsp::OneOf::Left(true)),
21754            ..lsp::ServerCapabilities::default()
21755        },
21756        cx,
21757    )
21758    .await;
21759
21760    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21761        let go_to_definition = cx
21762            .lsp
21763            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21764                move |params, _| async move {
21765                    if empty_go_to_definition {
21766                        Ok(None)
21767                    } else {
21768                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21769                            uri: params.text_document_position_params.text_document.uri,
21770                            range: lsp::Range::new(
21771                                lsp::Position::new(4, 3),
21772                                lsp::Position::new(4, 6),
21773                            ),
21774                        })))
21775                    }
21776                },
21777            );
21778        let references = cx
21779            .lsp
21780            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21781                Ok(Some(vec![lsp::Location {
21782                    uri: params.text_document_position.text_document.uri,
21783                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21784                }]))
21785            });
21786        (go_to_definition, references)
21787    };
21788
21789    cx.set_state(
21790        &r#"fn one() {
21791            let mut a = ˇtwo();
21792        }
21793
21794        fn two() {}"#
21795            .unindent(),
21796    );
21797    set_up_lsp_handlers(false, &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 definition");
21802    assert_eq!(
21803        navigated,
21804        Navigated::Yes,
21805        "Should have navigated to definition from the GetDefinition response"
21806    );
21807    cx.assert_editor_state(
21808        &r#"fn one() {
21809            let mut a = two();
21810        }
21811
21812        fn «twoˇ»() {}"#
21813            .unindent(),
21814    );
21815
21816    let editors = cx.update_workspace(|workspace, _, cx| {
21817        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21818    });
21819    cx.update_editor(|_, _, test_editor_cx| {
21820        assert_eq!(
21821            editors.len(),
21822            1,
21823            "Initially, only one, test, editor should be open in the workspace"
21824        );
21825        assert_eq!(
21826            test_editor_cx.entity(),
21827            editors.last().expect("Asserted len is 1").clone()
21828        );
21829    });
21830
21831    set_up_lsp_handlers(true, &mut cx);
21832    let navigated = cx
21833        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21834        .await
21835        .expect("Failed to navigate to lookup references");
21836    assert_eq!(
21837        navigated,
21838        Navigated::Yes,
21839        "Should have navigated to references as a fallback after empty GoToDefinition response"
21840    );
21841    // We should not change the selections in the existing file,
21842    // if opening another milti buffer with the references
21843    cx.assert_editor_state(
21844        &r#"fn one() {
21845            let mut a = two();
21846        }
21847
21848        fn «twoˇ»() {}"#
21849            .unindent(),
21850    );
21851    let editors = cx.update_workspace(|workspace, _, cx| {
21852        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21853    });
21854    cx.update_editor(|_, _, test_editor_cx| {
21855        assert_eq!(
21856            editors.len(),
21857            2,
21858            "After falling back to references search, we open a new editor with the results"
21859        );
21860        let references_fallback_text = editors
21861            .into_iter()
21862            .find(|new_editor| *new_editor != test_editor_cx.entity())
21863            .expect("Should have one non-test editor now")
21864            .read(test_editor_cx)
21865            .text(test_editor_cx);
21866        assert_eq!(
21867            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21868            "Should use the range from the references response and not the GoToDefinition one"
21869        );
21870    });
21871}
21872
21873#[gpui::test]
21874async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21875    init_test(cx, |_| {});
21876    cx.update(|cx| {
21877        let mut editor_settings = EditorSettings::get_global(cx).clone();
21878        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21879        EditorSettings::override_global(editor_settings, cx);
21880    });
21881    let mut cx = EditorLspTestContext::new_rust(
21882        lsp::ServerCapabilities {
21883            definition_provider: Some(lsp::OneOf::Left(true)),
21884            references_provider: Some(lsp::OneOf::Left(true)),
21885            ..lsp::ServerCapabilities::default()
21886        },
21887        cx,
21888    )
21889    .await;
21890    let original_state = r#"fn one() {
21891        let mut a = ˇtwo();
21892    }
21893
21894    fn two() {}"#
21895        .unindent();
21896    cx.set_state(&original_state);
21897
21898    let mut go_to_definition = cx
21899        .lsp
21900        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21901            move |_, _| async move { Ok(None) },
21902        );
21903    let _references = cx
21904        .lsp
21905        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21906            panic!("Should not call for references with no go to definition fallback")
21907        });
21908
21909    let navigated = cx
21910        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21911        .await
21912        .expect("Failed to navigate to lookup references");
21913    go_to_definition
21914        .next()
21915        .await
21916        .expect("Should have called the go_to_definition handler");
21917
21918    assert_eq!(
21919        navigated,
21920        Navigated::No,
21921        "Should have navigated to references as a fallback after empty GoToDefinition response"
21922    );
21923    cx.assert_editor_state(&original_state);
21924    let editors = cx.update_workspace(|workspace, _, cx| {
21925        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21926    });
21927    cx.update_editor(|_, _, _| {
21928        assert_eq!(
21929            editors.len(),
21930            1,
21931            "After unsuccessful fallback, no other editor should have been opened"
21932        );
21933    });
21934}
21935
21936#[gpui::test]
21937async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21938    init_test(cx, |_| {});
21939    let mut cx = EditorLspTestContext::new_rust(
21940        lsp::ServerCapabilities {
21941            references_provider: Some(lsp::OneOf::Left(true)),
21942            ..lsp::ServerCapabilities::default()
21943        },
21944        cx,
21945    )
21946    .await;
21947
21948    cx.set_state(
21949        &r#"
21950        fn one() {
21951            let mut a = two();
21952        }
21953
21954        fn ˇtwo() {}"#
21955            .unindent(),
21956    );
21957    cx.lsp
21958        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21959            Ok(Some(vec![
21960                lsp::Location {
21961                    uri: params.text_document_position.text_document.uri.clone(),
21962                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21963                },
21964                lsp::Location {
21965                    uri: params.text_document_position.text_document.uri,
21966                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21967                },
21968            ]))
21969        });
21970    let navigated = cx
21971        .update_editor(|editor, window, cx| {
21972            editor.find_all_references(&FindAllReferences, window, cx)
21973        })
21974        .unwrap()
21975        .await
21976        .expect("Failed to navigate to references");
21977    assert_eq!(
21978        navigated,
21979        Navigated::Yes,
21980        "Should have navigated to references from the FindAllReferences response"
21981    );
21982    cx.assert_editor_state(
21983        &r#"fn one() {
21984            let mut a = two();
21985        }
21986
21987        fn ˇtwo() {}"#
21988            .unindent(),
21989    );
21990
21991    let editors = cx.update_workspace(|workspace, _, cx| {
21992        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21993    });
21994    cx.update_editor(|_, _, _| {
21995        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21996    });
21997
21998    cx.set_state(
21999        &r#"fn one() {
22000            let mut a = ˇtwo();
22001        }
22002
22003        fn two() {}"#
22004            .unindent(),
22005    );
22006    let navigated = cx
22007        .update_editor(|editor, window, cx| {
22008            editor.find_all_references(&FindAllReferences, window, cx)
22009        })
22010        .unwrap()
22011        .await
22012        .expect("Failed to navigate to references");
22013    assert_eq!(
22014        navigated,
22015        Navigated::Yes,
22016        "Should have navigated to references from the FindAllReferences response"
22017    );
22018    cx.assert_editor_state(
22019        &r#"fn one() {
22020            let mut a = ˇtwo();
22021        }
22022
22023        fn two() {}"#
22024            .unindent(),
22025    );
22026    let editors = cx.update_workspace(|workspace, _, cx| {
22027        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22028    });
22029    cx.update_editor(|_, _, _| {
22030        assert_eq!(
22031            editors.len(),
22032            2,
22033            "should have re-used the previous multibuffer"
22034        );
22035    });
22036
22037    cx.set_state(
22038        &r#"fn one() {
22039            let mut a = ˇtwo();
22040        }
22041        fn three() {}
22042        fn two() {}"#
22043            .unindent(),
22044    );
22045    cx.lsp
22046        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22047            Ok(Some(vec![
22048                lsp::Location {
22049                    uri: params.text_document_position.text_document.uri.clone(),
22050                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22051                },
22052                lsp::Location {
22053                    uri: params.text_document_position.text_document.uri,
22054                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22055                },
22056            ]))
22057        });
22058    let navigated = cx
22059        .update_editor(|editor, window, cx| {
22060            editor.find_all_references(&FindAllReferences, window, cx)
22061        })
22062        .unwrap()
22063        .await
22064        .expect("Failed to navigate to references");
22065    assert_eq!(
22066        navigated,
22067        Navigated::Yes,
22068        "Should have navigated to references from the FindAllReferences response"
22069    );
22070    cx.assert_editor_state(
22071        &r#"fn one() {
22072                let mut a = ˇtwo();
22073            }
22074            fn three() {}
22075            fn two() {}"#
22076            .unindent(),
22077    );
22078    let editors = cx.update_workspace(|workspace, _, cx| {
22079        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22080    });
22081    cx.update_editor(|_, _, _| {
22082        assert_eq!(
22083            editors.len(),
22084            3,
22085            "should have used a new multibuffer as offsets changed"
22086        );
22087    });
22088}
22089#[gpui::test]
22090async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22091    init_test(cx, |_| {});
22092
22093    let language = Arc::new(Language::new(
22094        LanguageConfig::default(),
22095        Some(tree_sitter_rust::LANGUAGE.into()),
22096    ));
22097
22098    let text = r#"
22099        #[cfg(test)]
22100        mod tests() {
22101            #[test]
22102            fn runnable_1() {
22103                let a = 1;
22104            }
22105
22106            #[test]
22107            fn runnable_2() {
22108                let a = 1;
22109                let b = 2;
22110            }
22111        }
22112    "#
22113    .unindent();
22114
22115    let fs = FakeFs::new(cx.executor());
22116    fs.insert_file("/file.rs", Default::default()).await;
22117
22118    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22119    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22120    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22121    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22122    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22123
22124    let editor = cx.new_window_entity(|window, cx| {
22125        Editor::new(
22126            EditorMode::full(),
22127            multi_buffer,
22128            Some(project.clone()),
22129            window,
22130            cx,
22131        )
22132    });
22133
22134    editor.update_in(cx, |editor, window, cx| {
22135        let snapshot = editor.buffer().read(cx).snapshot(cx);
22136        editor.tasks.insert(
22137            (buffer.read(cx).remote_id(), 3),
22138            RunnableTasks {
22139                templates: vec![],
22140                offset: snapshot.anchor_before(43),
22141                column: 0,
22142                extra_variables: HashMap::default(),
22143                context_range: BufferOffset(43)..BufferOffset(85),
22144            },
22145        );
22146        editor.tasks.insert(
22147            (buffer.read(cx).remote_id(), 8),
22148            RunnableTasks {
22149                templates: vec![],
22150                offset: snapshot.anchor_before(86),
22151                column: 0,
22152                extra_variables: HashMap::default(),
22153                context_range: BufferOffset(86)..BufferOffset(191),
22154            },
22155        );
22156
22157        // Test finding task when cursor is inside function body
22158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22159            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22160        });
22161        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22162        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22163
22164        // Test finding task when cursor is on function name
22165        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22166            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22167        });
22168        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22169        assert_eq!(row, 8, "Should find task when cursor is on function name");
22170    });
22171}
22172
22173#[gpui::test]
22174async fn test_folding_buffers(cx: &mut TestAppContext) {
22175    init_test(cx, |_| {});
22176
22177    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22178    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22179    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22180
22181    let fs = FakeFs::new(cx.executor());
22182    fs.insert_tree(
22183        path!("/a"),
22184        json!({
22185            "first.rs": sample_text_1,
22186            "second.rs": sample_text_2,
22187            "third.rs": sample_text_3,
22188        }),
22189    )
22190    .await;
22191    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22192    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22193    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22194    let worktree = project.update(cx, |project, cx| {
22195        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22196        assert_eq!(worktrees.len(), 1);
22197        worktrees.pop().unwrap()
22198    });
22199    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22200
22201    let buffer_1 = project
22202        .update(cx, |project, cx| {
22203            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22204        })
22205        .await
22206        .unwrap();
22207    let buffer_2 = project
22208        .update(cx, |project, cx| {
22209            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22210        })
22211        .await
22212        .unwrap();
22213    let buffer_3 = project
22214        .update(cx, |project, cx| {
22215            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22216        })
22217        .await
22218        .unwrap();
22219
22220    let multi_buffer = cx.new(|cx| {
22221        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22222        multi_buffer.push_excerpts(
22223            buffer_1.clone(),
22224            [
22225                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22226                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22227                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22228            ],
22229            cx,
22230        );
22231        multi_buffer.push_excerpts(
22232            buffer_2.clone(),
22233            [
22234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22237            ],
22238            cx,
22239        );
22240        multi_buffer.push_excerpts(
22241            buffer_3.clone(),
22242            [
22243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22246            ],
22247            cx,
22248        );
22249        multi_buffer
22250    });
22251    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22252        Editor::new(
22253            EditorMode::full(),
22254            multi_buffer.clone(),
22255            Some(project.clone()),
22256            window,
22257            cx,
22258        )
22259    });
22260
22261    assert_eq!(
22262        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22263        "\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",
22264    );
22265
22266    multi_buffer_editor.update(cx, |editor, cx| {
22267        editor.fold_buffer(buffer_1.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\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22272        "After folding the first buffer, its text should not be displayed"
22273    );
22274
22275    multi_buffer_editor.update(cx, |editor, cx| {
22276        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22277    });
22278    assert_eq!(
22279        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22280        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22281        "After folding the second buffer, its text should not be displayed"
22282    );
22283
22284    multi_buffer_editor.update(cx, |editor, cx| {
22285        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22286    });
22287    assert_eq!(
22288        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22289        "\n\n\n\n\n",
22290        "After folding the third buffer, its text should not be displayed"
22291    );
22292
22293    // Emulate selection inside the fold logic, that should work
22294    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22295        editor
22296            .snapshot(window, cx)
22297            .next_line_boundary(Point::new(0, 4));
22298    });
22299
22300    multi_buffer_editor.update(cx, |editor, cx| {
22301        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22302    });
22303    assert_eq!(
22304        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22305        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22306        "After unfolding the second buffer, its text should be displayed"
22307    );
22308
22309    // Typing inside of buffer 1 causes that buffer to be unfolded.
22310    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22311        assert_eq!(
22312            multi_buffer
22313                .read(cx)
22314                .snapshot(cx)
22315                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22316                .collect::<String>(),
22317            "bbbb"
22318        );
22319        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22320            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22321        });
22322        editor.handle_input("B", window, cx);
22323    });
22324
22325    assert_eq!(
22326        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22327        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22328        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22329    );
22330
22331    multi_buffer_editor.update(cx, |editor, cx| {
22332        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22333    });
22334    assert_eq!(
22335        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22336        "\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",
22337        "After unfolding the all buffers, all original text should be displayed"
22338    );
22339}
22340
22341#[gpui::test]
22342async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22343    init_test(cx, |_| {});
22344
22345    let sample_text_1 = "1111\n2222\n3333".to_string();
22346    let sample_text_2 = "4444\n5555\n6666".to_string();
22347    let sample_text_3 = "7777\n8888\n9999".to_string();
22348
22349    let fs = FakeFs::new(cx.executor());
22350    fs.insert_tree(
22351        path!("/a"),
22352        json!({
22353            "first.rs": sample_text_1,
22354            "second.rs": sample_text_2,
22355            "third.rs": sample_text_3,
22356        }),
22357    )
22358    .await;
22359    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22360    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22361    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22362    let worktree = project.update(cx, |project, cx| {
22363        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22364        assert_eq!(worktrees.len(), 1);
22365        worktrees.pop().unwrap()
22366    });
22367    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22368
22369    let buffer_1 = project
22370        .update(cx, |project, cx| {
22371            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22372        })
22373        .await
22374        .unwrap();
22375    let buffer_2 = project
22376        .update(cx, |project, cx| {
22377            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22378        })
22379        .await
22380        .unwrap();
22381    let buffer_3 = project
22382        .update(cx, |project, cx| {
22383            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22384        })
22385        .await
22386        .unwrap();
22387
22388    let multi_buffer = cx.new(|cx| {
22389        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22390        multi_buffer.push_excerpts(
22391            buffer_1.clone(),
22392            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22393            cx,
22394        );
22395        multi_buffer.push_excerpts(
22396            buffer_2.clone(),
22397            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22398            cx,
22399        );
22400        multi_buffer.push_excerpts(
22401            buffer_3.clone(),
22402            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22403            cx,
22404        );
22405        multi_buffer
22406    });
22407
22408    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22409        Editor::new(
22410            EditorMode::full(),
22411            multi_buffer,
22412            Some(project.clone()),
22413            window,
22414            cx,
22415        )
22416    });
22417
22418    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22419    assert_eq!(
22420        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22421        full_text,
22422    );
22423
22424    multi_buffer_editor.update(cx, |editor, cx| {
22425        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22426    });
22427    assert_eq!(
22428        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22429        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22430        "After folding the first buffer, its text should not be displayed"
22431    );
22432
22433    multi_buffer_editor.update(cx, |editor, cx| {
22434        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22435    });
22436
22437    assert_eq!(
22438        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22439        "\n\n\n\n\n\n7777\n8888\n9999",
22440        "After folding the second buffer, its text should not be displayed"
22441    );
22442
22443    multi_buffer_editor.update(cx, |editor, cx| {
22444        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22445    });
22446    assert_eq!(
22447        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22448        "\n\n\n\n\n",
22449        "After folding the third buffer, its text should not be displayed"
22450    );
22451
22452    multi_buffer_editor.update(cx, |editor, cx| {
22453        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22454    });
22455    assert_eq!(
22456        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22457        "\n\n\n\n4444\n5555\n6666\n\n",
22458        "After unfolding the second buffer, its text should be displayed"
22459    );
22460
22461    multi_buffer_editor.update(cx, |editor, cx| {
22462        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22463    });
22464    assert_eq!(
22465        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22466        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22467        "After unfolding the first buffer, its text should be displayed"
22468    );
22469
22470    multi_buffer_editor.update(cx, |editor, cx| {
22471        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22472    });
22473    assert_eq!(
22474        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22475        full_text,
22476        "After unfolding all buffers, all original text should be displayed"
22477    );
22478}
22479
22480#[gpui::test]
22481async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22482    init_test(cx, |_| {});
22483
22484    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22485
22486    let fs = FakeFs::new(cx.executor());
22487    fs.insert_tree(
22488        path!("/a"),
22489        json!({
22490            "main.rs": sample_text,
22491        }),
22492    )
22493    .await;
22494    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22495    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22496    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22497    let worktree = project.update(cx, |project, cx| {
22498        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22499        assert_eq!(worktrees.len(), 1);
22500        worktrees.pop().unwrap()
22501    });
22502    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22503
22504    let buffer_1 = project
22505        .update(cx, |project, cx| {
22506            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22507        })
22508        .await
22509        .unwrap();
22510
22511    let multi_buffer = cx.new(|cx| {
22512        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22513        multi_buffer.push_excerpts(
22514            buffer_1.clone(),
22515            [ExcerptRange::new(
22516                Point::new(0, 0)
22517                    ..Point::new(
22518                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22519                        0,
22520                    ),
22521            )],
22522            cx,
22523        );
22524        multi_buffer
22525    });
22526    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22527        Editor::new(
22528            EditorMode::full(),
22529            multi_buffer,
22530            Some(project.clone()),
22531            window,
22532            cx,
22533        )
22534    });
22535
22536    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22537    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22538        enum TestHighlight {}
22539        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22540        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22541        editor.highlight_text::<TestHighlight>(
22542            vec![highlight_range.clone()],
22543            HighlightStyle::color(Hsla::green()),
22544            cx,
22545        );
22546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22547            s.select_ranges(Some(highlight_range))
22548        });
22549    });
22550
22551    let full_text = format!("\n\n{sample_text}");
22552    assert_eq!(
22553        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22554        full_text,
22555    );
22556}
22557
22558#[gpui::test]
22559async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22560    init_test(cx, |_| {});
22561    cx.update(|cx| {
22562        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22563            "keymaps/default-linux.json",
22564            cx,
22565        )
22566        .unwrap();
22567        cx.bind_keys(default_key_bindings);
22568    });
22569
22570    let (editor, cx) = cx.add_window_view(|window, cx| {
22571        let multi_buffer = MultiBuffer::build_multi(
22572            [
22573                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22574                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22575                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22576                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22577            ],
22578            cx,
22579        );
22580        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22581
22582        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22583        // fold all but the second buffer, so that we test navigating between two
22584        // adjacent folded buffers, as well as folded buffers at the start and
22585        // end the multibuffer
22586        editor.fold_buffer(buffer_ids[0], cx);
22587        editor.fold_buffer(buffer_ids[2], cx);
22588        editor.fold_buffer(buffer_ids[3], cx);
22589
22590        editor
22591    });
22592    cx.simulate_resize(size(px(1000.), px(1000.)));
22593
22594    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22595    cx.assert_excerpts_with_selections(indoc! {"
22596        [EXCERPT]
22597        ˇ[FOLDED]
22598        [EXCERPT]
22599        a1
22600        b1
22601        [EXCERPT]
22602        [FOLDED]
22603        [EXCERPT]
22604        [FOLDED]
22605        "
22606    });
22607    cx.simulate_keystroke("down");
22608    cx.assert_excerpts_with_selections(indoc! {"
22609        [EXCERPT]
22610        [FOLDED]
22611        [EXCERPT]
22612        ˇa1
22613        b1
22614        [EXCERPT]
22615        [FOLDED]
22616        [EXCERPT]
22617        [FOLDED]
22618        "
22619    });
22620    cx.simulate_keystroke("down");
22621    cx.assert_excerpts_with_selections(indoc! {"
22622        [EXCERPT]
22623        [FOLDED]
22624        [EXCERPT]
22625        a1
22626        ˇb1
22627        [EXCERPT]
22628        [FOLDED]
22629        [EXCERPT]
22630        [FOLDED]
22631        "
22632    });
22633    cx.simulate_keystroke("down");
22634    cx.assert_excerpts_with_selections(indoc! {"
22635        [EXCERPT]
22636        [FOLDED]
22637        [EXCERPT]
22638        a1
22639        b1
22640        ˇ[EXCERPT]
22641        [FOLDED]
22642        [EXCERPT]
22643        [FOLDED]
22644        "
22645    });
22646    cx.simulate_keystroke("down");
22647    cx.assert_excerpts_with_selections(indoc! {"
22648        [EXCERPT]
22649        [FOLDED]
22650        [EXCERPT]
22651        a1
22652        b1
22653        [EXCERPT]
22654        ˇ[FOLDED]
22655        [EXCERPT]
22656        [FOLDED]
22657        "
22658    });
22659    for _ in 0..5 {
22660        cx.simulate_keystroke("down");
22661        cx.assert_excerpts_with_selections(indoc! {"
22662            [EXCERPT]
22663            [FOLDED]
22664            [EXCERPT]
22665            a1
22666            b1
22667            [EXCERPT]
22668            [FOLDED]
22669            [EXCERPT]
22670            ˇ[FOLDED]
22671            "
22672        });
22673    }
22674
22675    cx.simulate_keystroke("up");
22676    cx.assert_excerpts_with_selections(indoc! {"
22677        [EXCERPT]
22678        [FOLDED]
22679        [EXCERPT]
22680        a1
22681        b1
22682        [EXCERPT]
22683        ˇ[FOLDED]
22684        [EXCERPT]
22685        [FOLDED]
22686        "
22687    });
22688    cx.simulate_keystroke("up");
22689    cx.assert_excerpts_with_selections(indoc! {"
22690        [EXCERPT]
22691        [FOLDED]
22692        [EXCERPT]
22693        a1
22694        b1
22695        ˇ[EXCERPT]
22696        [FOLDED]
22697        [EXCERPT]
22698        [FOLDED]
22699        "
22700    });
22701    cx.simulate_keystroke("up");
22702    cx.assert_excerpts_with_selections(indoc! {"
22703        [EXCERPT]
22704        [FOLDED]
22705        [EXCERPT]
22706        a1
22707        ˇb1
22708        [EXCERPT]
22709        [FOLDED]
22710        [EXCERPT]
22711        [FOLDED]
22712        "
22713    });
22714    cx.simulate_keystroke("up");
22715    cx.assert_excerpts_with_selections(indoc! {"
22716        [EXCERPT]
22717        [FOLDED]
22718        [EXCERPT]
22719        ˇa1
22720        b1
22721        [EXCERPT]
22722        [FOLDED]
22723        [EXCERPT]
22724        [FOLDED]
22725        "
22726    });
22727    for _ in 0..5 {
22728        cx.simulate_keystroke("up");
22729        cx.assert_excerpts_with_selections(indoc! {"
22730            [EXCERPT]
22731            ˇ[FOLDED]
22732            [EXCERPT]
22733            a1
22734            b1
22735            [EXCERPT]
22736            [FOLDED]
22737            [EXCERPT]
22738            [FOLDED]
22739            "
22740        });
22741    }
22742}
22743
22744#[gpui::test]
22745async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22746    init_test(cx, |_| {});
22747
22748    // Simple insertion
22749    assert_highlighted_edits(
22750        "Hello, world!",
22751        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22752        true,
22753        cx,
22754        |highlighted_edits, cx| {
22755            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22756            assert_eq!(highlighted_edits.highlights.len(), 1);
22757            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22758            assert_eq!(
22759                highlighted_edits.highlights[0].1.background_color,
22760                Some(cx.theme().status().created_background)
22761            );
22762        },
22763    )
22764    .await;
22765
22766    // Replacement
22767    assert_highlighted_edits(
22768        "This is a test.",
22769        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22770        false,
22771        cx,
22772        |highlighted_edits, cx| {
22773            assert_eq!(highlighted_edits.text, "That is a test.");
22774            assert_eq!(highlighted_edits.highlights.len(), 1);
22775            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22776            assert_eq!(
22777                highlighted_edits.highlights[0].1.background_color,
22778                Some(cx.theme().status().created_background)
22779            );
22780        },
22781    )
22782    .await;
22783
22784    // Multiple edits
22785    assert_highlighted_edits(
22786        "Hello, world!",
22787        vec![
22788            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22789            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22790        ],
22791        false,
22792        cx,
22793        |highlighted_edits, cx| {
22794            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22795            assert_eq!(highlighted_edits.highlights.len(), 2);
22796            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22797            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22798            assert_eq!(
22799                highlighted_edits.highlights[0].1.background_color,
22800                Some(cx.theme().status().created_background)
22801            );
22802            assert_eq!(
22803                highlighted_edits.highlights[1].1.background_color,
22804                Some(cx.theme().status().created_background)
22805            );
22806        },
22807    )
22808    .await;
22809
22810    // Multiple lines with edits
22811    assert_highlighted_edits(
22812        "First line\nSecond line\nThird line\nFourth line",
22813        vec![
22814            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22815            (
22816                Point::new(2, 0)..Point::new(2, 10),
22817                "New third line".to_string(),
22818            ),
22819            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22820        ],
22821        false,
22822        cx,
22823        |highlighted_edits, cx| {
22824            assert_eq!(
22825                highlighted_edits.text,
22826                "Second modified\nNew third line\nFourth updated line"
22827            );
22828            assert_eq!(highlighted_edits.highlights.len(), 3);
22829            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22830            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22831            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22832            for highlight in &highlighted_edits.highlights {
22833                assert_eq!(
22834                    highlight.1.background_color,
22835                    Some(cx.theme().status().created_background)
22836                );
22837            }
22838        },
22839    )
22840    .await;
22841}
22842
22843#[gpui::test]
22844async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22845    init_test(cx, |_| {});
22846
22847    // Deletion
22848    assert_highlighted_edits(
22849        "Hello, world!",
22850        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22851        true,
22852        cx,
22853        |highlighted_edits, cx| {
22854            assert_eq!(highlighted_edits.text, "Hello, world!");
22855            assert_eq!(highlighted_edits.highlights.len(), 1);
22856            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22857            assert_eq!(
22858                highlighted_edits.highlights[0].1.background_color,
22859                Some(cx.theme().status().deleted_background)
22860            );
22861        },
22862    )
22863    .await;
22864
22865    // Insertion
22866    assert_highlighted_edits(
22867        "Hello, world!",
22868        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22869        true,
22870        cx,
22871        |highlighted_edits, cx| {
22872            assert_eq!(highlighted_edits.highlights.len(), 1);
22873            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22874            assert_eq!(
22875                highlighted_edits.highlights[0].1.background_color,
22876                Some(cx.theme().status().created_background)
22877            );
22878        },
22879    )
22880    .await;
22881}
22882
22883async fn assert_highlighted_edits(
22884    text: &str,
22885    edits: Vec<(Range<Point>, String)>,
22886    include_deletions: bool,
22887    cx: &mut TestAppContext,
22888    assertion_fn: impl Fn(HighlightedText, &App),
22889) {
22890    let window = cx.add_window(|window, cx| {
22891        let buffer = MultiBuffer::build_simple(text, cx);
22892        Editor::new(EditorMode::full(), buffer, None, window, cx)
22893    });
22894    let cx = &mut VisualTestContext::from_window(*window, cx);
22895
22896    let (buffer, snapshot) = window
22897        .update(cx, |editor, _window, cx| {
22898            (
22899                editor.buffer().clone(),
22900                editor.buffer().read(cx).snapshot(cx),
22901            )
22902        })
22903        .unwrap();
22904
22905    let edits = edits
22906        .into_iter()
22907        .map(|(range, edit)| {
22908            (
22909                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22910                edit,
22911            )
22912        })
22913        .collect::<Vec<_>>();
22914
22915    let text_anchor_edits = edits
22916        .clone()
22917        .into_iter()
22918        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22919        .collect::<Vec<_>>();
22920
22921    let edit_preview = window
22922        .update(cx, |_, _window, cx| {
22923            buffer
22924                .read(cx)
22925                .as_singleton()
22926                .unwrap()
22927                .read(cx)
22928                .preview_edits(text_anchor_edits.into(), cx)
22929        })
22930        .unwrap()
22931        .await;
22932
22933    cx.update(|_window, cx| {
22934        let highlighted_edits = edit_prediction_edit_text(
22935            snapshot.as_singleton().unwrap().2,
22936            &edits,
22937            &edit_preview,
22938            include_deletions,
22939            cx,
22940        );
22941        assertion_fn(highlighted_edits, cx)
22942    });
22943}
22944
22945#[track_caller]
22946fn assert_breakpoint(
22947    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22948    path: &Arc<Path>,
22949    expected: Vec<(u32, Breakpoint)>,
22950) {
22951    if expected.is_empty() {
22952        assert!(!breakpoints.contains_key(path), "{}", path.display());
22953    } else {
22954        let mut breakpoint = breakpoints
22955            .get(path)
22956            .unwrap()
22957            .iter()
22958            .map(|breakpoint| {
22959                (
22960                    breakpoint.row,
22961                    Breakpoint {
22962                        message: breakpoint.message.clone(),
22963                        state: breakpoint.state,
22964                        condition: breakpoint.condition.clone(),
22965                        hit_condition: breakpoint.hit_condition.clone(),
22966                    },
22967                )
22968            })
22969            .collect::<Vec<_>>();
22970
22971        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22972
22973        assert_eq!(expected, breakpoint);
22974    }
22975}
22976
22977fn add_log_breakpoint_at_cursor(
22978    editor: &mut Editor,
22979    log_message: &str,
22980    window: &mut Window,
22981    cx: &mut Context<Editor>,
22982) {
22983    let (anchor, bp) = editor
22984        .breakpoints_at_cursors(window, cx)
22985        .first()
22986        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22987        .unwrap_or_else(|| {
22988            let snapshot = editor.snapshot(window, cx);
22989            let cursor_position: Point =
22990                editor.selections.newest(&snapshot.display_snapshot).head();
22991
22992            let breakpoint_position = snapshot
22993                .buffer_snapshot()
22994                .anchor_before(Point::new(cursor_position.row, 0));
22995
22996            (breakpoint_position, Breakpoint::new_log(log_message))
22997        });
22998
22999    editor.edit_breakpoint_at_anchor(
23000        anchor,
23001        bp,
23002        BreakpointEditAction::EditLogMessage(log_message.into()),
23003        cx,
23004    );
23005}
23006
23007#[gpui::test]
23008async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23009    init_test(cx, |_| {});
23010
23011    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23012    let fs = FakeFs::new(cx.executor());
23013    fs.insert_tree(
23014        path!("/a"),
23015        json!({
23016            "main.rs": sample_text,
23017        }),
23018    )
23019    .await;
23020    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23021    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23022    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23023
23024    let fs = FakeFs::new(cx.executor());
23025    fs.insert_tree(
23026        path!("/a"),
23027        json!({
23028            "main.rs": sample_text,
23029        }),
23030    )
23031    .await;
23032    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23033    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23034    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23035    let worktree_id = workspace
23036        .update(cx, |workspace, _window, cx| {
23037            workspace.project().update(cx, |project, cx| {
23038                project.worktrees(cx).next().unwrap().read(cx).id()
23039            })
23040        })
23041        .unwrap();
23042
23043    let buffer = project
23044        .update(cx, |project, cx| {
23045            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23046        })
23047        .await
23048        .unwrap();
23049
23050    let (editor, cx) = cx.add_window_view(|window, cx| {
23051        Editor::new(
23052            EditorMode::full(),
23053            MultiBuffer::build_from_buffer(buffer, cx),
23054            Some(project.clone()),
23055            window,
23056            cx,
23057        )
23058    });
23059
23060    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23061    let abs_path = project.read_with(cx, |project, cx| {
23062        project
23063            .absolute_path(&project_path, cx)
23064            .map(Arc::from)
23065            .unwrap()
23066    });
23067
23068    // assert we can add breakpoint on the first line
23069    editor.update_in(cx, |editor, window, cx| {
23070        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23071        editor.move_to_end(&MoveToEnd, window, cx);
23072        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23073    });
23074
23075    let breakpoints = editor.update(cx, |editor, cx| {
23076        editor
23077            .breakpoint_store()
23078            .as_ref()
23079            .unwrap()
23080            .read(cx)
23081            .all_source_breakpoints(cx)
23082    });
23083
23084    assert_eq!(1, breakpoints.len());
23085    assert_breakpoint(
23086        &breakpoints,
23087        &abs_path,
23088        vec![
23089            (0, Breakpoint::new_standard()),
23090            (3, Breakpoint::new_standard()),
23091        ],
23092    );
23093
23094    editor.update_in(cx, |editor, window, cx| {
23095        editor.move_to_beginning(&MoveToBeginning, window, cx);
23096        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23097    });
23098
23099    let breakpoints = editor.update(cx, |editor, cx| {
23100        editor
23101            .breakpoint_store()
23102            .as_ref()
23103            .unwrap()
23104            .read(cx)
23105            .all_source_breakpoints(cx)
23106    });
23107
23108    assert_eq!(1, breakpoints.len());
23109    assert_breakpoint(
23110        &breakpoints,
23111        &abs_path,
23112        vec![(3, Breakpoint::new_standard())],
23113    );
23114
23115    editor.update_in(cx, |editor, window, cx| {
23116        editor.move_to_end(&MoveToEnd, window, cx);
23117        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23118    });
23119
23120    let breakpoints = editor.update(cx, |editor, cx| {
23121        editor
23122            .breakpoint_store()
23123            .as_ref()
23124            .unwrap()
23125            .read(cx)
23126            .all_source_breakpoints(cx)
23127    });
23128
23129    assert_eq!(0, breakpoints.len());
23130    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23131}
23132
23133#[gpui::test]
23134async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23135    init_test(cx, |_| {});
23136
23137    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23138
23139    let fs = FakeFs::new(cx.executor());
23140    fs.insert_tree(
23141        path!("/a"),
23142        json!({
23143            "main.rs": sample_text,
23144        }),
23145    )
23146    .await;
23147    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23148    let (workspace, cx) =
23149        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23150
23151    let worktree_id = workspace.update(cx, |workspace, cx| {
23152        workspace.project().update(cx, |project, cx| {
23153            project.worktrees(cx).next().unwrap().read(cx).id()
23154        })
23155    });
23156
23157    let buffer = project
23158        .update(cx, |project, cx| {
23159            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23160        })
23161        .await
23162        .unwrap();
23163
23164    let (editor, cx) = cx.add_window_view(|window, cx| {
23165        Editor::new(
23166            EditorMode::full(),
23167            MultiBuffer::build_from_buffer(buffer, cx),
23168            Some(project.clone()),
23169            window,
23170            cx,
23171        )
23172    });
23173
23174    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23175    let abs_path = project.read_with(cx, |project, cx| {
23176        project
23177            .absolute_path(&project_path, cx)
23178            .map(Arc::from)
23179            .unwrap()
23180    });
23181
23182    editor.update_in(cx, |editor, window, cx| {
23183        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23184    });
23185
23186    let breakpoints = editor.update(cx, |editor, cx| {
23187        editor
23188            .breakpoint_store()
23189            .as_ref()
23190            .unwrap()
23191            .read(cx)
23192            .all_source_breakpoints(cx)
23193    });
23194
23195    assert_breakpoint(
23196        &breakpoints,
23197        &abs_path,
23198        vec![(0, Breakpoint::new_log("hello world"))],
23199    );
23200
23201    // Removing a log message from a log breakpoint should remove it
23202    editor.update_in(cx, |editor, window, cx| {
23203        add_log_breakpoint_at_cursor(editor, "", window, cx);
23204    });
23205
23206    let breakpoints = editor.update(cx, |editor, cx| {
23207        editor
23208            .breakpoint_store()
23209            .as_ref()
23210            .unwrap()
23211            .read(cx)
23212            .all_source_breakpoints(cx)
23213    });
23214
23215    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23216
23217    editor.update_in(cx, |editor, window, cx| {
23218        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23219        editor.move_to_end(&MoveToEnd, window, cx);
23220        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23221        // Not adding a log message to a standard breakpoint shouldn't remove it
23222        add_log_breakpoint_at_cursor(editor, "", window, cx);
23223    });
23224
23225    let breakpoints = editor.update(cx, |editor, cx| {
23226        editor
23227            .breakpoint_store()
23228            .as_ref()
23229            .unwrap()
23230            .read(cx)
23231            .all_source_breakpoints(cx)
23232    });
23233
23234    assert_breakpoint(
23235        &breakpoints,
23236        &abs_path,
23237        vec![
23238            (0, Breakpoint::new_standard()),
23239            (3, Breakpoint::new_standard()),
23240        ],
23241    );
23242
23243    editor.update_in(cx, |editor, window, cx| {
23244        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23245    });
23246
23247    let breakpoints = editor.update(cx, |editor, cx| {
23248        editor
23249            .breakpoint_store()
23250            .as_ref()
23251            .unwrap()
23252            .read(cx)
23253            .all_source_breakpoints(cx)
23254    });
23255
23256    assert_breakpoint(
23257        &breakpoints,
23258        &abs_path,
23259        vec![
23260            (0, Breakpoint::new_standard()),
23261            (3, Breakpoint::new_log("hello world")),
23262        ],
23263    );
23264
23265    editor.update_in(cx, |editor, window, cx| {
23266        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23267    });
23268
23269    let breakpoints = editor.update(cx, |editor, cx| {
23270        editor
23271            .breakpoint_store()
23272            .as_ref()
23273            .unwrap()
23274            .read(cx)
23275            .all_source_breakpoints(cx)
23276    });
23277
23278    assert_breakpoint(
23279        &breakpoints,
23280        &abs_path,
23281        vec![
23282            (0, Breakpoint::new_standard()),
23283            (3, Breakpoint::new_log("hello Earth!!")),
23284        ],
23285    );
23286}
23287
23288/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23289/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23290/// or when breakpoints were placed out of order. This tests for a regression too
23291#[gpui::test]
23292async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23293    init_test(cx, |_| {});
23294
23295    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23296    let fs = FakeFs::new(cx.executor());
23297    fs.insert_tree(
23298        path!("/a"),
23299        json!({
23300            "main.rs": sample_text,
23301        }),
23302    )
23303    .await;
23304    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23305    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23306    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23307
23308    let fs = FakeFs::new(cx.executor());
23309    fs.insert_tree(
23310        path!("/a"),
23311        json!({
23312            "main.rs": sample_text,
23313        }),
23314    )
23315    .await;
23316    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23317    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23318    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23319    let worktree_id = workspace
23320        .update(cx, |workspace, _window, cx| {
23321            workspace.project().update(cx, |project, cx| {
23322                project.worktrees(cx).next().unwrap().read(cx).id()
23323            })
23324        })
23325        .unwrap();
23326
23327    let buffer = project
23328        .update(cx, |project, cx| {
23329            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23330        })
23331        .await
23332        .unwrap();
23333
23334    let (editor, cx) = cx.add_window_view(|window, cx| {
23335        Editor::new(
23336            EditorMode::full(),
23337            MultiBuffer::build_from_buffer(buffer, cx),
23338            Some(project.clone()),
23339            window,
23340            cx,
23341        )
23342    });
23343
23344    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23345    let abs_path = project.read_with(cx, |project, cx| {
23346        project
23347            .absolute_path(&project_path, cx)
23348            .map(Arc::from)
23349            .unwrap()
23350    });
23351
23352    // assert we can add breakpoint on the first line
23353    editor.update_in(cx, |editor, window, cx| {
23354        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23355        editor.move_to_end(&MoveToEnd, window, cx);
23356        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23357        editor.move_up(&MoveUp, window, cx);
23358        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23359    });
23360
23361    let breakpoints = editor.update(cx, |editor, cx| {
23362        editor
23363            .breakpoint_store()
23364            .as_ref()
23365            .unwrap()
23366            .read(cx)
23367            .all_source_breakpoints(cx)
23368    });
23369
23370    assert_eq!(1, breakpoints.len());
23371    assert_breakpoint(
23372        &breakpoints,
23373        &abs_path,
23374        vec![
23375            (0, Breakpoint::new_standard()),
23376            (2, Breakpoint::new_standard()),
23377            (3, Breakpoint::new_standard()),
23378        ],
23379    );
23380
23381    editor.update_in(cx, |editor, window, cx| {
23382        editor.move_to_beginning(&MoveToBeginning, window, cx);
23383        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23384        editor.move_to_end(&MoveToEnd, window, cx);
23385        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23386        // Disabling a breakpoint that doesn't exist should do nothing
23387        editor.move_up(&MoveUp, window, cx);
23388        editor.move_up(&MoveUp, window, cx);
23389        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23390    });
23391
23392    let breakpoints = editor.update(cx, |editor, cx| {
23393        editor
23394            .breakpoint_store()
23395            .as_ref()
23396            .unwrap()
23397            .read(cx)
23398            .all_source_breakpoints(cx)
23399    });
23400
23401    let disable_breakpoint = {
23402        let mut bp = Breakpoint::new_standard();
23403        bp.state = BreakpointState::Disabled;
23404        bp
23405    };
23406
23407    assert_eq!(1, breakpoints.len());
23408    assert_breakpoint(
23409        &breakpoints,
23410        &abs_path,
23411        vec![
23412            (0, disable_breakpoint.clone()),
23413            (2, Breakpoint::new_standard()),
23414            (3, disable_breakpoint.clone()),
23415        ],
23416    );
23417
23418    editor.update_in(cx, |editor, window, cx| {
23419        editor.move_to_beginning(&MoveToBeginning, window, cx);
23420        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23421        editor.move_to_end(&MoveToEnd, window, cx);
23422        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23423        editor.move_up(&MoveUp, window, cx);
23424        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23425    });
23426
23427    let breakpoints = editor.update(cx, |editor, cx| {
23428        editor
23429            .breakpoint_store()
23430            .as_ref()
23431            .unwrap()
23432            .read(cx)
23433            .all_source_breakpoints(cx)
23434    });
23435
23436    assert_eq!(1, breakpoints.len());
23437    assert_breakpoint(
23438        &breakpoints,
23439        &abs_path,
23440        vec![
23441            (0, Breakpoint::new_standard()),
23442            (2, disable_breakpoint),
23443            (3, Breakpoint::new_standard()),
23444        ],
23445    );
23446}
23447
23448#[gpui::test]
23449async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23450    init_test(cx, |_| {});
23451    let capabilities = lsp::ServerCapabilities {
23452        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23453            prepare_provider: Some(true),
23454            work_done_progress_options: Default::default(),
23455        })),
23456        ..Default::default()
23457    };
23458    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23459
23460    cx.set_state(indoc! {"
23461        struct Fˇoo {}
23462    "});
23463
23464    cx.update_editor(|editor, _, cx| {
23465        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23466        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23467        editor.highlight_background::<DocumentHighlightRead>(
23468            &[highlight_range],
23469            |theme| theme.colors().editor_document_highlight_read_background,
23470            cx,
23471        );
23472    });
23473
23474    let mut prepare_rename_handler = cx
23475        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23476            move |_, _, _| async move {
23477                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23478                    start: lsp::Position {
23479                        line: 0,
23480                        character: 7,
23481                    },
23482                    end: lsp::Position {
23483                        line: 0,
23484                        character: 10,
23485                    },
23486                })))
23487            },
23488        );
23489    let prepare_rename_task = cx
23490        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23491        .expect("Prepare rename was not started");
23492    prepare_rename_handler.next().await.unwrap();
23493    prepare_rename_task.await.expect("Prepare rename failed");
23494
23495    let mut rename_handler =
23496        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23497            let edit = lsp::TextEdit {
23498                range: lsp::Range {
23499                    start: lsp::Position {
23500                        line: 0,
23501                        character: 7,
23502                    },
23503                    end: lsp::Position {
23504                        line: 0,
23505                        character: 10,
23506                    },
23507                },
23508                new_text: "FooRenamed".to_string(),
23509            };
23510            Ok(Some(lsp::WorkspaceEdit::new(
23511                // Specify the same edit twice
23512                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23513            )))
23514        });
23515    let rename_task = cx
23516        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23517        .expect("Confirm rename was not started");
23518    rename_handler.next().await.unwrap();
23519    rename_task.await.expect("Confirm rename failed");
23520    cx.run_until_parked();
23521
23522    // Despite two edits, only one is actually applied as those are identical
23523    cx.assert_editor_state(indoc! {"
23524        struct FooRenamedˇ {}
23525    "});
23526}
23527
23528#[gpui::test]
23529async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23530    init_test(cx, |_| {});
23531    // These capabilities indicate that the server does not support prepare rename.
23532    let capabilities = lsp::ServerCapabilities {
23533        rename_provider: Some(lsp::OneOf::Left(true)),
23534        ..Default::default()
23535    };
23536    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23537
23538    cx.set_state(indoc! {"
23539        struct Fˇoo {}
23540    "});
23541
23542    cx.update_editor(|editor, _window, cx| {
23543        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23544        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23545        editor.highlight_background::<DocumentHighlightRead>(
23546            &[highlight_range],
23547            |theme| theme.colors().editor_document_highlight_read_background,
23548            cx,
23549        );
23550    });
23551
23552    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23553        .expect("Prepare rename was not started")
23554        .await
23555        .expect("Prepare rename failed");
23556
23557    let mut rename_handler =
23558        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23559            let edit = lsp::TextEdit {
23560                range: lsp::Range {
23561                    start: lsp::Position {
23562                        line: 0,
23563                        character: 7,
23564                    },
23565                    end: lsp::Position {
23566                        line: 0,
23567                        character: 10,
23568                    },
23569                },
23570                new_text: "FooRenamed".to_string(),
23571            };
23572            Ok(Some(lsp::WorkspaceEdit::new(
23573                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23574            )))
23575        });
23576    let rename_task = cx
23577        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23578        .expect("Confirm rename was not started");
23579    rename_handler.next().await.unwrap();
23580    rename_task.await.expect("Confirm rename failed");
23581    cx.run_until_parked();
23582
23583    // Correct range is renamed, as `surrounding_word` is used to find it.
23584    cx.assert_editor_state(indoc! {"
23585        struct FooRenamedˇ {}
23586    "});
23587}
23588
23589#[gpui::test]
23590async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23591    init_test(cx, |_| {});
23592    let mut cx = EditorTestContext::new(cx).await;
23593
23594    let language = Arc::new(
23595        Language::new(
23596            LanguageConfig::default(),
23597            Some(tree_sitter_html::LANGUAGE.into()),
23598        )
23599        .with_brackets_query(
23600            r#"
23601            ("<" @open "/>" @close)
23602            ("</" @open ">" @close)
23603            ("<" @open ">" @close)
23604            ("\"" @open "\"" @close)
23605            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23606        "#,
23607        )
23608        .unwrap(),
23609    );
23610    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23611
23612    cx.set_state(indoc! {"
23613        <span>ˇ</span>
23614    "});
23615    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23616    cx.assert_editor_state(indoc! {"
23617        <span>
23618        ˇ
23619        </span>
23620    "});
23621
23622    cx.set_state(indoc! {"
23623        <span><span></span>ˇ</span>
23624    "});
23625    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23626    cx.assert_editor_state(indoc! {"
23627        <span><span></span>
23628        ˇ</span>
23629    "});
23630
23631    cx.set_state(indoc! {"
23632        <span>ˇ
23633        </span>
23634    "});
23635    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23636    cx.assert_editor_state(indoc! {"
23637        <span>
23638        ˇ
23639        </span>
23640    "});
23641}
23642
23643#[gpui::test(iterations = 10)]
23644async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23645    init_test(cx, |_| {});
23646
23647    let fs = FakeFs::new(cx.executor());
23648    fs.insert_tree(
23649        path!("/dir"),
23650        json!({
23651            "a.ts": "a",
23652        }),
23653    )
23654    .await;
23655
23656    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23657    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23658    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23659
23660    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23661    language_registry.add(Arc::new(Language::new(
23662        LanguageConfig {
23663            name: "TypeScript".into(),
23664            matcher: LanguageMatcher {
23665                path_suffixes: vec!["ts".to_string()],
23666                ..Default::default()
23667            },
23668            ..Default::default()
23669        },
23670        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23671    )));
23672    let mut fake_language_servers = language_registry.register_fake_lsp(
23673        "TypeScript",
23674        FakeLspAdapter {
23675            capabilities: lsp::ServerCapabilities {
23676                code_lens_provider: Some(lsp::CodeLensOptions {
23677                    resolve_provider: Some(true),
23678                }),
23679                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23680                    commands: vec!["_the/command".to_string()],
23681                    ..lsp::ExecuteCommandOptions::default()
23682                }),
23683                ..lsp::ServerCapabilities::default()
23684            },
23685            ..FakeLspAdapter::default()
23686        },
23687    );
23688
23689    let editor = workspace
23690        .update(cx, |workspace, window, cx| {
23691            workspace.open_abs_path(
23692                PathBuf::from(path!("/dir/a.ts")),
23693                OpenOptions::default(),
23694                window,
23695                cx,
23696            )
23697        })
23698        .unwrap()
23699        .await
23700        .unwrap()
23701        .downcast::<Editor>()
23702        .unwrap();
23703    cx.executor().run_until_parked();
23704
23705    let fake_server = fake_language_servers.next().await.unwrap();
23706
23707    let buffer = editor.update(cx, |editor, cx| {
23708        editor
23709            .buffer()
23710            .read(cx)
23711            .as_singleton()
23712            .expect("have opened a single file by path")
23713    });
23714
23715    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23716    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23717    drop(buffer_snapshot);
23718    let actions = cx
23719        .update_window(*workspace, |_, window, cx| {
23720            project.code_actions(&buffer, anchor..anchor, window, cx)
23721        })
23722        .unwrap();
23723
23724    fake_server
23725        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23726            Ok(Some(vec![
23727                lsp::CodeLens {
23728                    range: lsp::Range::default(),
23729                    command: Some(lsp::Command {
23730                        title: "Code lens command".to_owned(),
23731                        command: "_the/command".to_owned(),
23732                        arguments: None,
23733                    }),
23734                    data: None,
23735                },
23736                lsp::CodeLens {
23737                    range: lsp::Range::default(),
23738                    command: Some(lsp::Command {
23739                        title: "Command not in capabilities".to_owned(),
23740                        command: "not in capabilities".to_owned(),
23741                        arguments: None,
23742                    }),
23743                    data: None,
23744                },
23745                lsp::CodeLens {
23746                    range: lsp::Range {
23747                        start: lsp::Position {
23748                            line: 1,
23749                            character: 1,
23750                        },
23751                        end: lsp::Position {
23752                            line: 1,
23753                            character: 1,
23754                        },
23755                    },
23756                    command: Some(lsp::Command {
23757                        title: "Command not in range".to_owned(),
23758                        command: "_the/command".to_owned(),
23759                        arguments: None,
23760                    }),
23761                    data: None,
23762                },
23763            ]))
23764        })
23765        .next()
23766        .await;
23767
23768    let actions = actions.await.unwrap();
23769    assert_eq!(
23770        actions.len(),
23771        1,
23772        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23773    );
23774    let action = actions[0].clone();
23775    let apply = project.update(cx, |project, cx| {
23776        project.apply_code_action(buffer.clone(), action, true, cx)
23777    });
23778
23779    // Resolving the code action does not populate its edits. In absence of
23780    // edits, we must execute the given command.
23781    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23782        |mut lens, _| async move {
23783            let lens_command = lens.command.as_mut().expect("should have a command");
23784            assert_eq!(lens_command.title, "Code lens command");
23785            lens_command.arguments = Some(vec![json!("the-argument")]);
23786            Ok(lens)
23787        },
23788    );
23789
23790    // While executing the command, the language server sends the editor
23791    // a `workspaceEdit` request.
23792    fake_server
23793        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23794            let fake = fake_server.clone();
23795            move |params, _| {
23796                assert_eq!(params.command, "_the/command");
23797                let fake = fake.clone();
23798                async move {
23799                    fake.server
23800                        .request::<lsp::request::ApplyWorkspaceEdit>(
23801                            lsp::ApplyWorkspaceEditParams {
23802                                label: None,
23803                                edit: lsp::WorkspaceEdit {
23804                                    changes: Some(
23805                                        [(
23806                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23807                                            vec![lsp::TextEdit {
23808                                                range: lsp::Range::new(
23809                                                    lsp::Position::new(0, 0),
23810                                                    lsp::Position::new(0, 0),
23811                                                ),
23812                                                new_text: "X".into(),
23813                                            }],
23814                                        )]
23815                                        .into_iter()
23816                                        .collect(),
23817                                    ),
23818                                    ..lsp::WorkspaceEdit::default()
23819                                },
23820                            },
23821                        )
23822                        .await
23823                        .into_response()
23824                        .unwrap();
23825                    Ok(Some(json!(null)))
23826                }
23827            }
23828        })
23829        .next()
23830        .await;
23831
23832    // Applying the code lens command returns a project transaction containing the edits
23833    // sent by the language server in its `workspaceEdit` request.
23834    let transaction = apply.await.unwrap();
23835    assert!(transaction.0.contains_key(&buffer));
23836    buffer.update(cx, |buffer, cx| {
23837        assert_eq!(buffer.text(), "Xa");
23838        buffer.undo(cx);
23839        assert_eq!(buffer.text(), "a");
23840    });
23841
23842    let actions_after_edits = cx
23843        .update_window(*workspace, |_, window, cx| {
23844            project.code_actions(&buffer, anchor..anchor, window, cx)
23845        })
23846        .unwrap()
23847        .await
23848        .unwrap();
23849    assert_eq!(
23850        actions, actions_after_edits,
23851        "For the same selection, same code lens actions should be returned"
23852    );
23853
23854    let _responses =
23855        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23856            panic!("No more code lens requests are expected");
23857        });
23858    editor.update_in(cx, |editor, window, cx| {
23859        editor.select_all(&SelectAll, window, cx);
23860    });
23861    cx.executor().run_until_parked();
23862    let new_actions = cx
23863        .update_window(*workspace, |_, window, cx| {
23864            project.code_actions(&buffer, anchor..anchor, window, cx)
23865        })
23866        .unwrap()
23867        .await
23868        .unwrap();
23869    assert_eq!(
23870        actions, new_actions,
23871        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23872    );
23873}
23874
23875#[gpui::test]
23876async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23877    init_test(cx, |_| {});
23878
23879    let fs = FakeFs::new(cx.executor());
23880    let main_text = r#"fn main() {
23881println!("1");
23882println!("2");
23883println!("3");
23884println!("4");
23885println!("5");
23886}"#;
23887    let lib_text = "mod foo {}";
23888    fs.insert_tree(
23889        path!("/a"),
23890        json!({
23891            "lib.rs": lib_text,
23892            "main.rs": main_text,
23893        }),
23894    )
23895    .await;
23896
23897    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23898    let (workspace, cx) =
23899        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23900    let worktree_id = workspace.update(cx, |workspace, cx| {
23901        workspace.project().update(cx, |project, cx| {
23902            project.worktrees(cx).next().unwrap().read(cx).id()
23903        })
23904    });
23905
23906    let expected_ranges = vec![
23907        Point::new(0, 0)..Point::new(0, 0),
23908        Point::new(1, 0)..Point::new(1, 1),
23909        Point::new(2, 0)..Point::new(2, 2),
23910        Point::new(3, 0)..Point::new(3, 3),
23911    ];
23912
23913    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23914    let editor_1 = workspace
23915        .update_in(cx, |workspace, window, cx| {
23916            workspace.open_path(
23917                (worktree_id, rel_path("main.rs")),
23918                Some(pane_1.downgrade()),
23919                true,
23920                window,
23921                cx,
23922            )
23923        })
23924        .unwrap()
23925        .await
23926        .downcast::<Editor>()
23927        .unwrap();
23928    pane_1.update(cx, |pane, cx| {
23929        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23930        open_editor.update(cx, |editor, cx| {
23931            assert_eq!(
23932                editor.display_text(cx),
23933                main_text,
23934                "Original main.rs text on initial open",
23935            );
23936            assert_eq!(
23937                editor
23938                    .selections
23939                    .all::<Point>(&editor.display_snapshot(cx))
23940                    .into_iter()
23941                    .map(|s| s.range())
23942                    .collect::<Vec<_>>(),
23943                vec![Point::zero()..Point::zero()],
23944                "Default selections on initial open",
23945            );
23946        })
23947    });
23948    editor_1.update_in(cx, |editor, window, cx| {
23949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23950            s.select_ranges(expected_ranges.clone());
23951        });
23952    });
23953
23954    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23955        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23956    });
23957    let editor_2 = workspace
23958        .update_in(cx, |workspace, window, cx| {
23959            workspace.open_path(
23960                (worktree_id, rel_path("main.rs")),
23961                Some(pane_2.downgrade()),
23962                true,
23963                window,
23964                cx,
23965            )
23966        })
23967        .unwrap()
23968        .await
23969        .downcast::<Editor>()
23970        .unwrap();
23971    pane_2.update(cx, |pane, cx| {
23972        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23973        open_editor.update(cx, |editor, cx| {
23974            assert_eq!(
23975                editor.display_text(cx),
23976                main_text,
23977                "Original main.rs text on initial open in another panel",
23978            );
23979            assert_eq!(
23980                editor
23981                    .selections
23982                    .all::<Point>(&editor.display_snapshot(cx))
23983                    .into_iter()
23984                    .map(|s| s.range())
23985                    .collect::<Vec<_>>(),
23986                vec![Point::zero()..Point::zero()],
23987                "Default selections on initial open in another panel",
23988            );
23989        })
23990    });
23991
23992    editor_2.update_in(cx, |editor, window, cx| {
23993        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23994    });
23995
23996    let _other_editor_1 = workspace
23997        .update_in(cx, |workspace, window, cx| {
23998            workspace.open_path(
23999                (worktree_id, rel_path("lib.rs")),
24000                Some(pane_1.downgrade()),
24001                true,
24002                window,
24003                cx,
24004            )
24005        })
24006        .unwrap()
24007        .await
24008        .downcast::<Editor>()
24009        .unwrap();
24010    pane_1
24011        .update_in(cx, |pane, window, cx| {
24012            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24013        })
24014        .await
24015        .unwrap();
24016    drop(editor_1);
24017    pane_1.update(cx, |pane, cx| {
24018        pane.active_item()
24019            .unwrap()
24020            .downcast::<Editor>()
24021            .unwrap()
24022            .update(cx, |editor, cx| {
24023                assert_eq!(
24024                    editor.display_text(cx),
24025                    lib_text,
24026                    "Other file should be open and active",
24027                );
24028            });
24029        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24030    });
24031
24032    let _other_editor_2 = workspace
24033        .update_in(cx, |workspace, window, cx| {
24034            workspace.open_path(
24035                (worktree_id, rel_path("lib.rs")),
24036                Some(pane_2.downgrade()),
24037                true,
24038                window,
24039                cx,
24040            )
24041        })
24042        .unwrap()
24043        .await
24044        .downcast::<Editor>()
24045        .unwrap();
24046    pane_2
24047        .update_in(cx, |pane, window, cx| {
24048            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24049        })
24050        .await
24051        .unwrap();
24052    drop(editor_2);
24053    pane_2.update(cx, |pane, cx| {
24054        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24055        open_editor.update(cx, |editor, cx| {
24056            assert_eq!(
24057                editor.display_text(cx),
24058                lib_text,
24059                "Other file should be open and active in another panel too",
24060            );
24061        });
24062        assert_eq!(
24063            pane.items().count(),
24064            1,
24065            "No other editors should be open in another pane",
24066        );
24067    });
24068
24069    let _editor_1_reopened = workspace
24070        .update_in(cx, |workspace, window, cx| {
24071            workspace.open_path(
24072                (worktree_id, rel_path("main.rs")),
24073                Some(pane_1.downgrade()),
24074                true,
24075                window,
24076                cx,
24077            )
24078        })
24079        .unwrap()
24080        .await
24081        .downcast::<Editor>()
24082        .unwrap();
24083    let _editor_2_reopened = workspace
24084        .update_in(cx, |workspace, window, cx| {
24085            workspace.open_path(
24086                (worktree_id, rel_path("main.rs")),
24087                Some(pane_2.downgrade()),
24088                true,
24089                window,
24090                cx,
24091            )
24092        })
24093        .unwrap()
24094        .await
24095        .downcast::<Editor>()
24096        .unwrap();
24097    pane_1.update(cx, |pane, cx| {
24098        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24099        open_editor.update(cx, |editor, cx| {
24100            assert_eq!(
24101                editor.display_text(cx),
24102                main_text,
24103                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24104            );
24105            assert_eq!(
24106                editor
24107                    .selections
24108                    .all::<Point>(&editor.display_snapshot(cx))
24109                    .into_iter()
24110                    .map(|s| s.range())
24111                    .collect::<Vec<_>>(),
24112                expected_ranges,
24113                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24114            );
24115        })
24116    });
24117    pane_2.update(cx, |pane, cx| {
24118        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24119        open_editor.update(cx, |editor, cx| {
24120            assert_eq!(
24121                editor.display_text(cx),
24122                r#"fn main() {
24123⋯rintln!("1");
24124⋯intln!("2");
24125⋯ntln!("3");
24126println!("4");
24127println!("5");
24128}"#,
24129                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24130            );
24131            assert_eq!(
24132                editor
24133                    .selections
24134                    .all::<Point>(&editor.display_snapshot(cx))
24135                    .into_iter()
24136                    .map(|s| s.range())
24137                    .collect::<Vec<_>>(),
24138                vec![Point::zero()..Point::zero()],
24139                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24140            );
24141        })
24142    });
24143}
24144
24145#[gpui::test]
24146async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24147    init_test(cx, |_| {});
24148
24149    let fs = FakeFs::new(cx.executor());
24150    let main_text = r#"fn main() {
24151println!("1");
24152println!("2");
24153println!("3");
24154println!("4");
24155println!("5");
24156}"#;
24157    let lib_text = "mod foo {}";
24158    fs.insert_tree(
24159        path!("/a"),
24160        json!({
24161            "lib.rs": lib_text,
24162            "main.rs": main_text,
24163        }),
24164    )
24165    .await;
24166
24167    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24168    let (workspace, cx) =
24169        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24170    let worktree_id = workspace.update(cx, |workspace, cx| {
24171        workspace.project().update(cx, |project, cx| {
24172            project.worktrees(cx).next().unwrap().read(cx).id()
24173        })
24174    });
24175
24176    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24177    let editor = workspace
24178        .update_in(cx, |workspace, window, cx| {
24179            workspace.open_path(
24180                (worktree_id, rel_path("main.rs")),
24181                Some(pane.downgrade()),
24182                true,
24183                window,
24184                cx,
24185            )
24186        })
24187        .unwrap()
24188        .await
24189        .downcast::<Editor>()
24190        .unwrap();
24191    pane.update(cx, |pane, cx| {
24192        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24193        open_editor.update(cx, |editor, cx| {
24194            assert_eq!(
24195                editor.display_text(cx),
24196                main_text,
24197                "Original main.rs text on initial open",
24198            );
24199        })
24200    });
24201    editor.update_in(cx, |editor, window, cx| {
24202        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24203    });
24204
24205    cx.update_global(|store: &mut SettingsStore, cx| {
24206        store.update_user_settings(cx, |s| {
24207            s.workspace.restore_on_file_reopen = Some(false);
24208        });
24209    });
24210    editor.update_in(cx, |editor, window, cx| {
24211        editor.fold_ranges(
24212            vec![
24213                Point::new(1, 0)..Point::new(1, 1),
24214                Point::new(2, 0)..Point::new(2, 2),
24215                Point::new(3, 0)..Point::new(3, 3),
24216            ],
24217            false,
24218            window,
24219            cx,
24220        );
24221    });
24222    pane.update_in(cx, |pane, window, cx| {
24223        pane.close_all_items(&CloseAllItems::default(), window, cx)
24224    })
24225    .await
24226    .unwrap();
24227    pane.update(cx, |pane, _| {
24228        assert!(pane.active_item().is_none());
24229    });
24230    cx.update_global(|store: &mut SettingsStore, cx| {
24231        store.update_user_settings(cx, |s| {
24232            s.workspace.restore_on_file_reopen = Some(true);
24233        });
24234    });
24235
24236    let _editor_reopened = workspace
24237        .update_in(cx, |workspace, window, cx| {
24238            workspace.open_path(
24239                (worktree_id, rel_path("main.rs")),
24240                Some(pane.downgrade()),
24241                true,
24242                window,
24243                cx,
24244            )
24245        })
24246        .unwrap()
24247        .await
24248        .downcast::<Editor>()
24249        .unwrap();
24250    pane.update(cx, |pane, cx| {
24251        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24252        open_editor.update(cx, |editor, cx| {
24253            assert_eq!(
24254                editor.display_text(cx),
24255                main_text,
24256                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24257            );
24258        })
24259    });
24260}
24261
24262#[gpui::test]
24263async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24264    struct EmptyModalView {
24265        focus_handle: gpui::FocusHandle,
24266    }
24267    impl EventEmitter<DismissEvent> for EmptyModalView {}
24268    impl Render for EmptyModalView {
24269        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24270            div()
24271        }
24272    }
24273    impl Focusable for EmptyModalView {
24274        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24275            self.focus_handle.clone()
24276        }
24277    }
24278    impl workspace::ModalView for EmptyModalView {}
24279    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24280        EmptyModalView {
24281            focus_handle: cx.focus_handle(),
24282        }
24283    }
24284
24285    init_test(cx, |_| {});
24286
24287    let fs = FakeFs::new(cx.executor());
24288    let project = Project::test(fs, [], cx).await;
24289    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24290    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24291    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24292    let editor = cx.new_window_entity(|window, cx| {
24293        Editor::new(
24294            EditorMode::full(),
24295            buffer,
24296            Some(project.clone()),
24297            window,
24298            cx,
24299        )
24300    });
24301    workspace
24302        .update(cx, |workspace, window, cx| {
24303            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24304        })
24305        .unwrap();
24306    editor.update_in(cx, |editor, window, cx| {
24307        editor.open_context_menu(&OpenContextMenu, window, cx);
24308        assert!(editor.mouse_context_menu.is_some());
24309    });
24310    workspace
24311        .update(cx, |workspace, window, cx| {
24312            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24313        })
24314        .unwrap();
24315    cx.read(|cx| {
24316        assert!(editor.read(cx).mouse_context_menu.is_none());
24317    });
24318}
24319
24320fn set_linked_edit_ranges(
24321    opening: (Point, Point),
24322    closing: (Point, Point),
24323    editor: &mut Editor,
24324    cx: &mut Context<Editor>,
24325) {
24326    let Some((buffer, _)) = editor
24327        .buffer
24328        .read(cx)
24329        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24330    else {
24331        panic!("Failed to get buffer for selection position");
24332    };
24333    let buffer = buffer.read(cx);
24334    let buffer_id = buffer.remote_id();
24335    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24336    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24337    let mut linked_ranges = HashMap::default();
24338    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24339    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24340}
24341
24342#[gpui::test]
24343async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24344    init_test(cx, |_| {});
24345
24346    let fs = FakeFs::new(cx.executor());
24347    fs.insert_file(path!("/file.html"), Default::default())
24348        .await;
24349
24350    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24351
24352    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24353    let html_language = Arc::new(Language::new(
24354        LanguageConfig {
24355            name: "HTML".into(),
24356            matcher: LanguageMatcher {
24357                path_suffixes: vec!["html".to_string()],
24358                ..LanguageMatcher::default()
24359            },
24360            brackets: BracketPairConfig {
24361                pairs: vec![BracketPair {
24362                    start: "<".into(),
24363                    end: ">".into(),
24364                    close: true,
24365                    ..Default::default()
24366                }],
24367                ..Default::default()
24368            },
24369            ..Default::default()
24370        },
24371        Some(tree_sitter_html::LANGUAGE.into()),
24372    ));
24373    language_registry.add(html_language);
24374    let mut fake_servers = language_registry.register_fake_lsp(
24375        "HTML",
24376        FakeLspAdapter {
24377            capabilities: lsp::ServerCapabilities {
24378                completion_provider: Some(lsp::CompletionOptions {
24379                    resolve_provider: Some(true),
24380                    ..Default::default()
24381                }),
24382                ..Default::default()
24383            },
24384            ..Default::default()
24385        },
24386    );
24387
24388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24389    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24390
24391    let worktree_id = workspace
24392        .update(cx, |workspace, _window, cx| {
24393            workspace.project().update(cx, |project, cx| {
24394                project.worktrees(cx).next().unwrap().read(cx).id()
24395            })
24396        })
24397        .unwrap();
24398    project
24399        .update(cx, |project, cx| {
24400            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24401        })
24402        .await
24403        .unwrap();
24404    let editor = workspace
24405        .update(cx, |workspace, window, cx| {
24406            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24407        })
24408        .unwrap()
24409        .await
24410        .unwrap()
24411        .downcast::<Editor>()
24412        .unwrap();
24413
24414    let fake_server = fake_servers.next().await.unwrap();
24415    editor.update_in(cx, |editor, window, cx| {
24416        editor.set_text("<ad></ad>", window, cx);
24417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24418            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24419        });
24420        set_linked_edit_ranges(
24421            (Point::new(0, 1), Point::new(0, 3)),
24422            (Point::new(0, 6), Point::new(0, 8)),
24423            editor,
24424            cx,
24425        );
24426    });
24427    let mut completion_handle =
24428        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24429            Ok(Some(lsp::CompletionResponse::Array(vec![
24430                lsp::CompletionItem {
24431                    label: "head".to_string(),
24432                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24433                        lsp::InsertReplaceEdit {
24434                            new_text: "head".to_string(),
24435                            insert: lsp::Range::new(
24436                                lsp::Position::new(0, 1),
24437                                lsp::Position::new(0, 3),
24438                            ),
24439                            replace: lsp::Range::new(
24440                                lsp::Position::new(0, 1),
24441                                lsp::Position::new(0, 3),
24442                            ),
24443                        },
24444                    )),
24445                    ..Default::default()
24446                },
24447            ])))
24448        });
24449    editor.update_in(cx, |editor, window, cx| {
24450        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24451    });
24452    cx.run_until_parked();
24453    completion_handle.next().await.unwrap();
24454    editor.update(cx, |editor, _| {
24455        assert!(
24456            editor.context_menu_visible(),
24457            "Completion menu should be visible"
24458        );
24459    });
24460    editor.update_in(cx, |editor, window, cx| {
24461        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24462    });
24463    cx.executor().run_until_parked();
24464    editor.update(cx, |editor, cx| {
24465        assert_eq!(editor.text(cx), "<head></head>");
24466    });
24467}
24468
24469#[gpui::test]
24470async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24471    init_test(cx, |_| {});
24472
24473    let mut cx = EditorTestContext::new(cx).await;
24474    let language = Arc::new(Language::new(
24475        LanguageConfig {
24476            name: "TSX".into(),
24477            matcher: LanguageMatcher {
24478                path_suffixes: vec!["tsx".to_string()],
24479                ..LanguageMatcher::default()
24480            },
24481            brackets: BracketPairConfig {
24482                pairs: vec![BracketPair {
24483                    start: "<".into(),
24484                    end: ">".into(),
24485                    close: true,
24486                    ..Default::default()
24487                }],
24488                ..Default::default()
24489            },
24490            linked_edit_characters: HashSet::from_iter(['.']),
24491            ..Default::default()
24492        },
24493        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24494    ));
24495    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24496
24497    // Test typing > does not extend linked pair
24498    cx.set_state("<divˇ<div></div>");
24499    cx.update_editor(|editor, _, cx| {
24500        set_linked_edit_ranges(
24501            (Point::new(0, 1), Point::new(0, 4)),
24502            (Point::new(0, 11), Point::new(0, 14)),
24503            editor,
24504            cx,
24505        );
24506    });
24507    cx.update_editor(|editor, window, cx| {
24508        editor.handle_input(">", window, cx);
24509    });
24510    cx.assert_editor_state("<div>ˇ<div></div>");
24511
24512    // Test typing . do extend linked pair
24513    cx.set_state("<Animatedˇ></Animated>");
24514    cx.update_editor(|editor, _, cx| {
24515        set_linked_edit_ranges(
24516            (Point::new(0, 1), Point::new(0, 9)),
24517            (Point::new(0, 12), Point::new(0, 20)),
24518            editor,
24519            cx,
24520        );
24521    });
24522    cx.update_editor(|editor, window, cx| {
24523        editor.handle_input(".", window, cx);
24524    });
24525    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24526    cx.update_editor(|editor, _, cx| {
24527        set_linked_edit_ranges(
24528            (Point::new(0, 1), Point::new(0, 10)),
24529            (Point::new(0, 13), Point::new(0, 21)),
24530            editor,
24531            cx,
24532        );
24533    });
24534    cx.update_editor(|editor, window, cx| {
24535        editor.handle_input("V", window, cx);
24536    });
24537    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24538}
24539
24540#[gpui::test]
24541async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24542    init_test(cx, |_| {});
24543
24544    let fs = FakeFs::new(cx.executor());
24545    fs.insert_tree(
24546        path!("/root"),
24547        json!({
24548            "a": {
24549                "main.rs": "fn main() {}",
24550            },
24551            "foo": {
24552                "bar": {
24553                    "external_file.rs": "pub mod external {}",
24554                }
24555            }
24556        }),
24557    )
24558    .await;
24559
24560    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24561    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24562    language_registry.add(rust_lang());
24563    let _fake_servers = language_registry.register_fake_lsp(
24564        "Rust",
24565        FakeLspAdapter {
24566            ..FakeLspAdapter::default()
24567        },
24568    );
24569    let (workspace, cx) =
24570        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24571    let worktree_id = workspace.update(cx, |workspace, cx| {
24572        workspace.project().update(cx, |project, cx| {
24573            project.worktrees(cx).next().unwrap().read(cx).id()
24574        })
24575    });
24576
24577    let assert_language_servers_count =
24578        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24579            project.update(cx, |project, cx| {
24580                let current = project
24581                    .lsp_store()
24582                    .read(cx)
24583                    .as_local()
24584                    .unwrap()
24585                    .language_servers
24586                    .len();
24587                assert_eq!(expected, current, "{context}");
24588            });
24589        };
24590
24591    assert_language_servers_count(
24592        0,
24593        "No servers should be running before any file is open",
24594        cx,
24595    );
24596    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24597    let main_editor = workspace
24598        .update_in(cx, |workspace, window, cx| {
24599            workspace.open_path(
24600                (worktree_id, rel_path("main.rs")),
24601                Some(pane.downgrade()),
24602                true,
24603                window,
24604                cx,
24605            )
24606        })
24607        .unwrap()
24608        .await
24609        .downcast::<Editor>()
24610        .unwrap();
24611    pane.update(cx, |pane, cx| {
24612        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24613        open_editor.update(cx, |editor, cx| {
24614            assert_eq!(
24615                editor.display_text(cx),
24616                "fn main() {}",
24617                "Original main.rs text on initial open",
24618            );
24619        });
24620        assert_eq!(open_editor, main_editor);
24621    });
24622    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24623
24624    let external_editor = workspace
24625        .update_in(cx, |workspace, window, cx| {
24626            workspace.open_abs_path(
24627                PathBuf::from("/root/foo/bar/external_file.rs"),
24628                OpenOptions::default(),
24629                window,
24630                cx,
24631            )
24632        })
24633        .await
24634        .expect("opening external file")
24635        .downcast::<Editor>()
24636        .expect("downcasted external file's open element to editor");
24637    pane.update(cx, |pane, cx| {
24638        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24639        open_editor.update(cx, |editor, cx| {
24640            assert_eq!(
24641                editor.display_text(cx),
24642                "pub mod external {}",
24643                "External file is open now",
24644            );
24645        });
24646        assert_eq!(open_editor, external_editor);
24647    });
24648    assert_language_servers_count(
24649        1,
24650        "Second, external, *.rs file should join the existing server",
24651        cx,
24652    );
24653
24654    pane.update_in(cx, |pane, window, cx| {
24655        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24656    })
24657    .await
24658    .unwrap();
24659    pane.update_in(cx, |pane, window, cx| {
24660        pane.navigate_backward(&Default::default(), window, cx);
24661    });
24662    cx.run_until_parked();
24663    pane.update(cx, |pane, cx| {
24664        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24665        open_editor.update(cx, |editor, cx| {
24666            assert_eq!(
24667                editor.display_text(cx),
24668                "pub mod external {}",
24669                "External file is open now",
24670            );
24671        });
24672    });
24673    assert_language_servers_count(
24674        1,
24675        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24676        cx,
24677    );
24678
24679    cx.update(|_, cx| {
24680        workspace::reload(cx);
24681    });
24682    assert_language_servers_count(
24683        1,
24684        "After reloading the worktree with local and external files opened, only one project should be started",
24685        cx,
24686    );
24687}
24688
24689#[gpui::test]
24690async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24691    init_test(cx, |_| {});
24692
24693    let mut cx = EditorTestContext::new(cx).await;
24694    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24695    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24696
24697    // test cursor move to start of each line on tab
24698    // for `if`, `elif`, `else`, `while`, `with` and `for`
24699    cx.set_state(indoc! {"
24700        def main():
24701        ˇ    for item in items:
24702        ˇ        while item.active:
24703        ˇ            if item.value > 10:
24704        ˇ                continue
24705        ˇ            elif item.value < 0:
24706        ˇ                break
24707        ˇ            else:
24708        ˇ                with item.context() as ctx:
24709        ˇ                    yield count
24710        ˇ        else:
24711        ˇ            log('while else')
24712        ˇ    else:
24713        ˇ        log('for else')
24714    "});
24715    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24716    cx.assert_editor_state(indoc! {"
24717        def main():
24718            ˇfor item in items:
24719                ˇwhile item.active:
24720                    ˇif item.value > 10:
24721                        ˇcontinue
24722                    ˇelif item.value < 0:
24723                        ˇbreak
24724                    ˇelse:
24725                        ˇwith item.context() as ctx:
24726                            ˇyield count
24727                ˇelse:
24728                    ˇlog('while else')
24729            ˇelse:
24730                ˇlog('for else')
24731    "});
24732    // test relative indent is preserved when tab
24733    // for `if`, `elif`, `else`, `while`, `with` and `for`
24734    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24735    cx.assert_editor_state(indoc! {"
24736        def main():
24737                ˇfor item in items:
24738                    ˇwhile item.active:
24739                        ˇif item.value > 10:
24740                            ˇcontinue
24741                        ˇelif item.value < 0:
24742                            ˇbreak
24743                        ˇelse:
24744                            ˇwith item.context() as ctx:
24745                                ˇyield count
24746                    ˇelse:
24747                        ˇlog('while else')
24748                ˇelse:
24749                    ˇlog('for else')
24750    "});
24751
24752    // test cursor move to start of each line on tab
24753    // for `try`, `except`, `else`, `finally`, `match` and `def`
24754    cx.set_state(indoc! {"
24755        def main():
24756        ˇ    try:
24757        ˇ        fetch()
24758        ˇ    except ValueError:
24759        ˇ        handle_error()
24760        ˇ    else:
24761        ˇ        match value:
24762        ˇ            case _:
24763        ˇ    finally:
24764        ˇ        def status():
24765        ˇ            return 0
24766    "});
24767    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24768    cx.assert_editor_state(indoc! {"
24769        def main():
24770            ˇtry:
24771                ˇfetch()
24772            ˇexcept ValueError:
24773                ˇhandle_error()
24774            ˇelse:
24775                ˇmatch value:
24776                    ˇcase _:
24777            ˇfinally:
24778                ˇdef status():
24779                    ˇreturn 0
24780    "});
24781    // test relative indent is preserved when tab
24782    // for `try`, `except`, `else`, `finally`, `match` and `def`
24783    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24784    cx.assert_editor_state(indoc! {"
24785        def main():
24786                ˇtry:
24787                    ˇfetch()
24788                ˇexcept ValueError:
24789                    ˇhandle_error()
24790                ˇelse:
24791                    ˇmatch value:
24792                        ˇcase _:
24793                ˇfinally:
24794                    ˇdef status():
24795                        ˇreturn 0
24796    "});
24797}
24798
24799#[gpui::test]
24800async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24801    init_test(cx, |_| {});
24802
24803    let mut cx = EditorTestContext::new(cx).await;
24804    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24805    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24806
24807    // test `else` auto outdents when typed inside `if` block
24808    cx.set_state(indoc! {"
24809        def main():
24810            if i == 2:
24811                return
24812                ˇ
24813    "});
24814    cx.update_editor(|editor, window, cx| {
24815        editor.handle_input("else:", window, cx);
24816    });
24817    cx.assert_editor_state(indoc! {"
24818        def main():
24819            if i == 2:
24820                return
24821            else:ˇ
24822    "});
24823
24824    // test `except` auto outdents when typed inside `try` block
24825    cx.set_state(indoc! {"
24826        def main():
24827            try:
24828                i = 2
24829                ˇ
24830    "});
24831    cx.update_editor(|editor, window, cx| {
24832        editor.handle_input("except:", window, cx);
24833    });
24834    cx.assert_editor_state(indoc! {"
24835        def main():
24836            try:
24837                i = 2
24838            except:ˇ
24839    "});
24840
24841    // test `else` auto outdents when typed inside `except` block
24842    cx.set_state(indoc! {"
24843        def main():
24844            try:
24845                i = 2
24846            except:
24847                j = 2
24848                ˇ
24849    "});
24850    cx.update_editor(|editor, window, cx| {
24851        editor.handle_input("else:", window, cx);
24852    });
24853    cx.assert_editor_state(indoc! {"
24854        def main():
24855            try:
24856                i = 2
24857            except:
24858                j = 2
24859            else:ˇ
24860    "});
24861
24862    // test `finally` auto outdents when typed inside `else` block
24863    cx.set_state(indoc! {"
24864        def main():
24865            try:
24866                i = 2
24867            except:
24868                j = 2
24869            else:
24870                k = 2
24871                ˇ
24872    "});
24873    cx.update_editor(|editor, window, cx| {
24874        editor.handle_input("finally:", window, cx);
24875    });
24876    cx.assert_editor_state(indoc! {"
24877        def main():
24878            try:
24879                i = 2
24880            except:
24881                j = 2
24882            else:
24883                k = 2
24884            finally:ˇ
24885    "});
24886
24887    // test `else` does not outdents when typed inside `except` block right after for block
24888    cx.set_state(indoc! {"
24889        def main():
24890            try:
24891                i = 2
24892            except:
24893                for i in range(n):
24894                    pass
24895                ˇ
24896    "});
24897    cx.update_editor(|editor, window, cx| {
24898        editor.handle_input("else:", window, cx);
24899    });
24900    cx.assert_editor_state(indoc! {"
24901        def main():
24902            try:
24903                i = 2
24904            except:
24905                for i in range(n):
24906                    pass
24907                else:ˇ
24908    "});
24909
24910    // test `finally` auto outdents when typed inside `else` block right after for block
24911    cx.set_state(indoc! {"
24912        def main():
24913            try:
24914                i = 2
24915            except:
24916                j = 2
24917            else:
24918                for i in range(n):
24919                    pass
24920                ˇ
24921    "});
24922    cx.update_editor(|editor, window, cx| {
24923        editor.handle_input("finally:", window, cx);
24924    });
24925    cx.assert_editor_state(indoc! {"
24926        def main():
24927            try:
24928                i = 2
24929            except:
24930                j = 2
24931            else:
24932                for i in range(n):
24933                    pass
24934            finally:ˇ
24935    "});
24936
24937    // test `except` outdents to inner "try" block
24938    cx.set_state(indoc! {"
24939        def main():
24940            try:
24941                i = 2
24942                if i == 2:
24943                    try:
24944                        i = 3
24945                        ˇ
24946    "});
24947    cx.update_editor(|editor, window, cx| {
24948        editor.handle_input("except:", window, cx);
24949    });
24950    cx.assert_editor_state(indoc! {"
24951        def main():
24952            try:
24953                i = 2
24954                if i == 2:
24955                    try:
24956                        i = 3
24957                    except:ˇ
24958    "});
24959
24960    // test `except` outdents to outer "try" block
24961    cx.set_state(indoc! {"
24962        def main():
24963            try:
24964                i = 2
24965                if i == 2:
24966                    try:
24967                        i = 3
24968                ˇ
24969    "});
24970    cx.update_editor(|editor, window, cx| {
24971        editor.handle_input("except:", window, cx);
24972    });
24973    cx.assert_editor_state(indoc! {"
24974        def main():
24975            try:
24976                i = 2
24977                if i == 2:
24978                    try:
24979                        i = 3
24980            except:ˇ
24981    "});
24982
24983    // test `else` stays at correct indent when typed after `for` block
24984    cx.set_state(indoc! {"
24985        def main():
24986            for i in range(10):
24987                if i == 3:
24988                    break
24989            ˇ
24990    "});
24991    cx.update_editor(|editor, window, cx| {
24992        editor.handle_input("else:", window, cx);
24993    });
24994    cx.assert_editor_state(indoc! {"
24995        def main():
24996            for i in range(10):
24997                if i == 3:
24998                    break
24999            else:ˇ
25000    "});
25001
25002    // test does not outdent on typing after line with square brackets
25003    cx.set_state(indoc! {"
25004        def f() -> list[str]:
25005            ˇ
25006    "});
25007    cx.update_editor(|editor, window, cx| {
25008        editor.handle_input("a", window, cx);
25009    });
25010    cx.assert_editor_state(indoc! {"
25011        def f() -> list[str]:
2501225013    "});
25014
25015    // test does not outdent on typing : after case keyword
25016    cx.set_state(indoc! {"
25017        match 1:
25018            caseˇ
25019    "});
25020    cx.update_editor(|editor, window, cx| {
25021        editor.handle_input(":", window, cx);
25022    });
25023    cx.assert_editor_state(indoc! {"
25024        match 1:
25025            case:ˇ
25026    "});
25027}
25028
25029#[gpui::test]
25030async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25031    init_test(cx, |_| {});
25032    update_test_language_settings(cx, |settings| {
25033        settings.defaults.extend_comment_on_newline = Some(false);
25034    });
25035    let mut cx = EditorTestContext::new(cx).await;
25036    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25037    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25038
25039    // test correct indent after newline on comment
25040    cx.set_state(indoc! {"
25041        # COMMENT:ˇ
25042    "});
25043    cx.update_editor(|editor, window, cx| {
25044        editor.newline(&Newline, window, cx);
25045    });
25046    cx.assert_editor_state(indoc! {"
25047        # COMMENT:
25048        ˇ
25049    "});
25050
25051    // test correct indent after newline in brackets
25052    cx.set_state(indoc! {"
25053        {ˇ}
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        {
25061            ˇ
25062        }
25063    "});
25064
25065    cx.set_state(indoc! {"
25066        (ˇ)
25067    "});
25068    cx.update_editor(|editor, window, cx| {
25069        editor.newline(&Newline, window, cx);
25070    });
25071    cx.run_until_parked();
25072    cx.assert_editor_state(indoc! {"
25073        (
25074            ˇ
25075        )
25076    "});
25077
25078    // do not indent after empty lists or dictionaries
25079    cx.set_state(indoc! {"
25080        a = []ˇ
25081    "});
25082    cx.update_editor(|editor, window, cx| {
25083        editor.newline(&Newline, window, cx);
25084    });
25085    cx.run_until_parked();
25086    cx.assert_editor_state(indoc! {"
25087        a = []
25088        ˇ
25089    "});
25090}
25091
25092#[gpui::test]
25093async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25094    init_test(cx, |_| {});
25095
25096    let mut cx = EditorTestContext::new(cx).await;
25097    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25098    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25099
25100    // test cursor move to start of each line on tab
25101    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25102    cx.set_state(indoc! {"
25103        function main() {
25104        ˇ    for item in $items; do
25105        ˇ        while [ -n \"$item\" ]; do
25106        ˇ            if [ \"$value\" -gt 10 ]; then
25107        ˇ                continue
25108        ˇ            elif [ \"$value\" -lt 0 ]; then
25109        ˇ                break
25110        ˇ            else
25111        ˇ                echo \"$item\"
25112        ˇ            fi
25113        ˇ        done
25114        ˇ    done
25115        ˇ}
25116    "});
25117    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25118    cx.assert_editor_state(indoc! {"
25119        function main() {
25120            ˇfor item in $items; do
25121                ˇwhile [ -n \"$item\" ]; do
25122                    ˇif [ \"$value\" -gt 10 ]; then
25123                        ˇcontinue
25124                    ˇelif [ \"$value\" -lt 0 ]; then
25125                        ˇbreak
25126                    ˇelse
25127                        ˇecho \"$item\"
25128                    ˇfi
25129                ˇdone
25130            ˇdone
25131        ˇ}
25132    "});
25133    // test relative indent is preserved when tab
25134    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25135    cx.assert_editor_state(indoc! {"
25136        function main() {
25137                ˇfor item in $items; do
25138                    ˇwhile [ -n \"$item\" ]; do
25139                        ˇif [ \"$value\" -gt 10 ]; then
25140                            ˇcontinue
25141                        ˇelif [ \"$value\" -lt 0 ]; then
25142                            ˇbreak
25143                        ˇelse
25144                            ˇecho \"$item\"
25145                        ˇfi
25146                    ˇdone
25147                ˇdone
25148            ˇ}
25149    "});
25150
25151    // test cursor move to start of each line on tab
25152    // for `case` statement with patterns
25153    cx.set_state(indoc! {"
25154        function handle() {
25155        ˇ    case \"$1\" in
25156        ˇ        start)
25157        ˇ            echo \"a\"
25158        ˇ            ;;
25159        ˇ        stop)
25160        ˇ            echo \"b\"
25161        ˇ            ;;
25162        ˇ        *)
25163        ˇ            echo \"c\"
25164        ˇ            ;;
25165        ˇ    esac
25166        ˇ}
25167    "});
25168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25169    cx.assert_editor_state(indoc! {"
25170        function handle() {
25171            ˇcase \"$1\" in
25172                ˇstart)
25173                    ˇecho \"a\"
25174                    ˇ;;
25175                ˇstop)
25176                    ˇecho \"b\"
25177                    ˇ;;
25178                ˇ*)
25179                    ˇecho \"c\"
25180                    ˇ;;
25181            ˇesac
25182        ˇ}
25183    "});
25184}
25185
25186#[gpui::test]
25187async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25188    init_test(cx, |_| {});
25189
25190    let mut cx = EditorTestContext::new(cx).await;
25191    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25192    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25193
25194    // test indents on comment insert
25195    cx.set_state(indoc! {"
25196        function main() {
25197        ˇ    for item in $items; do
25198        ˇ        while [ -n \"$item\" ]; do
25199        ˇ            if [ \"$value\" -gt 10 ]; then
25200        ˇ                continue
25201        ˇ            elif [ \"$value\" -lt 0 ]; then
25202        ˇ                break
25203        ˇ            else
25204        ˇ                echo \"$item\"
25205        ˇ            fi
25206        ˇ        done
25207        ˇ    done
25208        ˇ}
25209    "});
25210    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25211    cx.assert_editor_state(indoc! {"
25212        function main() {
25213        #ˇ    for item in $items; do
25214        #ˇ        while [ -n \"$item\" ]; do
25215        #ˇ            if [ \"$value\" -gt 10 ]; then
25216        #ˇ                continue
25217        #ˇ            elif [ \"$value\" -lt 0 ]; then
25218        #ˇ                break
25219        #ˇ            else
25220        #ˇ                echo \"$item\"
25221        #ˇ            fi
25222        #ˇ        done
25223        #ˇ    done
25224        #ˇ}
25225    "});
25226}
25227
25228#[gpui::test]
25229async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25230    init_test(cx, |_| {});
25231
25232    let mut cx = EditorTestContext::new(cx).await;
25233    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25234    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25235
25236    // test `else` auto outdents when typed inside `if` block
25237    cx.set_state(indoc! {"
25238        if [ \"$1\" = \"test\" ]; then
25239            echo \"foo bar\"
25240            ˇ
25241    "});
25242    cx.update_editor(|editor, window, cx| {
25243        editor.handle_input("else", window, cx);
25244    });
25245    cx.assert_editor_state(indoc! {"
25246        if [ \"$1\" = \"test\" ]; then
25247            echo \"foo bar\"
25248        elseˇ
25249    "});
25250
25251    // test `elif` auto outdents when typed inside `if` block
25252    cx.set_state(indoc! {"
25253        if [ \"$1\" = \"test\" ]; then
25254            echo \"foo bar\"
25255            ˇ
25256    "});
25257    cx.update_editor(|editor, window, cx| {
25258        editor.handle_input("elif", window, cx);
25259    });
25260    cx.assert_editor_state(indoc! {"
25261        if [ \"$1\" = \"test\" ]; then
25262            echo \"foo bar\"
25263        elifˇ
25264    "});
25265
25266    // test `fi` auto outdents when typed inside `else` block
25267    cx.set_state(indoc! {"
25268        if [ \"$1\" = \"test\" ]; then
25269            echo \"foo bar\"
25270        else
25271            echo \"bar baz\"
25272            ˇ
25273    "});
25274    cx.update_editor(|editor, window, cx| {
25275        editor.handle_input("fi", window, cx);
25276    });
25277    cx.assert_editor_state(indoc! {"
25278        if [ \"$1\" = \"test\" ]; then
25279            echo \"foo bar\"
25280        else
25281            echo \"bar baz\"
25282        fiˇ
25283    "});
25284
25285    // test `done` auto outdents when typed inside `while` block
25286    cx.set_state(indoc! {"
25287        while read line; do
25288            echo \"$line\"
25289            ˇ
25290    "});
25291    cx.update_editor(|editor, window, cx| {
25292        editor.handle_input("done", window, cx);
25293    });
25294    cx.assert_editor_state(indoc! {"
25295        while read line; do
25296            echo \"$line\"
25297        doneˇ
25298    "});
25299
25300    // test `done` auto outdents when typed inside `for` block
25301    cx.set_state(indoc! {"
25302        for file in *.txt; do
25303            cat \"$file\"
25304            ˇ
25305    "});
25306    cx.update_editor(|editor, window, cx| {
25307        editor.handle_input("done", window, cx);
25308    });
25309    cx.assert_editor_state(indoc! {"
25310        for file in *.txt; do
25311            cat \"$file\"
25312        doneˇ
25313    "});
25314
25315    // test `esac` auto outdents when typed inside `case` block
25316    cx.set_state(indoc! {"
25317        case \"$1\" in
25318            start)
25319                echo \"foo bar\"
25320                ;;
25321            stop)
25322                echo \"bar baz\"
25323                ;;
25324            ˇ
25325    "});
25326    cx.update_editor(|editor, window, cx| {
25327        editor.handle_input("esac", window, cx);
25328    });
25329    cx.assert_editor_state(indoc! {"
25330        case \"$1\" in
25331            start)
25332                echo \"foo bar\"
25333                ;;
25334            stop)
25335                echo \"bar baz\"
25336                ;;
25337        esacˇ
25338    "});
25339
25340    // test `*)` auto outdents when typed inside `case` block
25341    cx.set_state(indoc! {"
25342        case \"$1\" in
25343            start)
25344                echo \"foo bar\"
25345                ;;
25346                ˇ
25347    "});
25348    cx.update_editor(|editor, window, cx| {
25349        editor.handle_input("*)", window, cx);
25350    });
25351    cx.assert_editor_state(indoc! {"
25352        case \"$1\" in
25353            start)
25354                echo \"foo bar\"
25355                ;;
25356            *)ˇ
25357    "});
25358
25359    // test `fi` outdents to correct level with nested if blocks
25360    cx.set_state(indoc! {"
25361        if [ \"$1\" = \"test\" ]; then
25362            echo \"outer if\"
25363            if [ \"$2\" = \"debug\" ]; then
25364                echo \"inner if\"
25365                ˇ
25366    "});
25367    cx.update_editor(|editor, window, cx| {
25368        editor.handle_input("fi", window, cx);
25369    });
25370    cx.assert_editor_state(indoc! {"
25371        if [ \"$1\" = \"test\" ]; then
25372            echo \"outer if\"
25373            if [ \"$2\" = \"debug\" ]; then
25374                echo \"inner if\"
25375            fiˇ
25376    "});
25377}
25378
25379#[gpui::test]
25380async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25381    init_test(cx, |_| {});
25382    update_test_language_settings(cx, |settings| {
25383        settings.defaults.extend_comment_on_newline = Some(false);
25384    });
25385    let mut cx = EditorTestContext::new(cx).await;
25386    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25387    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25388
25389    // test correct indent after newline on comment
25390    cx.set_state(indoc! {"
25391        # COMMENT:ˇ
25392    "});
25393    cx.update_editor(|editor, window, cx| {
25394        editor.newline(&Newline, window, cx);
25395    });
25396    cx.assert_editor_state(indoc! {"
25397        # COMMENT:
25398        ˇ
25399    "});
25400
25401    // test correct indent after newline after `then`
25402    cx.set_state(indoc! {"
25403
25404        if [ \"$1\" = \"test\" ]; thenˇ
25405    "});
25406    cx.update_editor(|editor, window, cx| {
25407        editor.newline(&Newline, window, cx);
25408    });
25409    cx.run_until_parked();
25410    cx.assert_editor_state(indoc! {"
25411
25412        if [ \"$1\" = \"test\" ]; then
25413            ˇ
25414    "});
25415
25416    // test correct indent after newline after `else`
25417    cx.set_state(indoc! {"
25418        if [ \"$1\" = \"test\" ]; then
25419        elseˇ
25420    "});
25421    cx.update_editor(|editor, window, cx| {
25422        editor.newline(&Newline, window, cx);
25423    });
25424    cx.run_until_parked();
25425    cx.assert_editor_state(indoc! {"
25426        if [ \"$1\" = \"test\" ]; then
25427        else
25428            ˇ
25429    "});
25430
25431    // test correct indent after newline after `elif`
25432    cx.set_state(indoc! {"
25433        if [ \"$1\" = \"test\" ]; then
25434        elifˇ
25435    "});
25436    cx.update_editor(|editor, window, cx| {
25437        editor.newline(&Newline, window, cx);
25438    });
25439    cx.run_until_parked();
25440    cx.assert_editor_state(indoc! {"
25441        if [ \"$1\" = \"test\" ]; then
25442        elif
25443            ˇ
25444    "});
25445
25446    // test correct indent after newline after `do`
25447    cx.set_state(indoc! {"
25448        for file in *.txt; doˇ
25449    "});
25450    cx.update_editor(|editor, window, cx| {
25451        editor.newline(&Newline, window, cx);
25452    });
25453    cx.run_until_parked();
25454    cx.assert_editor_state(indoc! {"
25455        for file in *.txt; do
25456            ˇ
25457    "});
25458
25459    // test correct indent after newline after case pattern
25460    cx.set_state(indoc! {"
25461        case \"$1\" in
25462            start)ˇ
25463    "});
25464    cx.update_editor(|editor, window, cx| {
25465        editor.newline(&Newline, window, cx);
25466    });
25467    cx.run_until_parked();
25468    cx.assert_editor_state(indoc! {"
25469        case \"$1\" in
25470            start)
25471                ˇ
25472    "});
25473
25474    // test correct indent after newline after case pattern
25475    cx.set_state(indoc! {"
25476        case \"$1\" in
25477            start)
25478                ;;
25479            *)ˇ
25480    "});
25481    cx.update_editor(|editor, window, cx| {
25482        editor.newline(&Newline, window, cx);
25483    });
25484    cx.run_until_parked();
25485    cx.assert_editor_state(indoc! {"
25486        case \"$1\" in
25487            start)
25488                ;;
25489            *)
25490                ˇ
25491    "});
25492
25493    // test correct indent after newline after function opening brace
25494    cx.set_state(indoc! {"
25495        function test() {ˇ}
25496    "});
25497    cx.update_editor(|editor, window, cx| {
25498        editor.newline(&Newline, window, cx);
25499    });
25500    cx.run_until_parked();
25501    cx.assert_editor_state(indoc! {"
25502        function test() {
25503            ˇ
25504        }
25505    "});
25506
25507    // test no extra indent after semicolon on same line
25508    cx.set_state(indoc! {"
25509        echo \"test\"25510    "});
25511    cx.update_editor(|editor, window, cx| {
25512        editor.newline(&Newline, window, cx);
25513    });
25514    cx.run_until_parked();
25515    cx.assert_editor_state(indoc! {"
25516        echo \"test\";
25517        ˇ
25518    "});
25519}
25520
25521fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25522    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25523    point..point
25524}
25525
25526#[track_caller]
25527fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25528    let (text, ranges) = marked_text_ranges(marked_text, true);
25529    assert_eq!(editor.text(cx), text);
25530    assert_eq!(
25531        editor.selections.ranges(&editor.display_snapshot(cx)),
25532        ranges,
25533        "Assert selections are {}",
25534        marked_text
25535    );
25536}
25537
25538pub fn handle_signature_help_request(
25539    cx: &mut EditorLspTestContext,
25540    mocked_response: lsp::SignatureHelp,
25541) -> impl Future<Output = ()> + use<> {
25542    let mut request =
25543        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25544            let mocked_response = mocked_response.clone();
25545            async move { Ok(Some(mocked_response)) }
25546        });
25547
25548    async move {
25549        request.next().await;
25550    }
25551}
25552
25553#[track_caller]
25554pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25555    cx.update_editor(|editor, _, _| {
25556        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25557            let entries = menu.entries.borrow();
25558            let entries = entries
25559                .iter()
25560                .map(|entry| entry.string.as_str())
25561                .collect::<Vec<_>>();
25562            assert_eq!(entries, expected);
25563        } else {
25564            panic!("Expected completions menu");
25565        }
25566    });
25567}
25568
25569/// Handle completion request passing a marked string specifying where the completion
25570/// should be triggered from using '|' character, what range should be replaced, and what completions
25571/// should be returned using '<' and '>' to delimit the range.
25572///
25573/// Also see `handle_completion_request_with_insert_and_replace`.
25574#[track_caller]
25575pub fn handle_completion_request(
25576    marked_string: &str,
25577    completions: Vec<&'static str>,
25578    is_incomplete: bool,
25579    counter: Arc<AtomicUsize>,
25580    cx: &mut EditorLspTestContext,
25581) -> impl Future<Output = ()> {
25582    let complete_from_marker: TextRangeMarker = '|'.into();
25583    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25584    let (_, mut marked_ranges) = marked_text_ranges_by(
25585        marked_string,
25586        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25587    );
25588
25589    let complete_from_position =
25590        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25591    let replace_range =
25592        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25593
25594    let mut request =
25595        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25596            let completions = completions.clone();
25597            counter.fetch_add(1, atomic::Ordering::Release);
25598            async move {
25599                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25600                assert_eq!(
25601                    params.text_document_position.position,
25602                    complete_from_position
25603                );
25604                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25605                    is_incomplete,
25606                    item_defaults: None,
25607                    items: completions
25608                        .iter()
25609                        .map(|completion_text| lsp::CompletionItem {
25610                            label: completion_text.to_string(),
25611                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25612                                range: replace_range,
25613                                new_text: completion_text.to_string(),
25614                            })),
25615                            ..Default::default()
25616                        })
25617                        .collect(),
25618                })))
25619            }
25620        });
25621
25622    async move {
25623        request.next().await;
25624    }
25625}
25626
25627/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25628/// given instead, which also contains an `insert` range.
25629///
25630/// This function uses markers to define ranges:
25631/// - `|` marks the cursor position
25632/// - `<>` marks the replace range
25633/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25634pub fn handle_completion_request_with_insert_and_replace(
25635    cx: &mut EditorLspTestContext,
25636    marked_string: &str,
25637    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25638    counter: Arc<AtomicUsize>,
25639) -> impl Future<Output = ()> {
25640    let complete_from_marker: TextRangeMarker = '|'.into();
25641    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25642    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25643
25644    let (_, mut marked_ranges) = marked_text_ranges_by(
25645        marked_string,
25646        vec![
25647            complete_from_marker.clone(),
25648            replace_range_marker.clone(),
25649            insert_range_marker.clone(),
25650        ],
25651    );
25652
25653    let complete_from_position =
25654        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25655    let replace_range =
25656        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25657
25658    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25659        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25660        _ => lsp::Range {
25661            start: replace_range.start,
25662            end: complete_from_position,
25663        },
25664    };
25665
25666    let mut request =
25667        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25668            let completions = completions.clone();
25669            counter.fetch_add(1, atomic::Ordering::Release);
25670            async move {
25671                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25672                assert_eq!(
25673                    params.text_document_position.position, complete_from_position,
25674                    "marker `|` position doesn't match",
25675                );
25676                Ok(Some(lsp::CompletionResponse::Array(
25677                    completions
25678                        .iter()
25679                        .map(|(label, new_text)| lsp::CompletionItem {
25680                            label: label.to_string(),
25681                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25682                                lsp::InsertReplaceEdit {
25683                                    insert: insert_range,
25684                                    replace: replace_range,
25685                                    new_text: new_text.to_string(),
25686                                },
25687                            )),
25688                            ..Default::default()
25689                        })
25690                        .collect(),
25691                )))
25692            }
25693        });
25694
25695    async move {
25696        request.next().await;
25697    }
25698}
25699
25700fn handle_resolve_completion_request(
25701    cx: &mut EditorLspTestContext,
25702    edits: Option<Vec<(&'static str, &'static str)>>,
25703) -> impl Future<Output = ()> {
25704    let edits = edits.map(|edits| {
25705        edits
25706            .iter()
25707            .map(|(marked_string, new_text)| {
25708                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25709                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25710                lsp::TextEdit::new(replace_range, new_text.to_string())
25711            })
25712            .collect::<Vec<_>>()
25713    });
25714
25715    let mut request =
25716        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25717            let edits = edits.clone();
25718            async move {
25719                Ok(lsp::CompletionItem {
25720                    additional_text_edits: edits,
25721                    ..Default::default()
25722                })
25723            }
25724        });
25725
25726    async move {
25727        request.next().await;
25728    }
25729}
25730
25731pub(crate) fn update_test_language_settings(
25732    cx: &mut TestAppContext,
25733    f: impl Fn(&mut AllLanguageSettingsContent),
25734) {
25735    cx.update(|cx| {
25736        SettingsStore::update_global(cx, |store, cx| {
25737            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25738        });
25739    });
25740}
25741
25742pub(crate) fn update_test_project_settings(
25743    cx: &mut TestAppContext,
25744    f: impl Fn(&mut ProjectSettingsContent),
25745) {
25746    cx.update(|cx| {
25747        SettingsStore::update_global(cx, |store, cx| {
25748            store.update_user_settings(cx, |settings| f(&mut settings.project));
25749        });
25750    });
25751}
25752
25753pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25754    cx.update(|cx| {
25755        assets::Assets.load_test_fonts(cx);
25756        let store = SettingsStore::test(cx);
25757        cx.set_global(store);
25758        theme::init(theme::LoadThemes::JustBase, cx);
25759        release_channel::init(SemanticVersion::default(), cx);
25760        client::init_settings(cx);
25761        language::init(cx);
25762        Project::init_settings(cx);
25763        workspace::init_settings(cx);
25764        crate::init(cx);
25765    });
25766    zlog::init_test();
25767    update_test_language_settings(cx, f);
25768}
25769
25770#[track_caller]
25771fn assert_hunk_revert(
25772    not_reverted_text_with_selections: &str,
25773    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25774    expected_reverted_text_with_selections: &str,
25775    base_text: &str,
25776    cx: &mut EditorLspTestContext,
25777) {
25778    cx.set_state(not_reverted_text_with_selections);
25779    cx.set_head_text(base_text);
25780    cx.executor().run_until_parked();
25781
25782    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25783        let snapshot = editor.snapshot(window, cx);
25784        let reverted_hunk_statuses = snapshot
25785            .buffer_snapshot()
25786            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25787            .map(|hunk| hunk.status().kind)
25788            .collect::<Vec<_>>();
25789
25790        editor.git_restore(&Default::default(), window, cx);
25791        reverted_hunk_statuses
25792    });
25793    cx.executor().run_until_parked();
25794    cx.assert_editor_state(expected_reverted_text_with_selections);
25795    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25796}
25797
25798#[gpui::test(iterations = 10)]
25799async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25800    init_test(cx, |_| {});
25801
25802    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25803    let counter = diagnostic_requests.clone();
25804
25805    let fs = FakeFs::new(cx.executor());
25806    fs.insert_tree(
25807        path!("/a"),
25808        json!({
25809            "first.rs": "fn main() { let a = 5; }",
25810            "second.rs": "// Test file",
25811        }),
25812    )
25813    .await;
25814
25815    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25817    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25818
25819    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25820    language_registry.add(rust_lang());
25821    let mut fake_servers = language_registry.register_fake_lsp(
25822        "Rust",
25823        FakeLspAdapter {
25824            capabilities: lsp::ServerCapabilities {
25825                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25826                    lsp::DiagnosticOptions {
25827                        identifier: None,
25828                        inter_file_dependencies: true,
25829                        workspace_diagnostics: true,
25830                        work_done_progress_options: Default::default(),
25831                    },
25832                )),
25833                ..Default::default()
25834            },
25835            ..Default::default()
25836        },
25837    );
25838
25839    let editor = workspace
25840        .update(cx, |workspace, window, cx| {
25841            workspace.open_abs_path(
25842                PathBuf::from(path!("/a/first.rs")),
25843                OpenOptions::default(),
25844                window,
25845                cx,
25846            )
25847        })
25848        .unwrap()
25849        .await
25850        .unwrap()
25851        .downcast::<Editor>()
25852        .unwrap();
25853    let fake_server = fake_servers.next().await.unwrap();
25854    let server_id = fake_server.server.server_id();
25855    let mut first_request = fake_server
25856        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25857            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25858            let result_id = Some(new_result_id.to_string());
25859            assert_eq!(
25860                params.text_document.uri,
25861                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25862            );
25863            async move {
25864                Ok(lsp::DocumentDiagnosticReportResult::Report(
25865                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25866                        related_documents: None,
25867                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25868                            items: Vec::new(),
25869                            result_id,
25870                        },
25871                    }),
25872                ))
25873            }
25874        });
25875
25876    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25877        project.update(cx, |project, cx| {
25878            let buffer_id = editor
25879                .read(cx)
25880                .buffer()
25881                .read(cx)
25882                .as_singleton()
25883                .expect("created a singleton buffer")
25884                .read(cx)
25885                .remote_id();
25886            let buffer_result_id = project
25887                .lsp_store()
25888                .read(cx)
25889                .result_id(server_id, buffer_id, cx);
25890            assert_eq!(expected, buffer_result_id);
25891        });
25892    };
25893
25894    ensure_result_id(None, cx);
25895    cx.executor().advance_clock(Duration::from_millis(60));
25896    cx.executor().run_until_parked();
25897    assert_eq!(
25898        diagnostic_requests.load(atomic::Ordering::Acquire),
25899        1,
25900        "Opening file should trigger diagnostic request"
25901    );
25902    first_request
25903        .next()
25904        .await
25905        .expect("should have sent the first diagnostics pull request");
25906    ensure_result_id(Some("1".to_string()), cx);
25907
25908    // Editing should trigger diagnostics
25909    editor.update_in(cx, |editor, window, cx| {
25910        editor.handle_input("2", window, cx)
25911    });
25912    cx.executor().advance_clock(Duration::from_millis(60));
25913    cx.executor().run_until_parked();
25914    assert_eq!(
25915        diagnostic_requests.load(atomic::Ordering::Acquire),
25916        2,
25917        "Editing should trigger diagnostic request"
25918    );
25919    ensure_result_id(Some("2".to_string()), cx);
25920
25921    // Moving cursor should not trigger diagnostic request
25922    editor.update_in(cx, |editor, window, cx| {
25923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25924            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25925        });
25926    });
25927    cx.executor().advance_clock(Duration::from_millis(60));
25928    cx.executor().run_until_parked();
25929    assert_eq!(
25930        diagnostic_requests.load(atomic::Ordering::Acquire),
25931        2,
25932        "Cursor movement should not trigger diagnostic request"
25933    );
25934    ensure_result_id(Some("2".to_string()), cx);
25935    // Multiple rapid edits should be debounced
25936    for _ in 0..5 {
25937        editor.update_in(cx, |editor, window, cx| {
25938            editor.handle_input("x", window, cx)
25939        });
25940    }
25941    cx.executor().advance_clock(Duration::from_millis(60));
25942    cx.executor().run_until_parked();
25943
25944    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25945    assert!(
25946        final_requests <= 4,
25947        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25948    );
25949    ensure_result_id(Some(final_requests.to_string()), cx);
25950}
25951
25952#[gpui::test]
25953async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25954    // Regression test for issue #11671
25955    // Previously, adding a cursor after moving multiple cursors would reset
25956    // the cursor count instead of adding to the existing cursors.
25957    init_test(cx, |_| {});
25958    let mut cx = EditorTestContext::new(cx).await;
25959
25960    // Create a simple buffer with cursor at start
25961    cx.set_state(indoc! {"
25962        ˇaaaa
25963        bbbb
25964        cccc
25965        dddd
25966        eeee
25967        ffff
25968        gggg
25969        hhhh"});
25970
25971    // Add 2 cursors below (so we have 3 total)
25972    cx.update_editor(|editor, window, cx| {
25973        editor.add_selection_below(&Default::default(), window, cx);
25974        editor.add_selection_below(&Default::default(), window, cx);
25975    });
25976
25977    // Verify we have 3 cursors
25978    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25979    assert_eq!(
25980        initial_count, 3,
25981        "Should have 3 cursors after adding 2 below"
25982    );
25983
25984    // Move down one line
25985    cx.update_editor(|editor, window, cx| {
25986        editor.move_down(&MoveDown, window, cx);
25987    });
25988
25989    // Add another cursor below
25990    cx.update_editor(|editor, window, cx| {
25991        editor.add_selection_below(&Default::default(), window, cx);
25992    });
25993
25994    // Should now have 4 cursors (3 original + 1 new)
25995    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25996    assert_eq!(
25997        final_count, 4,
25998        "Should have 4 cursors after moving and adding another"
25999    );
26000}
26001
26002#[gpui::test]
26003async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26004    init_test(cx, |_| {});
26005
26006    let mut cx = EditorTestContext::new(cx).await;
26007
26008    cx.set_state(indoc!(
26009        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26010           Second line here"#
26011    ));
26012
26013    cx.update_editor(|editor, window, cx| {
26014        // Enable soft wrapping with a narrow width to force soft wrapping and
26015        // confirm that more than 2 rows are being displayed.
26016        editor.set_wrap_width(Some(100.0.into()), cx);
26017        assert!(editor.display_text(cx).lines().count() > 2);
26018
26019        editor.add_selection_below(
26020            &AddSelectionBelow {
26021                skip_soft_wrap: true,
26022            },
26023            window,
26024            cx,
26025        );
26026
26027        assert_eq!(
26028            editor.selections.display_ranges(cx),
26029            &[
26030                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26031                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26032            ]
26033        );
26034
26035        editor.add_selection_above(
26036            &AddSelectionAbove {
26037                skip_soft_wrap: true,
26038            },
26039            window,
26040            cx,
26041        );
26042
26043        assert_eq!(
26044            editor.selections.display_ranges(cx),
26045            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26046        );
26047
26048        editor.add_selection_below(
26049            &AddSelectionBelow {
26050                skip_soft_wrap: false,
26051            },
26052            window,
26053            cx,
26054        );
26055
26056        assert_eq!(
26057            editor.selections.display_ranges(cx),
26058            &[
26059                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26060                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26061            ]
26062        );
26063
26064        editor.add_selection_above(
26065            &AddSelectionAbove {
26066                skip_soft_wrap: false,
26067            },
26068            window,
26069            cx,
26070        );
26071
26072        assert_eq!(
26073            editor.selections.display_ranges(cx),
26074            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26075        );
26076    });
26077}
26078
26079#[gpui::test(iterations = 10)]
26080async fn test_document_colors(cx: &mut TestAppContext) {
26081    let expected_color = Rgba {
26082        r: 0.33,
26083        g: 0.33,
26084        b: 0.33,
26085        a: 0.33,
26086    };
26087
26088    init_test(cx, |_| {});
26089
26090    let fs = FakeFs::new(cx.executor());
26091    fs.insert_tree(
26092        path!("/a"),
26093        json!({
26094            "first.rs": "fn main() { let a = 5; }",
26095        }),
26096    )
26097    .await;
26098
26099    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26100    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26101    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26102
26103    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26104    language_registry.add(rust_lang());
26105    let mut fake_servers = language_registry.register_fake_lsp(
26106        "Rust",
26107        FakeLspAdapter {
26108            capabilities: lsp::ServerCapabilities {
26109                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26110                ..lsp::ServerCapabilities::default()
26111            },
26112            name: "rust-analyzer",
26113            ..FakeLspAdapter::default()
26114        },
26115    );
26116    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26117        "Rust",
26118        FakeLspAdapter {
26119            capabilities: lsp::ServerCapabilities {
26120                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26121                ..lsp::ServerCapabilities::default()
26122            },
26123            name: "not-rust-analyzer",
26124            ..FakeLspAdapter::default()
26125        },
26126    );
26127
26128    let editor = workspace
26129        .update(cx, |workspace, window, cx| {
26130            workspace.open_abs_path(
26131                PathBuf::from(path!("/a/first.rs")),
26132                OpenOptions::default(),
26133                window,
26134                cx,
26135            )
26136        })
26137        .unwrap()
26138        .await
26139        .unwrap()
26140        .downcast::<Editor>()
26141        .unwrap();
26142    let fake_language_server = fake_servers.next().await.unwrap();
26143    let fake_language_server_without_capabilities =
26144        fake_servers_without_capabilities.next().await.unwrap();
26145    let requests_made = Arc::new(AtomicUsize::new(0));
26146    let closure_requests_made = Arc::clone(&requests_made);
26147    let mut color_request_handle = fake_language_server
26148        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26149            let requests_made = Arc::clone(&closure_requests_made);
26150            async move {
26151                assert_eq!(
26152                    params.text_document.uri,
26153                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26154                );
26155                requests_made.fetch_add(1, atomic::Ordering::Release);
26156                Ok(vec![
26157                    lsp::ColorInformation {
26158                        range: lsp::Range {
26159                            start: lsp::Position {
26160                                line: 0,
26161                                character: 0,
26162                            },
26163                            end: lsp::Position {
26164                                line: 0,
26165                                character: 1,
26166                            },
26167                        },
26168                        color: lsp::Color {
26169                            red: 0.33,
26170                            green: 0.33,
26171                            blue: 0.33,
26172                            alpha: 0.33,
26173                        },
26174                    },
26175                    lsp::ColorInformation {
26176                        range: lsp::Range {
26177                            start: lsp::Position {
26178                                line: 0,
26179                                character: 0,
26180                            },
26181                            end: lsp::Position {
26182                                line: 0,
26183                                character: 1,
26184                            },
26185                        },
26186                        color: lsp::Color {
26187                            red: 0.33,
26188                            green: 0.33,
26189                            blue: 0.33,
26190                            alpha: 0.33,
26191                        },
26192                    },
26193                ])
26194            }
26195        });
26196
26197    let _handle = fake_language_server_without_capabilities
26198        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26199            panic!("Should not be called");
26200        });
26201    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26202    color_request_handle.next().await.unwrap();
26203    cx.run_until_parked();
26204    assert_eq!(
26205        1,
26206        requests_made.load(atomic::Ordering::Acquire),
26207        "Should query for colors once per editor open"
26208    );
26209    editor.update_in(cx, |editor, _, cx| {
26210        assert_eq!(
26211            vec![expected_color],
26212            extract_color_inlays(editor, cx),
26213            "Should have an initial inlay"
26214        );
26215    });
26216
26217    // opening another file in a split should not influence the LSP query counter
26218    workspace
26219        .update(cx, |workspace, window, cx| {
26220            assert_eq!(
26221                workspace.panes().len(),
26222                1,
26223                "Should have one pane with one editor"
26224            );
26225            workspace.move_item_to_pane_in_direction(
26226                &MoveItemToPaneInDirection {
26227                    direction: SplitDirection::Right,
26228                    focus: false,
26229                    clone: true,
26230                },
26231                window,
26232                cx,
26233            );
26234        })
26235        .unwrap();
26236    cx.run_until_parked();
26237    workspace
26238        .update(cx, |workspace, _, cx| {
26239            let panes = workspace.panes();
26240            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26241            for pane in panes {
26242                let editor = pane
26243                    .read(cx)
26244                    .active_item()
26245                    .and_then(|item| item.downcast::<Editor>())
26246                    .expect("Should have opened an editor in each split");
26247                let editor_file = editor
26248                    .read(cx)
26249                    .buffer()
26250                    .read(cx)
26251                    .as_singleton()
26252                    .expect("test deals with singleton buffers")
26253                    .read(cx)
26254                    .file()
26255                    .expect("test buffese should have a file")
26256                    .path();
26257                assert_eq!(
26258                    editor_file.as_ref(),
26259                    rel_path("first.rs"),
26260                    "Both editors should be opened for the same file"
26261                )
26262            }
26263        })
26264        .unwrap();
26265
26266    cx.executor().advance_clock(Duration::from_millis(500));
26267    let save = editor.update_in(cx, |editor, window, cx| {
26268        editor.move_to_end(&MoveToEnd, window, cx);
26269        editor.handle_input("dirty", window, cx);
26270        editor.save(
26271            SaveOptions {
26272                format: true,
26273                autosave: true,
26274            },
26275            project.clone(),
26276            window,
26277            cx,
26278        )
26279    });
26280    save.await.unwrap();
26281
26282    color_request_handle.next().await.unwrap();
26283    cx.run_until_parked();
26284    assert_eq!(
26285        2,
26286        requests_made.load(atomic::Ordering::Acquire),
26287        "Should query for colors once per save (deduplicated) and once per formatting after save"
26288    );
26289
26290    drop(editor);
26291    let close = workspace
26292        .update(cx, |workspace, window, cx| {
26293            workspace.active_pane().update(cx, |pane, cx| {
26294                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26295            })
26296        })
26297        .unwrap();
26298    close.await.unwrap();
26299    let close = workspace
26300        .update(cx, |workspace, window, cx| {
26301            workspace.active_pane().update(cx, |pane, cx| {
26302                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26303            })
26304        })
26305        .unwrap();
26306    close.await.unwrap();
26307    assert_eq!(
26308        2,
26309        requests_made.load(atomic::Ordering::Acquire),
26310        "After saving and closing all editors, no extra requests should be made"
26311    );
26312    workspace
26313        .update(cx, |workspace, _, cx| {
26314            assert!(
26315                workspace.active_item(cx).is_none(),
26316                "Should close all editors"
26317            )
26318        })
26319        .unwrap();
26320
26321    workspace
26322        .update(cx, |workspace, window, cx| {
26323            workspace.active_pane().update(cx, |pane, cx| {
26324                pane.navigate_backward(&workspace::GoBack, window, cx);
26325            })
26326        })
26327        .unwrap();
26328    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26329    cx.run_until_parked();
26330    let editor = workspace
26331        .update(cx, |workspace, _, cx| {
26332            workspace
26333                .active_item(cx)
26334                .expect("Should have reopened the editor again after navigating back")
26335                .downcast::<Editor>()
26336                .expect("Should be an editor")
26337        })
26338        .unwrap();
26339
26340    assert_eq!(
26341        2,
26342        requests_made.load(atomic::Ordering::Acquire),
26343        "Cache should be reused on buffer close and reopen"
26344    );
26345    editor.update(cx, |editor, cx| {
26346        assert_eq!(
26347            vec![expected_color],
26348            extract_color_inlays(editor, cx),
26349            "Should have an initial inlay"
26350        );
26351    });
26352
26353    drop(color_request_handle);
26354    let closure_requests_made = Arc::clone(&requests_made);
26355    let mut empty_color_request_handle = fake_language_server
26356        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26357            let requests_made = Arc::clone(&closure_requests_made);
26358            async move {
26359                assert_eq!(
26360                    params.text_document.uri,
26361                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26362                );
26363                requests_made.fetch_add(1, atomic::Ordering::Release);
26364                Ok(Vec::new())
26365            }
26366        });
26367    let save = editor.update_in(cx, |editor, window, cx| {
26368        editor.move_to_end(&MoveToEnd, window, cx);
26369        editor.handle_input("dirty_again", window, cx);
26370        editor.save(
26371            SaveOptions {
26372                format: false,
26373                autosave: true,
26374            },
26375            project.clone(),
26376            window,
26377            cx,
26378        )
26379    });
26380    save.await.unwrap();
26381
26382    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26383    empty_color_request_handle.next().await.unwrap();
26384    cx.run_until_parked();
26385    assert_eq!(
26386        3,
26387        requests_made.load(atomic::Ordering::Acquire),
26388        "Should query for colors once per save only, as formatting was not requested"
26389    );
26390    editor.update(cx, |editor, cx| {
26391        assert_eq!(
26392            Vec::<Rgba>::new(),
26393            extract_color_inlays(editor, cx),
26394            "Should clear all colors when the server returns an empty response"
26395        );
26396    });
26397}
26398
26399#[gpui::test]
26400async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26401    init_test(cx, |_| {});
26402    let (editor, cx) = cx.add_window_view(Editor::single_line);
26403    editor.update_in(cx, |editor, window, cx| {
26404        editor.set_text("oops\n\nwow\n", window, cx)
26405    });
26406    cx.run_until_parked();
26407    editor.update(cx, |editor, cx| {
26408        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26409    });
26410    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26411    cx.run_until_parked();
26412    editor.update(cx, |editor, cx| {
26413        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26414    });
26415}
26416
26417#[gpui::test]
26418async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26419    init_test(cx, |_| {});
26420
26421    cx.update(|cx| {
26422        register_project_item::<Editor>(cx);
26423    });
26424
26425    let fs = FakeFs::new(cx.executor());
26426    fs.insert_tree("/root1", json!({})).await;
26427    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26428        .await;
26429
26430    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26431    let (workspace, cx) =
26432        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26433
26434    let worktree_id = project.update(cx, |project, cx| {
26435        project.worktrees(cx).next().unwrap().read(cx).id()
26436    });
26437
26438    let handle = workspace
26439        .update_in(cx, |workspace, window, cx| {
26440            let project_path = (worktree_id, rel_path("one.pdf"));
26441            workspace.open_path(project_path, None, true, window, cx)
26442        })
26443        .await
26444        .unwrap();
26445
26446    assert_eq!(
26447        handle.to_any().entity_type(),
26448        TypeId::of::<InvalidItemView>()
26449    );
26450}
26451
26452#[gpui::test]
26453async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26454    init_test(cx, |_| {});
26455
26456    let language = Arc::new(Language::new(
26457        LanguageConfig::default(),
26458        Some(tree_sitter_rust::LANGUAGE.into()),
26459    ));
26460
26461    // Test hierarchical sibling navigation
26462    let text = r#"
26463        fn outer() {
26464            if condition {
26465                let a = 1;
26466            }
26467            let b = 2;
26468        }
26469
26470        fn another() {
26471            let c = 3;
26472        }
26473    "#;
26474
26475    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26476    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26477    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26478
26479    // Wait for parsing to complete
26480    editor
26481        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26482        .await;
26483
26484    editor.update_in(cx, |editor, window, cx| {
26485        // Start by selecting "let a = 1;" inside the if block
26486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26487            s.select_display_ranges([
26488                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26489            ]);
26490        });
26491
26492        let initial_selection = editor.selections.display_ranges(cx);
26493        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26494
26495        // Test select next sibling - should move up levels to find the next sibling
26496        // Since "let a = 1;" has no siblings in the if block, it should move up
26497        // to find "let b = 2;" which is a sibling of the if block
26498        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26499        let next_selection = editor.selections.display_ranges(cx);
26500
26501        // Should have a selection and it should be different from the initial
26502        assert_eq!(
26503            next_selection.len(),
26504            1,
26505            "Should have one selection after next"
26506        );
26507        assert_ne!(
26508            next_selection[0], initial_selection[0],
26509            "Next sibling selection should be different"
26510        );
26511
26512        // Test hierarchical navigation by going to the end of the current function
26513        // and trying to navigate to the next function
26514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26515            s.select_display_ranges([
26516                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26517            ]);
26518        });
26519
26520        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26521        let function_next_selection = editor.selections.display_ranges(cx);
26522
26523        // Should move to the next function
26524        assert_eq!(
26525            function_next_selection.len(),
26526            1,
26527            "Should have one selection after function next"
26528        );
26529
26530        // Test select previous sibling navigation
26531        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26532        let prev_selection = editor.selections.display_ranges(cx);
26533
26534        // Should have a selection and it should be different
26535        assert_eq!(
26536            prev_selection.len(),
26537            1,
26538            "Should have one selection after prev"
26539        );
26540        assert_ne!(
26541            prev_selection[0], function_next_selection[0],
26542            "Previous sibling selection should be different from next"
26543        );
26544    });
26545}
26546
26547#[gpui::test]
26548async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26549    init_test(cx, |_| {});
26550
26551    let mut cx = EditorTestContext::new(cx).await;
26552    cx.set_state(
26553        "let ˇvariable = 42;
26554let another = variable + 1;
26555let result = variable * 2;",
26556    );
26557
26558    // Set up document highlights manually (simulating LSP response)
26559    cx.update_editor(|editor, _window, cx| {
26560        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26561
26562        // Create highlights for "variable" occurrences
26563        let highlight_ranges = [
26564            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26565            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26566            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26567        ];
26568
26569        let anchor_ranges: Vec<_> = highlight_ranges
26570            .iter()
26571            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26572            .collect();
26573
26574        editor.highlight_background::<DocumentHighlightRead>(
26575            &anchor_ranges,
26576            |theme| theme.colors().editor_document_highlight_read_background,
26577            cx,
26578        );
26579    });
26580
26581    // Go to next highlight - should move to second "variable"
26582    cx.update_editor(|editor, window, cx| {
26583        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26584    });
26585    cx.assert_editor_state(
26586        "let variable = 42;
26587let another = ˇvariable + 1;
26588let result = variable * 2;",
26589    );
26590
26591    // Go to next highlight - should move to third "variable"
26592    cx.update_editor(|editor, window, cx| {
26593        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26594    });
26595    cx.assert_editor_state(
26596        "let variable = 42;
26597let another = variable + 1;
26598let result = ˇvariable * 2;",
26599    );
26600
26601    // Go to next highlight - should stay at third "variable" (no wrap-around)
26602    cx.update_editor(|editor, window, cx| {
26603        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26604    });
26605    cx.assert_editor_state(
26606        "let variable = 42;
26607let another = variable + 1;
26608let result = ˇvariable * 2;",
26609    );
26610
26611    // Now test going backwards from third position
26612    cx.update_editor(|editor, window, cx| {
26613        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26614    });
26615    cx.assert_editor_state(
26616        "let variable = 42;
26617let another = ˇvariable + 1;
26618let result = variable * 2;",
26619    );
26620
26621    // Go to previous highlight - should move to first "variable"
26622    cx.update_editor(|editor, window, cx| {
26623        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26624    });
26625    cx.assert_editor_state(
26626        "let ˇvariable = 42;
26627let another = variable + 1;
26628let result = variable * 2;",
26629    );
26630
26631    // Go to previous highlight - should stay on first "variable"
26632    cx.update_editor(|editor, window, cx| {
26633        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26634    });
26635    cx.assert_editor_state(
26636        "let ˇvariable = 42;
26637let another = variable + 1;
26638let result = variable * 2;",
26639    );
26640}
26641
26642#[gpui::test]
26643async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26644    cx: &mut gpui::TestAppContext,
26645) {
26646    init_test(cx, |_| {});
26647
26648    let url = "https://zed.dev";
26649
26650    let markdown_language = Arc::new(Language::new(
26651        LanguageConfig {
26652            name: "Markdown".into(),
26653            ..LanguageConfig::default()
26654        },
26655        None,
26656    ));
26657
26658    let mut cx = EditorTestContext::new(cx).await;
26659    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26660    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26661
26662    cx.update_editor(|editor, window, cx| {
26663        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26664        editor.paste(&Paste, window, cx);
26665    });
26666
26667    cx.assert_editor_state(&format!(
26668        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26669    ));
26670}
26671
26672#[gpui::test]
26673async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26674    cx: &mut gpui::TestAppContext,
26675) {
26676    init_test(cx, |_| {});
26677
26678    let url = "https://zed.dev";
26679
26680    let markdown_language = Arc::new(Language::new(
26681        LanguageConfig {
26682            name: "Markdown".into(),
26683            ..LanguageConfig::default()
26684        },
26685        None,
26686    ));
26687
26688    let mut cx = EditorTestContext::new(cx).await;
26689    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26690    cx.set_state(&format!(
26691        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26692    ));
26693
26694    cx.update_editor(|editor, window, cx| {
26695        editor.copy(&Copy, window, cx);
26696    });
26697
26698    cx.set_state(&format!(
26699        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26700    ));
26701
26702    cx.update_editor(|editor, window, cx| {
26703        editor.paste(&Paste, window, cx);
26704    });
26705
26706    cx.assert_editor_state(&format!(
26707        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26708    ));
26709}
26710
26711#[gpui::test]
26712async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26713    cx: &mut gpui::TestAppContext,
26714) {
26715    init_test(cx, |_| {});
26716
26717    let url = "https://zed.dev";
26718
26719    let markdown_language = Arc::new(Language::new(
26720        LanguageConfig {
26721            name: "Markdown".into(),
26722            ..LanguageConfig::default()
26723        },
26724        None,
26725    ));
26726
26727    let mut cx = EditorTestContext::new(cx).await;
26728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26729    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26730
26731    cx.update_editor(|editor, window, cx| {
26732        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26733        editor.paste(&Paste, window, cx);
26734    });
26735
26736    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26737}
26738
26739#[gpui::test]
26740async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26741    cx: &mut gpui::TestAppContext,
26742) {
26743    init_test(cx, |_| {});
26744
26745    let text = "Awesome";
26746
26747    let markdown_language = Arc::new(Language::new(
26748        LanguageConfig {
26749            name: "Markdown".into(),
26750            ..LanguageConfig::default()
26751        },
26752        None,
26753    ));
26754
26755    let mut cx = EditorTestContext::new(cx).await;
26756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26757    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26758
26759    cx.update_editor(|editor, window, cx| {
26760        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26761        editor.paste(&Paste, window, cx);
26762    });
26763
26764    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26765}
26766
26767#[gpui::test]
26768async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26769    cx: &mut gpui::TestAppContext,
26770) {
26771    init_test(cx, |_| {});
26772
26773    let url = "https://zed.dev";
26774
26775    let markdown_language = Arc::new(Language::new(
26776        LanguageConfig {
26777            name: "Rust".into(),
26778            ..LanguageConfig::default()
26779        },
26780        None,
26781    ));
26782
26783    let mut cx = EditorTestContext::new(cx).await;
26784    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26785    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26786
26787    cx.update_editor(|editor, window, cx| {
26788        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26789        editor.paste(&Paste, window, cx);
26790    });
26791
26792    cx.assert_editor_state(&format!(
26793        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26794    ));
26795}
26796
26797#[gpui::test]
26798async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26799    cx: &mut TestAppContext,
26800) {
26801    init_test(cx, |_| {});
26802
26803    let url = "https://zed.dev";
26804
26805    let markdown_language = Arc::new(Language::new(
26806        LanguageConfig {
26807            name: "Markdown".into(),
26808            ..LanguageConfig::default()
26809        },
26810        None,
26811    ));
26812
26813    let (editor, cx) = cx.add_window_view(|window, cx| {
26814        let multi_buffer = MultiBuffer::build_multi(
26815            [
26816                ("this will embed -> link", vec![Point::row_range(0..1)]),
26817                ("this will replace -> link", vec![Point::row_range(0..1)]),
26818            ],
26819            cx,
26820        );
26821        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26823            s.select_ranges(vec![
26824                Point::new(0, 19)..Point::new(0, 23),
26825                Point::new(1, 21)..Point::new(1, 25),
26826            ])
26827        });
26828        let first_buffer_id = multi_buffer
26829            .read(cx)
26830            .excerpt_buffer_ids()
26831            .into_iter()
26832            .next()
26833            .unwrap();
26834        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26835        first_buffer.update(cx, |buffer, cx| {
26836            buffer.set_language(Some(markdown_language.clone()), cx);
26837        });
26838
26839        editor
26840    });
26841    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26842
26843    cx.update_editor(|editor, window, cx| {
26844        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26845        editor.paste(&Paste, window, cx);
26846    });
26847
26848    cx.assert_editor_state(&format!(
26849        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26850    ));
26851}
26852
26853#[gpui::test]
26854async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26855    init_test(cx, |_| {});
26856
26857    let fs = FakeFs::new(cx.executor());
26858    fs.insert_tree(
26859        path!("/project"),
26860        json!({
26861            "first.rs": "# First Document\nSome content here.",
26862            "second.rs": "Plain text content for second file.",
26863        }),
26864    )
26865    .await;
26866
26867    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26868    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26869    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26870
26871    let language = rust_lang();
26872    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26873    language_registry.add(language.clone());
26874    let mut fake_servers = language_registry.register_fake_lsp(
26875        "Rust",
26876        FakeLspAdapter {
26877            ..FakeLspAdapter::default()
26878        },
26879    );
26880
26881    let buffer1 = project
26882        .update(cx, |project, cx| {
26883            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26884        })
26885        .await
26886        .unwrap();
26887    let buffer2 = project
26888        .update(cx, |project, cx| {
26889            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26890        })
26891        .await
26892        .unwrap();
26893
26894    let multi_buffer = cx.new(|cx| {
26895        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26896        multi_buffer.set_excerpts_for_path(
26897            PathKey::for_buffer(&buffer1, cx),
26898            buffer1.clone(),
26899            [Point::zero()..buffer1.read(cx).max_point()],
26900            3,
26901            cx,
26902        );
26903        multi_buffer.set_excerpts_for_path(
26904            PathKey::for_buffer(&buffer2, cx),
26905            buffer2.clone(),
26906            [Point::zero()..buffer1.read(cx).max_point()],
26907            3,
26908            cx,
26909        );
26910        multi_buffer
26911    });
26912
26913    let (editor, cx) = cx.add_window_view(|window, cx| {
26914        Editor::new(
26915            EditorMode::full(),
26916            multi_buffer,
26917            Some(project.clone()),
26918            window,
26919            cx,
26920        )
26921    });
26922
26923    let fake_language_server = fake_servers.next().await.unwrap();
26924
26925    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26926
26927    let save = editor.update_in(cx, |editor, window, cx| {
26928        assert!(editor.is_dirty(cx));
26929
26930        editor.save(
26931            SaveOptions {
26932                format: true,
26933                autosave: true,
26934            },
26935            project,
26936            window,
26937            cx,
26938        )
26939    });
26940    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26941    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26942    let mut done_edit_rx = Some(done_edit_rx);
26943    let mut start_edit_tx = Some(start_edit_tx);
26944
26945    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26946        start_edit_tx.take().unwrap().send(()).unwrap();
26947        let done_edit_rx = done_edit_rx.take().unwrap();
26948        async move {
26949            done_edit_rx.await.unwrap();
26950            Ok(None)
26951        }
26952    });
26953
26954    start_edit_rx.await.unwrap();
26955    buffer2
26956        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26957        .unwrap();
26958
26959    done_edit_tx.send(()).unwrap();
26960
26961    save.await.unwrap();
26962    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26963}
26964
26965#[track_caller]
26966fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26967    editor
26968        .all_inlays(cx)
26969        .into_iter()
26970        .filter_map(|inlay| inlay.get_color())
26971        .map(Rgba::from)
26972        .collect()
26973}
26974
26975#[gpui::test]
26976fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26977    init_test(cx, |_| {});
26978
26979    let editor = cx.add_window(|window, cx| {
26980        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26981        build_editor(buffer, window, cx)
26982    });
26983
26984    editor
26985        .update(cx, |editor, window, cx| {
26986            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26987                s.select_display_ranges([
26988                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26989                ])
26990            });
26991
26992            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26993
26994            assert_eq!(
26995                editor.display_text(cx),
26996                "line1\nline2\nline2",
26997                "Duplicating last line upward should create duplicate above, not on same line"
26998            );
26999
27000            assert_eq!(
27001                editor.selections.display_ranges(cx),
27002                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27003                "Selection should move to the duplicated line"
27004            );
27005        })
27006        .unwrap();
27007}
27008
27009#[gpui::test]
27010async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27011    init_test(cx, |_| {});
27012
27013    let mut cx = EditorTestContext::new(cx).await;
27014
27015    cx.set_state("line1\nline2ˇ");
27016
27017    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27018
27019    let clipboard_text = cx
27020        .read_from_clipboard()
27021        .and_then(|item| item.text().as_deref().map(str::to_string));
27022
27023    assert_eq!(
27024        clipboard_text,
27025        Some("line2\n".to_string()),
27026        "Copying a line without trailing newline should include a newline"
27027    );
27028
27029    cx.set_state("line1\nˇ");
27030
27031    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27032
27033    cx.assert_editor_state("line1\nline2\nˇ");
27034}
27035
27036#[gpui::test]
27037async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27038    init_test(cx, |_| {});
27039
27040    let mut cx = EditorTestContext::new(cx).await;
27041
27042    cx.set_state("line1\nline2ˇ");
27043    cx.update_editor(|e, window, cx| {
27044        e.set_mode(EditorMode::SingleLine);
27045        assert!(e.key_context(window, cx).contains("end_of_input"));
27046    });
27047    cx.set_state("ˇline1\nline2");
27048    cx.update_editor(|e, window, cx| {
27049        assert!(!e.key_context(window, cx).contains("end_of_input"));
27050    });
27051    cx.set_state("line1ˇ\nline2");
27052    cx.update_editor(|e, window, cx| {
27053        assert!(!e.key_context(window, cx).contains("end_of_input"));
27054    });
27055}
27056
27057#[gpui::test]
27058async fn test_next_prev_reference(cx: &mut TestAppContext) {
27059    const CYCLE_POSITIONS: &[&'static str] = &[
27060        indoc! {"
27061            fn foo() {
27062                let ˇabc = 123;
27063                let x = abc + 1;
27064                let y = abc + 2;
27065                let z = abc + 2;
27066            }
27067        "},
27068        indoc! {"
27069            fn foo() {
27070                let abc = 123;
27071                let x = ˇabc + 1;
27072                let y = abc + 2;
27073                let z = abc + 2;
27074            }
27075        "},
27076        indoc! {"
27077            fn foo() {
27078                let abc = 123;
27079                let x = abc + 1;
27080                let y = ˇabc + 2;
27081                let z = abc + 2;
27082            }
27083        "},
27084        indoc! {"
27085            fn foo() {
27086                let abc = 123;
27087                let x = abc + 1;
27088                let y = abc + 2;
27089                let z = ˇabc + 2;
27090            }
27091        "},
27092    ];
27093
27094    init_test(cx, |_| {});
27095
27096    let mut cx = EditorLspTestContext::new_rust(
27097        lsp::ServerCapabilities {
27098            references_provider: Some(lsp::OneOf::Left(true)),
27099            ..Default::default()
27100        },
27101        cx,
27102    )
27103    .await;
27104
27105    // importantly, the cursor is in the middle
27106    cx.set_state(indoc! {"
27107        fn foo() {
27108            let aˇbc = 123;
27109            let x = abc + 1;
27110            let y = abc + 2;
27111            let z = abc + 2;
27112        }
27113    "});
27114
27115    let reference_ranges = [
27116        lsp::Position::new(1, 8),
27117        lsp::Position::new(2, 12),
27118        lsp::Position::new(3, 12),
27119        lsp::Position::new(4, 12),
27120    ]
27121    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27122
27123    cx.lsp
27124        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27125            Ok(Some(
27126                reference_ranges
27127                    .map(|range| lsp::Location {
27128                        uri: params.text_document_position.text_document.uri.clone(),
27129                        range,
27130                    })
27131                    .to_vec(),
27132            ))
27133        });
27134
27135    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27136        cx.update_editor(|editor, window, cx| {
27137            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27138        })
27139        .unwrap()
27140        .await
27141        .unwrap()
27142    };
27143
27144    _move(Direction::Next, 1, &mut cx).await;
27145    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27146
27147    _move(Direction::Next, 1, &mut cx).await;
27148    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27149
27150    _move(Direction::Next, 1, &mut cx).await;
27151    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27152
27153    // loops back to the start
27154    _move(Direction::Next, 1, &mut cx).await;
27155    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27156
27157    // loops back to the end
27158    _move(Direction::Prev, 1, &mut cx).await;
27159    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27160
27161    _move(Direction::Prev, 1, &mut cx).await;
27162    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27163
27164    _move(Direction::Prev, 1, &mut cx).await;
27165    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27166
27167    _move(Direction::Prev, 1, &mut cx).await;
27168    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27169
27170    _move(Direction::Next, 3, &mut cx).await;
27171    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27172
27173    _move(Direction::Prev, 2, &mut cx).await;
27174    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27175}