editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    linked_editing_ranges::LinkedEditingRanges,
    5    scroll::scroll_amount::ScrollAmount,
    6    test::{
    7        assert_text_with_selections, build_editor,
    8        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
    9        editor_test_context::EditorTestContext,
   10        select_ranges,
   11    },
   12};
   13use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   14use futures::StreamExt;
   15use gpui::{
   16    BackgroundExecutor, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
   17    VisualTestContext, WindowBounds, WindowOptions, div,
   18};
   19use indoc::indoc;
   20use language::{
   21    BracketPairConfig,
   22    Capability::ReadWrite,
   23    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   24    Override, Point,
   25    language_settings::{
   26        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   27        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   28    },
   29    tree_sitter_python,
   30};
   31use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   32use lsp::CompletionParams;
   33use multi_buffer::{IndentGuide, PathKey};
   34use parking_lot::Mutex;
   35use pretty_assertions::{assert_eq, assert_ne};
   36use project::{
   37    FakeFs,
   38    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   39    project_settings::{LspSettings, ProjectSettings},
   40};
   41use serde_json::{self, json};
   42use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   43use std::{
   44    iter,
   45    sync::atomic::{self, AtomicUsize},
   46};
   47use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   48use text::ToPoint as _;
   49use unindent::Unindent;
   50use util::{
   51    assert_set_eq, path,
   52    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   53    uri,
   54};
   55use workspace::{
   56    CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
   57    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   58};
   59
   60#[gpui::test]
   61fn test_edit_events(cx: &mut TestAppContext) {
   62    init_test(cx, |_| {});
   63
   64    let buffer = cx.new(|cx| {
   65        let mut buffer = language::Buffer::local("123456", cx);
   66        buffer.set_group_interval(Duration::from_secs(1));
   67        buffer
   68    });
   69
   70    let events = Rc::new(RefCell::new(Vec::new()));
   71    let editor1 = cx.add_window({
   72        let events = events.clone();
   73        |window, cx| {
   74            let entity = cx.entity().clone();
   75            cx.subscribe_in(
   76                &entity,
   77                window,
   78                move |_, _, event: &EditorEvent, _, _| match event {
   79                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   80                    EditorEvent::BufferEdited => {
   81                        events.borrow_mut().push(("editor1", "buffer edited"))
   82                    }
   83                    _ => {}
   84                },
   85            )
   86            .detach();
   87            Editor::for_buffer(buffer.clone(), None, window, cx)
   88        }
   89    });
   90
   91    let editor2 = cx.add_window({
   92        let events = events.clone();
   93        |window, cx| {
   94            cx.subscribe_in(
   95                &cx.entity().clone(),
   96                window,
   97                move |_, _, event: &EditorEvent, _, _| match event {
   98                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
   99                    EditorEvent::BufferEdited => {
  100                        events.borrow_mut().push(("editor2", "buffer edited"))
  101                    }
  102                    _ => {}
  103                },
  104            )
  105            .detach();
  106            Editor::for_buffer(buffer.clone(), None, window, cx)
  107        }
  108    });
  109
  110    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  111
  112    // Mutating editor 1 will emit an `Edited` event only for that editor.
  113    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  114    assert_eq!(
  115        mem::take(&mut *events.borrow_mut()),
  116        [
  117            ("editor1", "edited"),
  118            ("editor1", "buffer edited"),
  119            ("editor2", "buffer edited"),
  120        ]
  121    );
  122
  123    // Mutating editor 2 will emit an `Edited` event only for that editor.
  124    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor2", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, 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    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  157    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor2", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, 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    // No event is emitted when the mutation is a no-op.
  179    _ = editor2.update(cx, |editor, window, cx| {
  180        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  181
  182        editor.backspace(&Backspace, window, cx);
  183    });
  184    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  185}
  186
  187#[gpui::test]
  188fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  189    init_test(cx, |_| {});
  190
  191    let mut now = Instant::now();
  192    let group_interval = Duration::from_millis(1);
  193    let buffer = cx.new(|cx| {
  194        let mut buf = language::Buffer::local("123456", cx);
  195        buf.set_group_interval(group_interval);
  196        buf
  197    });
  198    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  199    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  200
  201    _ = editor.update(cx, |editor, window, cx| {
  202        editor.start_transaction_at(now, window, cx);
  203        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  204
  205        editor.insert("cd", window, cx);
  206        editor.end_transaction_at(now, cx);
  207        assert_eq!(editor.text(cx), "12cd56");
  208        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  209
  210        editor.start_transaction_at(now, window, cx);
  211        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  212        editor.insert("e", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cde6");
  215        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  216
  217        now += group_interval + Duration::from_millis(1);
  218        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  219
  220        // Simulate an edit in another editor
  221        buffer.update(cx, |buffer, cx| {
  222            buffer.start_transaction_at(now, cx);
  223            buffer.edit([(0..1, "a")], None, cx);
  224            buffer.edit([(1..1, "b")], None, cx);
  225            buffer.end_transaction_at(now, cx);
  226        });
  227
  228        assert_eq!(editor.text(cx), "ab2cde6");
  229        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  230
  231        // Last transaction happened past the group interval in a different editor.
  232        // Undo it individually and don't restore selections.
  233        editor.undo(&Undo, window, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  236
  237        // First two transactions happened within the group interval in this editor.
  238        // Undo them together and restore selections.
  239        editor.undo(&Undo, window, cx);
  240        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  241        assert_eq!(editor.text(cx), "123456");
  242        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  243
  244        // Redo the first two transactions together.
  245        editor.redo(&Redo, window, cx);
  246        assert_eq!(editor.text(cx), "12cde6");
  247        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  248
  249        // Redo the last transaction on its own.
  250        editor.redo(&Redo, window, cx);
  251        assert_eq!(editor.text(cx), "ab2cde6");
  252        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  253
  254        // Test empty transactions.
  255        editor.start_transaction_at(now, window, cx);
  256        editor.end_transaction_at(now, cx);
  257        editor.undo(&Undo, window, cx);
  258        assert_eq!(editor.text(cx), "12cde6");
  259    });
  260}
  261
  262#[gpui::test]
  263fn test_ime_composition(cx: &mut TestAppContext) {
  264    init_test(cx, |_| {});
  265
  266    let buffer = cx.new(|cx| {
  267        let mut buffer = language::Buffer::local("abcde", cx);
  268        // Ensure automatic grouping doesn't occur.
  269        buffer.set_group_interval(Duration::ZERO);
  270        buffer
  271    });
  272
  273    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  274    cx.add_window(|window, cx| {
  275        let mut editor = build_editor(buffer.clone(), window, cx);
  276
  277        // Start a new IME composition.
  278        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  279        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  280        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  281        assert_eq!(editor.text(cx), "äbcde");
  282        assert_eq!(
  283            editor.marked_text_ranges(cx),
  284            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  285        );
  286
  287        // Finalize IME composition.
  288        editor.replace_text_in_range(None, "ā", window, cx);
  289        assert_eq!(editor.text(cx), "ābcde");
  290        assert_eq!(editor.marked_text_ranges(cx), None);
  291
  292        // IME composition edits are grouped and are undone/redone at once.
  293        editor.undo(&Default::default(), window, cx);
  294        assert_eq!(editor.text(cx), "abcde");
  295        assert_eq!(editor.marked_text_ranges(cx), None);
  296        editor.redo(&Default::default(), window, cx);
  297        assert_eq!(editor.text(cx), "ābcde");
  298        assert_eq!(editor.marked_text_ranges(cx), None);
  299
  300        // Start a new IME composition.
  301        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  302        assert_eq!(
  303            editor.marked_text_ranges(cx),
  304            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  305        );
  306
  307        // Undoing during an IME composition cancels it.
  308        editor.undo(&Default::default(), window, cx);
  309        assert_eq!(editor.text(cx), "ābcde");
  310        assert_eq!(editor.marked_text_ranges(cx), None);
  311
  312        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  313        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  314        assert_eq!(editor.text(cx), "ābcdè");
  315        assert_eq!(
  316            editor.marked_text_ranges(cx),
  317            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  318        );
  319
  320        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  321        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  322        assert_eq!(editor.text(cx), "ābcdę");
  323        assert_eq!(editor.marked_text_ranges(cx), None);
  324
  325        // Start a new IME composition with multiple cursors.
  326        editor.change_selections(None, window, cx, |s| {
  327            s.select_ranges([
  328                OffsetUtf16(1)..OffsetUtf16(1),
  329                OffsetUtf16(3)..OffsetUtf16(3),
  330                OffsetUtf16(5)..OffsetUtf16(5),
  331            ])
  332        });
  333        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  334        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  335        assert_eq!(
  336            editor.marked_text_ranges(cx),
  337            Some(vec![
  338                OffsetUtf16(0)..OffsetUtf16(3),
  339                OffsetUtf16(4)..OffsetUtf16(7),
  340                OffsetUtf16(8)..OffsetUtf16(11)
  341            ])
  342        );
  343
  344        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  345        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  346        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  347        assert_eq!(
  348            editor.marked_text_ranges(cx),
  349            Some(vec![
  350                OffsetUtf16(1)..OffsetUtf16(2),
  351                OffsetUtf16(5)..OffsetUtf16(6),
  352                OffsetUtf16(9)..OffsetUtf16(10)
  353            ])
  354        );
  355
  356        // Finalize IME composition with multiple cursors.
  357        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  358        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        editor
  362    });
  363}
  364
  365#[gpui::test]
  366fn test_selection_with_mouse(cx: &mut TestAppContext) {
  367    init_test(cx, |_| {});
  368
  369    let editor = cx.add_window(|window, cx| {
  370        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  371        build_editor(buffer, window, cx)
  372    });
  373
  374    _ = editor.update(cx, |editor, window, cx| {
  375        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  376    });
  377    assert_eq!(
  378        editor
  379            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  380            .unwrap(),
  381        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  382    );
  383
  384    _ = editor.update(cx, |editor, window, cx| {
  385        editor.update_selection(
  386            DisplayPoint::new(DisplayRow(3), 3),
  387            0,
  388            gpui::Point::<f32>::default(),
  389            window,
  390            cx,
  391        );
  392    });
  393
  394    assert_eq!(
  395        editor
  396            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  397            .unwrap(),
  398        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  399    );
  400
  401    _ = editor.update(cx, |editor, window, cx| {
  402        editor.update_selection(
  403            DisplayPoint::new(DisplayRow(1), 1),
  404            0,
  405            gpui::Point::<f32>::default(),
  406            window,
  407            cx,
  408        );
  409    });
  410
  411    assert_eq!(
  412        editor
  413            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  414            .unwrap(),
  415        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  416    );
  417
  418    _ = editor.update(cx, |editor, window, cx| {
  419        editor.end_selection(window, cx);
  420        editor.update_selection(
  421            DisplayPoint::new(DisplayRow(3), 3),
  422            0,
  423            gpui::Point::<f32>::default(),
  424            window,
  425            cx,
  426        );
  427    });
  428
  429    assert_eq!(
  430        editor
  431            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  432            .unwrap(),
  433        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  434    );
  435
  436    _ = editor.update(cx, |editor, window, cx| {
  437        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  438        editor.update_selection(
  439            DisplayPoint::new(DisplayRow(0), 0),
  440            0,
  441            gpui::Point::<f32>::default(),
  442            window,
  443            cx,
  444        );
  445    });
  446
  447    assert_eq!(
  448        editor
  449            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  450            .unwrap(),
  451        [
  452            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  453            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  454        ]
  455    );
  456
  457    _ = editor.update(cx, |editor, window, cx| {
  458        editor.end_selection(window, cx);
  459    });
  460
  461    assert_eq!(
  462        editor
  463            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  464            .unwrap(),
  465        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  466    );
  467}
  468
  469#[gpui::test]
  470fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  471    init_test(cx, |_| {});
  472
  473    let editor = cx.add_window(|window, cx| {
  474        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  475        build_editor(buffer, window, cx)
  476    });
  477
  478    _ = editor.update(cx, |editor, window, cx| {
  479        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  480    });
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.end_selection(window, cx);
  484    });
  485
  486    _ = editor.update(cx, |editor, window, cx| {
  487        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  488    });
  489
  490    _ = editor.update(cx, |editor, window, cx| {
  491        editor.end_selection(window, cx);
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  497            .unwrap(),
  498        [
  499            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  500            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  501        ]
  502    );
  503
  504    _ = editor.update(cx, |editor, window, cx| {
  505        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  506    });
  507
  508    _ = editor.update(cx, |editor, window, cx| {
  509        editor.end_selection(window, cx);
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  515            .unwrap(),
  516        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  517    );
  518}
  519
  520#[gpui::test]
  521fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  522    init_test(cx, |_| {});
  523
  524    let editor = cx.add_window(|window, cx| {
  525        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  526        build_editor(buffer, window, cx)
  527    });
  528
  529    _ = editor.update(cx, |editor, window, cx| {
  530        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  531        assert_eq!(
  532            editor.selections.display_ranges(cx),
  533            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  534        );
  535    });
  536
  537    _ = editor.update(cx, |editor, window, cx| {
  538        editor.update_selection(
  539            DisplayPoint::new(DisplayRow(3), 3),
  540            0,
  541            gpui::Point::<f32>::default(),
  542            window,
  543            cx,
  544        );
  545        assert_eq!(
  546            editor.selections.display_ranges(cx),
  547            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  548        );
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.cancel(&Cancel, window, cx);
  553        editor.update_selection(
  554            DisplayPoint::new(DisplayRow(1), 1),
  555            0,
  556            gpui::Point::<f32>::default(),
  557            window,
  558            cx,
  559        );
  560        assert_eq!(
  561            editor.selections.display_ranges(cx),
  562            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  563        );
  564    });
  565}
  566
  567#[gpui::test]
  568fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  569    init_test(cx, |_| {});
  570
  571    let editor = cx.add_window(|window, cx| {
  572        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  573        build_editor(buffer, window, cx)
  574    });
  575
  576    _ = editor.update(cx, |editor, window, cx| {
  577        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  578        assert_eq!(
  579            editor.selections.display_ranges(cx),
  580            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  581        );
  582
  583        editor.move_down(&Default::default(), window, cx);
  584        assert_eq!(
  585            editor.selections.display_ranges(cx),
  586            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  587        );
  588
  589        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  590        assert_eq!(
  591            editor.selections.display_ranges(cx),
  592            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  593        );
  594
  595        editor.move_up(&Default::default(), window, cx);
  596        assert_eq!(
  597            editor.selections.display_ranges(cx),
  598            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  599        );
  600    });
  601}
  602
  603#[gpui::test]
  604fn test_clone(cx: &mut TestAppContext) {
  605    init_test(cx, |_| {});
  606
  607    let (text, selection_ranges) = marked_text_ranges(
  608        indoc! {"
  609            one
  610            two
  611            threeˇ
  612            four
  613            fiveˇ
  614        "},
  615        true,
  616    );
  617
  618    let editor = cx.add_window(|window, cx| {
  619        let buffer = MultiBuffer::build_simple(&text, cx);
  620        build_editor(buffer, window, cx)
  621    });
  622
  623    _ = editor.update(cx, |editor, window, cx| {
  624        editor.change_selections(None, window, cx, |s| {
  625            s.select_ranges(selection_ranges.clone())
  626        });
  627        editor.fold_creases(
  628            vec![
  629                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  630                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  631            ],
  632            true,
  633            window,
  634            cx,
  635        );
  636    });
  637
  638    let cloned_editor = editor
  639        .update(cx, |editor, _, cx| {
  640            cx.open_window(Default::default(), |window, cx| {
  641                cx.new(|cx| editor.clone(window, cx))
  642            })
  643        })
  644        .unwrap()
  645        .unwrap();
  646
  647    let snapshot = editor
  648        .update(cx, |e, window, cx| e.snapshot(window, cx))
  649        .unwrap();
  650    let cloned_snapshot = cloned_editor
  651        .update(cx, |e, window, cx| e.snapshot(window, cx))
  652        .unwrap();
  653
  654    assert_eq!(
  655        cloned_editor
  656            .update(cx, |e, _, cx| e.display_text(cx))
  657            .unwrap(),
  658        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  659    );
  660    assert_eq!(
  661        cloned_snapshot
  662            .folds_in_range(0..text.len())
  663            .collect::<Vec<_>>(),
  664        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  665    );
  666    assert_set_eq!(
  667        cloned_editor
  668            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  669            .unwrap(),
  670        editor
  671            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  672            .unwrap()
  673    );
  674    assert_set_eq!(
  675        cloned_editor
  676            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  677            .unwrap(),
  678        editor
  679            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  680            .unwrap()
  681    );
  682}
  683
  684#[gpui::test]
  685async fn test_navigation_history(cx: &mut TestAppContext) {
  686    init_test(cx, |_| {});
  687
  688    use workspace::item::Item;
  689
  690    let fs = FakeFs::new(cx.executor());
  691    let project = Project::test(fs, [], cx).await;
  692    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  693    let pane = workspace
  694        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  695        .unwrap();
  696
  697    _ = workspace.update(cx, |_v, window, cx| {
  698        cx.new(|cx| {
  699            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  700            let mut editor = build_editor(buffer.clone(), window, cx);
  701            let handle = cx.entity();
  702            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  703
  704            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  705                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  706            }
  707
  708            // Move the cursor a small distance.
  709            // Nothing is added to the navigation history.
  710            editor.change_selections(None, window, cx, |s| {
  711                s.select_display_ranges([
  712                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  713                ])
  714            });
  715            editor.change_selections(None, window, cx, |s| {
  716                s.select_display_ranges([
  717                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  718                ])
  719            });
  720            assert!(pop_history(&mut editor, cx).is_none());
  721
  722            // Move the cursor a large distance.
  723            // The history can jump back to the previous position.
  724            editor.change_selections(None, window, cx, |s| {
  725                s.select_display_ranges([
  726                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  727                ])
  728            });
  729            let nav_entry = pop_history(&mut editor, cx).unwrap();
  730            editor.navigate(nav_entry.data.unwrap(), window, cx);
  731            assert_eq!(nav_entry.item.id(), cx.entity_id());
  732            assert_eq!(
  733                editor.selections.display_ranges(cx),
  734                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  735            );
  736            assert!(pop_history(&mut editor, cx).is_none());
  737
  738            // Move the cursor a small distance via the mouse.
  739            // Nothing is added to the navigation history.
  740            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  741            editor.end_selection(window, cx);
  742            assert_eq!(
  743                editor.selections.display_ranges(cx),
  744                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  745            );
  746            assert!(pop_history(&mut editor, cx).is_none());
  747
  748            // Move the cursor a large distance via the mouse.
  749            // The history can jump back to the previous position.
  750            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  751            editor.end_selection(window, cx);
  752            assert_eq!(
  753                editor.selections.display_ranges(cx),
  754                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  755            );
  756            let nav_entry = pop_history(&mut editor, cx).unwrap();
  757            editor.navigate(nav_entry.data.unwrap(), window, cx);
  758            assert_eq!(nav_entry.item.id(), cx.entity_id());
  759            assert_eq!(
  760                editor.selections.display_ranges(cx),
  761                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  762            );
  763            assert!(pop_history(&mut editor, cx).is_none());
  764
  765            // Set scroll position to check later
  766            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  767            let original_scroll_position = editor.scroll_manager.anchor();
  768
  769            // Jump to the end of the document and adjust scroll
  770            editor.move_to_end(&MoveToEnd, window, cx);
  771            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  772            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  773
  774            let nav_entry = pop_history(&mut editor, cx).unwrap();
  775            editor.navigate(nav_entry.data.unwrap(), window, cx);
  776            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  777
  778            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  779            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  780            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  781            let invalid_point = Point::new(9999, 0);
  782            editor.navigate(
  783                Box::new(NavigationData {
  784                    cursor_anchor: invalid_anchor,
  785                    cursor_position: invalid_point,
  786                    scroll_anchor: ScrollAnchor {
  787                        anchor: invalid_anchor,
  788                        offset: Default::default(),
  789                    },
  790                    scroll_top_row: invalid_point.row,
  791                }),
  792                window,
  793                cx,
  794            );
  795            assert_eq!(
  796                editor.selections.display_ranges(cx),
  797                &[editor.max_point(cx)..editor.max_point(cx)]
  798            );
  799            assert_eq!(
  800                editor.scroll_position(cx),
  801                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  802            );
  803
  804            editor
  805        })
  806    });
  807}
  808
  809#[gpui::test]
  810fn test_cancel(cx: &mut TestAppContext) {
  811    init_test(cx, |_| {});
  812
  813    let editor = cx.add_window(|window, cx| {
  814        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  815        build_editor(buffer, window, cx)
  816    });
  817
  818    _ = editor.update(cx, |editor, window, cx| {
  819        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  820        editor.update_selection(
  821            DisplayPoint::new(DisplayRow(1), 1),
  822            0,
  823            gpui::Point::<f32>::default(),
  824            window,
  825            cx,
  826        );
  827        editor.end_selection(window, cx);
  828
  829        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  830        editor.update_selection(
  831            DisplayPoint::new(DisplayRow(0), 3),
  832            0,
  833            gpui::Point::<f32>::default(),
  834            window,
  835            cx,
  836        );
  837        editor.end_selection(window, cx);
  838        assert_eq!(
  839            editor.selections.display_ranges(cx),
  840            [
  841                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  842                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  843            ]
  844        );
  845    });
  846
  847    _ = editor.update(cx, |editor, window, cx| {
  848        editor.cancel(&Cancel, window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  852        );
  853    });
  854
  855    _ = editor.update(cx, |editor, window, cx| {
  856        editor.cancel(&Cancel, window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  860        );
  861    });
  862}
  863
  864#[gpui::test]
  865fn test_fold_action(cx: &mut TestAppContext) {
  866    init_test(cx, |_| {});
  867
  868    let editor = cx.add_window(|window, cx| {
  869        let buffer = MultiBuffer::build_simple(
  870            &"
  871                impl Foo {
  872                    // Hello!
  873
  874                    fn a() {
  875                        1
  876                    }
  877
  878                    fn b() {
  879                        2
  880                    }
  881
  882                    fn c() {
  883                        3
  884                    }
  885                }
  886            "
  887            .unindent(),
  888            cx,
  889        );
  890        build_editor(buffer.clone(), window, cx)
  891    });
  892
  893    _ = editor.update(cx, |editor, window, cx| {
  894        editor.change_selections(None, window, cx, |s| {
  895            s.select_display_ranges([
  896                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  897            ]);
  898        });
  899        editor.fold(&Fold, window, cx);
  900        assert_eq!(
  901            editor.display_text(cx),
  902            "
  903                impl Foo {
  904                    // Hello!
  905
  906                    fn a() {
  907                        1
  908                    }
  909
  910                    fn b() {⋯
  911                    }
  912
  913                    fn c() {⋯
  914                    }
  915                }
  916            "
  917            .unindent(),
  918        );
  919
  920        editor.fold(&Fold, window, cx);
  921        assert_eq!(
  922            editor.display_text(cx),
  923            "
  924                impl Foo {⋯
  925                }
  926            "
  927            .unindent(),
  928        );
  929
  930        editor.unfold_lines(&UnfoldLines, window, cx);
  931        assert_eq!(
  932            editor.display_text(cx),
  933            "
  934                impl Foo {
  935                    // Hello!
  936
  937                    fn a() {
  938                        1
  939                    }
  940
  941                    fn b() {⋯
  942                    }
  943
  944                    fn c() {⋯
  945                    }
  946                }
  947            "
  948            .unindent(),
  949        );
  950
  951        editor.unfold_lines(&UnfoldLines, window, cx);
  952        assert_eq!(
  953            editor.display_text(cx),
  954            editor.buffer.read(cx).read(cx).text()
  955        );
  956    });
  957}
  958
  959#[gpui::test]
  960fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  961    init_test(cx, |_| {});
  962
  963    let editor = cx.add_window(|window, cx| {
  964        let buffer = MultiBuffer::build_simple(
  965            &"
  966                class Foo:
  967                    # Hello!
  968
  969                    def a():
  970                        print(1)
  971
  972                    def b():
  973                        print(2)
  974
  975                    def c():
  976                        print(3)
  977            "
  978            .unindent(),
  979            cx,
  980        );
  981        build_editor(buffer.clone(), window, cx)
  982    });
  983
  984    _ = editor.update(cx, |editor, window, cx| {
  985        editor.change_selections(None, window, cx, |s| {
  986            s.select_display_ranges([
  987                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  988            ]);
  989        });
  990        editor.fold(&Fold, window, cx);
  991        assert_eq!(
  992            editor.display_text(cx),
  993            "
  994                class Foo:
  995                    # Hello!
  996
  997                    def a():
  998                        print(1)
  999
 1000                    def b():⋯
 1001
 1002                    def c():⋯
 1003            "
 1004            .unindent(),
 1005        );
 1006
 1007        editor.fold(&Fold, window, cx);
 1008        assert_eq!(
 1009            editor.display_text(cx),
 1010            "
 1011                class Foo:⋯
 1012            "
 1013            .unindent(),
 1014        );
 1015
 1016        editor.unfold_lines(&UnfoldLines, window, cx);
 1017        assert_eq!(
 1018            editor.display_text(cx),
 1019            "
 1020                class Foo:
 1021                    # Hello!
 1022
 1023                    def a():
 1024                        print(1)
 1025
 1026                    def b():⋯
 1027
 1028                    def c():⋯
 1029            "
 1030            .unindent(),
 1031        );
 1032
 1033        editor.unfold_lines(&UnfoldLines, window, cx);
 1034        assert_eq!(
 1035            editor.display_text(cx),
 1036            editor.buffer.read(cx).read(cx).text()
 1037        );
 1038    });
 1039}
 1040
 1041#[gpui::test]
 1042fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1043    init_test(cx, |_| {});
 1044
 1045    let editor = cx.add_window(|window, cx| {
 1046        let buffer = MultiBuffer::build_simple(
 1047            &"
 1048                class Foo:
 1049                    # Hello!
 1050
 1051                    def a():
 1052                        print(1)
 1053
 1054                    def b():
 1055                        print(2)
 1056
 1057
 1058                    def c():
 1059                        print(3)
 1060
 1061
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer.clone(), window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(None, window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                class Foo:
 1080                    # Hello!
 1081
 1082                    def a():
 1083                        print(1)
 1084
 1085                    def b():⋯
 1086
 1087
 1088                    def c():⋯
 1089
 1090
 1091            "
 1092            .unindent(),
 1093        );
 1094
 1095        editor.fold(&Fold, window, cx);
 1096        assert_eq!(
 1097            editor.display_text(cx),
 1098            "
 1099                class Foo:⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:
 1111                    # Hello!
 1112
 1113                    def a():
 1114                        print(1)
 1115
 1116                    def b():⋯
 1117
 1118
 1119                    def c():⋯
 1120
 1121
 1122            "
 1123            .unindent(),
 1124        );
 1125
 1126        editor.unfold_lines(&UnfoldLines, window, cx);
 1127        assert_eq!(
 1128            editor.display_text(cx),
 1129            editor.buffer.read(cx).read(cx).text()
 1130        );
 1131    });
 1132}
 1133
 1134#[gpui::test]
 1135fn test_fold_at_level(cx: &mut TestAppContext) {
 1136    init_test(cx, |_| {});
 1137
 1138    let editor = cx.add_window(|window, cx| {
 1139        let buffer = MultiBuffer::build_simple(
 1140            &"
 1141                class Foo:
 1142                    # Hello!
 1143
 1144                    def a():
 1145                        print(1)
 1146
 1147                    def b():
 1148                        print(2)
 1149
 1150
 1151                class Bar:
 1152                    # World!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():
 1158                        print(2)
 1159
 1160
 1161            "
 1162            .unindent(),
 1163            cx,
 1164        );
 1165        build_editor(buffer.clone(), window, cx)
 1166    });
 1167
 1168    _ = editor.update(cx, |editor, window, cx| {
 1169        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1170        assert_eq!(
 1171            editor.display_text(cx),
 1172            "
 1173                class Foo:
 1174                    # Hello!
 1175
 1176                    def a():⋯
 1177
 1178                    def b():⋯
 1179
 1180
 1181                class Bar:
 1182                    # World!
 1183
 1184                    def a():⋯
 1185
 1186                    def b():⋯
 1187
 1188
 1189            "
 1190            .unindent(),
 1191        );
 1192
 1193        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1194        assert_eq!(
 1195            editor.display_text(cx),
 1196            "
 1197                class Foo:⋯
 1198
 1199
 1200                class Bar:⋯
 1201
 1202
 1203            "
 1204            .unindent(),
 1205        );
 1206
 1207        editor.unfold_all(&UnfoldAll, window, cx);
 1208        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1209        assert_eq!(
 1210            editor.display_text(cx),
 1211            "
 1212                class Foo:
 1213                    # Hello!
 1214
 1215                    def a():
 1216                        print(1)
 1217
 1218                    def b():
 1219                        print(2)
 1220
 1221
 1222                class Bar:
 1223                    # World!
 1224
 1225                    def a():
 1226                        print(1)
 1227
 1228                    def b():
 1229                        print(2)
 1230
 1231
 1232            "
 1233            .unindent(),
 1234        );
 1235
 1236        assert_eq!(
 1237            editor.display_text(cx),
 1238            editor.buffer.read(cx).read(cx).text()
 1239        );
 1240    });
 1241}
 1242
 1243#[gpui::test]
 1244fn test_move_cursor(cx: &mut TestAppContext) {
 1245    init_test(cx, |_| {});
 1246
 1247    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1248    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1249
 1250    buffer.update(cx, |buffer, cx| {
 1251        buffer.edit(
 1252            vec![
 1253                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1254                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1255            ],
 1256            None,
 1257            cx,
 1258        );
 1259    });
 1260    _ = editor.update(cx, |editor, window, cx| {
 1261        assert_eq!(
 1262            editor.selections.display_ranges(cx),
 1263            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1264        );
 1265
 1266        editor.move_down(&MoveDown, window, cx);
 1267        assert_eq!(
 1268            editor.selections.display_ranges(cx),
 1269            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1270        );
 1271
 1272        editor.move_right(&MoveRight, window, cx);
 1273        assert_eq!(
 1274            editor.selections.display_ranges(cx),
 1275            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1276        );
 1277
 1278        editor.move_left(&MoveLeft, window, cx);
 1279        assert_eq!(
 1280            editor.selections.display_ranges(cx),
 1281            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1282        );
 1283
 1284        editor.move_up(&MoveUp, window, cx);
 1285        assert_eq!(
 1286            editor.selections.display_ranges(cx),
 1287            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1288        );
 1289
 1290        editor.move_to_end(&MoveToEnd, window, cx);
 1291        assert_eq!(
 1292            editor.selections.display_ranges(cx),
 1293            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1294        );
 1295
 1296        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1297        assert_eq!(
 1298            editor.selections.display_ranges(cx),
 1299            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1300        );
 1301
 1302        editor.change_selections(None, window, cx, |s| {
 1303            s.select_display_ranges([
 1304                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1305            ]);
 1306        });
 1307        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.select_to_end(&SelectToEnd, window, cx);
 1314        assert_eq!(
 1315            editor.selections.display_ranges(cx),
 1316            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1317        );
 1318    });
 1319}
 1320
 1321#[gpui::test]
 1322fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1323    init_test(cx, |_| {});
 1324
 1325    let editor = cx.add_window(|window, cx| {
 1326        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1327        build_editor(buffer.clone(), window, cx)
 1328    });
 1329
 1330    assert_eq!('🟥'.len_utf8(), 4);
 1331    assert_eq!('α'.len_utf8(), 2);
 1332
 1333    _ = editor.update(cx, |editor, window, cx| {
 1334        editor.fold_creases(
 1335            vec![
 1336                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1337                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1338                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1339            ],
 1340            true,
 1341            window,
 1342            cx,
 1343        );
 1344        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1345
 1346        editor.move_right(&MoveRight, window, cx);
 1347        assert_eq!(
 1348            editor.selections.display_ranges(cx),
 1349            &[empty_range(0, "🟥".len())]
 1350        );
 1351        editor.move_right(&MoveRight, window, cx);
 1352        assert_eq!(
 1353            editor.selections.display_ranges(cx),
 1354            &[empty_range(0, "🟥🟧".len())]
 1355        );
 1356        editor.move_right(&MoveRight, window, cx);
 1357        assert_eq!(
 1358            editor.selections.display_ranges(cx),
 1359            &[empty_range(0, "🟥🟧⋯".len())]
 1360        );
 1361
 1362        editor.move_down(&MoveDown, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(1, "ab⋯e".len())]
 1366        );
 1367        editor.move_left(&MoveLeft, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(1, "ab⋯".len())]
 1371        );
 1372        editor.move_left(&MoveLeft, window, cx);
 1373        assert_eq!(
 1374            editor.selections.display_ranges(cx),
 1375            &[empty_range(1, "ab".len())]
 1376        );
 1377        editor.move_left(&MoveLeft, window, cx);
 1378        assert_eq!(
 1379            editor.selections.display_ranges(cx),
 1380            &[empty_range(1, "a".len())]
 1381        );
 1382
 1383        editor.move_down(&MoveDown, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(2, "α".len())]
 1387        );
 1388        editor.move_right(&MoveRight, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(2, "αβ".len())]
 1392        );
 1393        editor.move_right(&MoveRight, window, cx);
 1394        assert_eq!(
 1395            editor.selections.display_ranges(cx),
 1396            &[empty_range(2, "αβ⋯".len())]
 1397        );
 1398        editor.move_right(&MoveRight, window, cx);
 1399        assert_eq!(
 1400            editor.selections.display_ranges(cx),
 1401            &[empty_range(2, "αβ⋯ε".len())]
 1402        );
 1403
 1404        editor.move_up(&MoveUp, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(1, "ab⋯e".len())]
 1408        );
 1409        editor.move_down(&MoveDown, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414        editor.move_up(&MoveUp, window, cx);
 1415        assert_eq!(
 1416            editor.selections.display_ranges(cx),
 1417            &[empty_range(1, "ab⋯e".len())]
 1418        );
 1419
 1420        editor.move_up(&MoveUp, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(0, "🟥🟧".len())]
 1424        );
 1425        editor.move_left(&MoveLeft, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(0, "🟥".len())]
 1429        );
 1430        editor.move_left(&MoveLeft, window, cx);
 1431        assert_eq!(
 1432            editor.selections.display_ranges(cx),
 1433            &[empty_range(0, "".len())]
 1434        );
 1435    });
 1436}
 1437
 1438#[gpui::test]
 1439fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1440    init_test(cx, |_| {});
 1441
 1442    let editor = cx.add_window(|window, cx| {
 1443        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1444        build_editor(buffer.clone(), window, cx)
 1445    });
 1446    _ = editor.update(cx, |editor, window, cx| {
 1447        editor.change_selections(None, window, cx, |s| {
 1448            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1449        });
 1450
 1451        // moving above start of document should move selection to start of document,
 1452        // but the next move down should still be at the original goal_x
 1453        editor.move_up(&MoveUp, window, cx);
 1454        assert_eq!(
 1455            editor.selections.display_ranges(cx),
 1456            &[empty_range(0, "".len())]
 1457        );
 1458
 1459        editor.move_down(&MoveDown, window, cx);
 1460        assert_eq!(
 1461            editor.selections.display_ranges(cx),
 1462            &[empty_range(1, "abcd".len())]
 1463        );
 1464
 1465        editor.move_down(&MoveDown, window, cx);
 1466        assert_eq!(
 1467            editor.selections.display_ranges(cx),
 1468            &[empty_range(2, "αβγ".len())]
 1469        );
 1470
 1471        editor.move_down(&MoveDown, window, cx);
 1472        assert_eq!(
 1473            editor.selections.display_ranges(cx),
 1474            &[empty_range(3, "abcd".len())]
 1475        );
 1476
 1477        editor.move_down(&MoveDown, window, cx);
 1478        assert_eq!(
 1479            editor.selections.display_ranges(cx),
 1480            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1481        );
 1482
 1483        // moving past end of document should not change goal_x
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(5, "".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(5, "".len())]
 1494        );
 1495
 1496        editor.move_up(&MoveUp, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1500        );
 1501
 1502        editor.move_up(&MoveUp, window, cx);
 1503        assert_eq!(
 1504            editor.selections.display_ranges(cx),
 1505            &[empty_range(3, "abcd".len())]
 1506        );
 1507
 1508        editor.move_up(&MoveUp, window, cx);
 1509        assert_eq!(
 1510            editor.selections.display_ranges(cx),
 1511            &[empty_range(2, "αβγ".len())]
 1512        );
 1513    });
 1514}
 1515
 1516#[gpui::test]
 1517fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1518    init_test(cx, |_| {});
 1519    let move_to_beg = MoveToBeginningOfLine {
 1520        stop_at_soft_wraps: true,
 1521        stop_at_indent: true,
 1522    };
 1523
 1524    let delete_to_beg = DeleteToBeginningOfLine {
 1525        stop_at_indent: false,
 1526    };
 1527
 1528    let move_to_end = MoveToEndOfLine {
 1529        stop_at_soft_wraps: true,
 1530    };
 1531
 1532    let editor = cx.add_window(|window, cx| {
 1533        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1534        build_editor(buffer, window, cx)
 1535    });
 1536    _ = editor.update(cx, |editor, window, cx| {
 1537        editor.change_selections(None, window, cx, |s| {
 1538            s.select_display_ranges([
 1539                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1540                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1541            ]);
 1542        });
 1543    });
 1544
 1545    _ = editor.update(cx, |editor, window, cx| {
 1546        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1547        assert_eq!(
 1548            editor.selections.display_ranges(cx),
 1549            &[
 1550                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1551                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1552            ]
 1553        );
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_end_of_line(&move_to_end, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1584                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1585            ]
 1586        );
 1587    });
 1588
 1589    // Moving to the end of line again is a no-op.
 1590    _ = editor.update(cx, |editor, window, cx| {
 1591        editor.move_to_end_of_line(&move_to_end, window, cx);
 1592        assert_eq!(
 1593            editor.selections.display_ranges(cx),
 1594            &[
 1595                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1596                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1597            ]
 1598        );
 1599    });
 1600
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_left(&MoveLeft, window, cx);
 1603        editor.select_to_beginning_of_line(
 1604            &SelectToBeginningOfLine {
 1605                stop_at_soft_wraps: true,
 1606                stop_at_indent: true,
 1607            },
 1608            window,
 1609            cx,
 1610        );
 1611        assert_eq!(
 1612            editor.selections.display_ranges(cx),
 1613            &[
 1614                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1615                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1616            ]
 1617        );
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.select_to_beginning_of_line(
 1622            &SelectToBeginningOfLine {
 1623                stop_at_soft_wraps: true,
 1624                stop_at_indent: true,
 1625            },
 1626            window,
 1627            cx,
 1628        );
 1629        assert_eq!(
 1630            editor.selections.display_ranges(cx),
 1631            &[
 1632                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1633                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1634            ]
 1635        );
 1636    });
 1637
 1638    _ = editor.update(cx, |editor, window, cx| {
 1639        editor.select_to_beginning_of_line(
 1640            &SelectToBeginningOfLine {
 1641                stop_at_soft_wraps: true,
 1642                stop_at_indent: true,
 1643            },
 1644            window,
 1645            cx,
 1646        );
 1647        assert_eq!(
 1648            editor.selections.display_ranges(cx),
 1649            &[
 1650                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1651                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1652            ]
 1653        );
 1654    });
 1655
 1656    _ = editor.update(cx, |editor, window, cx| {
 1657        editor.select_to_end_of_line(
 1658            &SelectToEndOfLine {
 1659                stop_at_soft_wraps: true,
 1660            },
 1661            window,
 1662            cx,
 1663        );
 1664        assert_eq!(
 1665            editor.selections.display_ranges(cx),
 1666            &[
 1667                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1668                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1669            ]
 1670        );
 1671    });
 1672
 1673    _ = editor.update(cx, |editor, window, cx| {
 1674        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1675        assert_eq!(editor.display_text(cx), "ab\n  de");
 1676        assert_eq!(
 1677            editor.selections.display_ranges(cx),
 1678            &[
 1679                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1680                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1681            ]
 1682        );
 1683    });
 1684
 1685    _ = editor.update(cx, |editor, window, cx| {
 1686        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1687        assert_eq!(editor.display_text(cx), "\n");
 1688        assert_eq!(
 1689            editor.selections.display_ranges(cx),
 1690            &[
 1691                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1692                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1693            ]
 1694        );
 1695    });
 1696}
 1697
 1698#[gpui::test]
 1699fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1700    init_test(cx, |_| {});
 1701    let move_to_beg = MoveToBeginningOfLine {
 1702        stop_at_soft_wraps: false,
 1703        stop_at_indent: false,
 1704    };
 1705
 1706    let move_to_end = MoveToEndOfLine {
 1707        stop_at_soft_wraps: false,
 1708    };
 1709
 1710    let editor = cx.add_window(|window, cx| {
 1711        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1712        build_editor(buffer, window, cx)
 1713    });
 1714
 1715    _ = editor.update(cx, |editor, window, cx| {
 1716        editor.set_wrap_width(Some(140.0.into()), cx);
 1717
 1718        // We expect the following lines after wrapping
 1719        // ```
 1720        // thequickbrownfox
 1721        // jumpedoverthelazydo
 1722        // gs
 1723        // ```
 1724        // The final `gs` was soft-wrapped onto a new line.
 1725        assert_eq!(
 1726            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1727            editor.display_text(cx),
 1728        );
 1729
 1730        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1731        // Start the cursor at the `k` on the first line
 1732        editor.change_selections(None, window, cx, |s| {
 1733            s.select_display_ranges([
 1734                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1735            ]);
 1736        });
 1737
 1738        // Moving to the beginning of the line should put us at the beginning of the line.
 1739        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1740        assert_eq!(
 1741            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1742            editor.selections.display_ranges(cx)
 1743        );
 1744
 1745        // Moving to the end of the line should put us at the end of the line.
 1746        editor.move_to_end_of_line(&move_to_end, window, cx);
 1747        assert_eq!(
 1748            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1749            editor.selections.display_ranges(cx)
 1750        );
 1751
 1752        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1753        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1754        editor.change_selections(None, window, cx, |s| {
 1755            s.select_display_ranges([
 1756                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1757            ]);
 1758        });
 1759
 1760        // Moving to the beginning of the line should put us at the start of the second line of
 1761        // display text, i.e., the `j`.
 1762        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1763        assert_eq!(
 1764            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1765            editor.selections.display_ranges(cx)
 1766        );
 1767
 1768        // Moving to the beginning of the line again should be a no-op.
 1769        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1770        assert_eq!(
 1771            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1772            editor.selections.display_ranges(cx)
 1773        );
 1774
 1775        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1776        // next display line.
 1777        editor.move_to_end_of_line(&move_to_end, window, cx);
 1778        assert_eq!(
 1779            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1780            editor.selections.display_ranges(cx)
 1781        );
 1782
 1783        // Moving to the end of the line again should be a no-op.
 1784        editor.move_to_end_of_line(&move_to_end, window, cx);
 1785        assert_eq!(
 1786            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1787            editor.selections.display_ranges(cx)
 1788        );
 1789    });
 1790}
 1791
 1792#[gpui::test]
 1793fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1794    init_test(cx, |_| {});
 1795
 1796    let move_to_beg = MoveToBeginningOfLine {
 1797        stop_at_soft_wraps: true,
 1798        stop_at_indent: true,
 1799    };
 1800
 1801    let select_to_beg = SelectToBeginningOfLine {
 1802        stop_at_soft_wraps: true,
 1803        stop_at_indent: true,
 1804    };
 1805
 1806    let delete_to_beg = DeleteToBeginningOfLine {
 1807        stop_at_indent: true,
 1808    };
 1809
 1810    let move_to_end = MoveToEndOfLine {
 1811        stop_at_soft_wraps: false,
 1812    };
 1813
 1814    let editor = cx.add_window(|window, cx| {
 1815        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1816        build_editor(buffer, window, cx)
 1817    });
 1818
 1819    _ = editor.update(cx, |editor, window, cx| {
 1820        editor.change_selections(None, window, cx, |s| {
 1821            s.select_display_ranges([
 1822                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1823                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1824            ]);
 1825        });
 1826
 1827        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1828        // and the second cursor at the first non-whitespace character in the line.
 1829        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1830        assert_eq!(
 1831            editor.selections.display_ranges(cx),
 1832            &[
 1833                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1834                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1835            ]
 1836        );
 1837
 1838        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1839        // and should move the second cursor to the beginning of the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1850        // and should move the second cursor back to the first non-whitespace character in the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1857            ]
 1858        );
 1859
 1860        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1861        // and to the first non-whitespace character in the line for the second cursor.
 1862        editor.move_to_end_of_line(&move_to_end, window, cx);
 1863        editor.move_left(&MoveLeft, window, cx);
 1864        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1874        // and should select to the beginning of the line for the second cursor.
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883
 1884        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1885        // and should delete to the first non-whitespace character in the line for the second cursor.
 1886        editor.move_to_end_of_line(&move_to_end, window, cx);
 1887        editor.move_left(&MoveLeft, window, cx);
 1888        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1889        assert_eq!(editor.text(cx), "c\n  f");
 1890    });
 1891}
 1892
 1893#[gpui::test]
 1894fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1895    init_test(cx, |_| {});
 1896
 1897    let editor = cx.add_window(|window, cx| {
 1898        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1899        build_editor(buffer, window, cx)
 1900    });
 1901    _ = editor.update(cx, |editor, window, cx| {
 1902        editor.change_selections(None, window, cx, |s| {
 1903            s.select_display_ranges([
 1904                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1905                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1906            ])
 1907        });
 1908
 1909        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1910        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1911
 1912        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1913        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1914
 1915        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1916        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1917
 1918        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1919        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1920
 1921        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1922        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1923
 1924        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1925        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1926
 1927        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1928        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1929
 1930        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1931        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1932
 1933        editor.move_right(&MoveRight, window, cx);
 1934        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1935        assert_selection_ranges(
 1936            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1937            editor,
 1938            cx,
 1939        );
 1940
 1941        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1942        assert_selection_ranges(
 1943            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1944            editor,
 1945            cx,
 1946        );
 1947
 1948        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1949        assert_selection_ranges(
 1950            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1951            editor,
 1952            cx,
 1953        );
 1954    });
 1955}
 1956
 1957#[gpui::test]
 1958fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1959    init_test(cx, |_| {});
 1960
 1961    let editor = cx.add_window(|window, cx| {
 1962        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1963        build_editor(buffer, window, cx)
 1964    });
 1965
 1966    _ = editor.update(cx, |editor, window, cx| {
 1967        editor.set_wrap_width(Some(140.0.into()), cx);
 1968        assert_eq!(
 1969            editor.display_text(cx),
 1970            "use one::{\n    two::three::\n    four::five\n};"
 1971        );
 1972
 1973        editor.change_selections(None, window, cx, |s| {
 1974            s.select_display_ranges([
 1975                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1976            ]);
 1977        });
 1978
 1979        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1980        assert_eq!(
 1981            editor.selections.display_ranges(cx),
 1982            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1983        );
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_eq!(
 1987            editor.selections.display_ranges(cx),
 1988            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1989        );
 1990
 1991        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1992        assert_eq!(
 1993            editor.selections.display_ranges(cx),
 1994            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1995        );
 1996
 1997        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1998        assert_eq!(
 1999            editor.selections.display_ranges(cx),
 2000            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2001        );
 2002
 2003        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2004        assert_eq!(
 2005            editor.selections.display_ranges(cx),
 2006            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2007        );
 2008
 2009        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2010        assert_eq!(
 2011            editor.selections.display_ranges(cx),
 2012            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2013        );
 2014    });
 2015}
 2016
 2017#[gpui::test]
 2018async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2019    init_test(cx, |_| {});
 2020    let mut cx = EditorTestContext::new(cx).await;
 2021
 2022    let line_height = cx.editor(|editor, window, _| {
 2023        editor
 2024            .style()
 2025            .unwrap()
 2026            .text
 2027            .line_height_in_pixels(window.rem_size())
 2028    });
 2029    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2030
 2031    cx.set_state(
 2032        &r#"ˇone
 2033        two
 2034
 2035        three
 2036        fourˇ
 2037        five
 2038
 2039        six"#
 2040            .unindent(),
 2041    );
 2042
 2043    cx.update_editor(|editor, window, cx| {
 2044        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2045    });
 2046    cx.assert_editor_state(
 2047        &r#"one
 2048        two
 2049        ˇ
 2050        three
 2051        four
 2052        five
 2053        ˇ
 2054        six"#
 2055            .unindent(),
 2056    );
 2057
 2058    cx.update_editor(|editor, window, cx| {
 2059        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2060    });
 2061    cx.assert_editor_state(
 2062        &r#"one
 2063        two
 2064
 2065        three
 2066        four
 2067        five
 2068        ˇ
 2069        sixˇ"#
 2070            .unindent(),
 2071    );
 2072
 2073    cx.update_editor(|editor, window, cx| {
 2074        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2075    });
 2076    cx.assert_editor_state(
 2077        &r#"one
 2078        two
 2079
 2080        three
 2081        four
 2082        five
 2083
 2084        sixˇ"#
 2085            .unindent(),
 2086    );
 2087
 2088    cx.update_editor(|editor, window, cx| {
 2089        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2090    });
 2091    cx.assert_editor_state(
 2092        &r#"one
 2093        two
 2094
 2095        three
 2096        four
 2097        five
 2098        ˇ
 2099        six"#
 2100            .unindent(),
 2101    );
 2102
 2103    cx.update_editor(|editor, window, cx| {
 2104        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2105    });
 2106    cx.assert_editor_state(
 2107        &r#"one
 2108        two
 2109        ˇ
 2110        three
 2111        four
 2112        five
 2113
 2114        six"#
 2115            .unindent(),
 2116    );
 2117
 2118    cx.update_editor(|editor, window, cx| {
 2119        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2120    });
 2121    cx.assert_editor_state(
 2122        &r#"ˇone
 2123        two
 2124
 2125        three
 2126        four
 2127        five
 2128
 2129        six"#
 2130            .unindent(),
 2131    );
 2132}
 2133
 2134#[gpui::test]
 2135async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2136    init_test(cx, |_| {});
 2137    let mut cx = EditorTestContext::new(cx).await;
 2138    let line_height = cx.editor(|editor, window, _| {
 2139        editor
 2140            .style()
 2141            .unwrap()
 2142            .text
 2143            .line_height_in_pixels(window.rem_size())
 2144    });
 2145    let window = cx.window;
 2146    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2147
 2148    cx.set_state(
 2149        r#"ˇone
 2150        two
 2151        three
 2152        four
 2153        five
 2154        six
 2155        seven
 2156        eight
 2157        nine
 2158        ten
 2159        "#,
 2160    );
 2161
 2162    cx.update_editor(|editor, window, cx| {
 2163        assert_eq!(
 2164            editor.snapshot(window, cx).scroll_position(),
 2165            gpui::Point::new(0., 0.)
 2166        );
 2167        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2168        assert_eq!(
 2169            editor.snapshot(window, cx).scroll_position(),
 2170            gpui::Point::new(0., 3.)
 2171        );
 2172        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 6.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182
 2183        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2184        assert_eq!(
 2185            editor.snapshot(window, cx).scroll_position(),
 2186            gpui::Point::new(0., 1.)
 2187        );
 2188        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2189        assert_eq!(
 2190            editor.snapshot(window, cx).scroll_position(),
 2191            gpui::Point::new(0., 3.)
 2192        );
 2193    });
 2194}
 2195
 2196#[gpui::test]
 2197async fn test_autoscroll(cx: &mut TestAppContext) {
 2198    init_test(cx, |_| {});
 2199    let mut cx = EditorTestContext::new(cx).await;
 2200
 2201    let line_height = cx.update_editor(|editor, window, cx| {
 2202        editor.set_vertical_scroll_margin(2, cx);
 2203        editor
 2204            .style()
 2205            .unwrap()
 2206            .text
 2207            .line_height_in_pixels(window.rem_size())
 2208    });
 2209    let window = cx.window;
 2210    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2211
 2212    cx.set_state(
 2213        r#"ˇone
 2214            two
 2215            three
 2216            four
 2217            five
 2218            six
 2219            seven
 2220            eight
 2221            nine
 2222            ten
 2223        "#,
 2224    );
 2225    cx.update_editor(|editor, window, cx| {
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 0.0)
 2229        );
 2230    });
 2231
 2232    // Add a cursor below the visible area. Since both cursors cannot fit
 2233    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2234    // allows the vertical scroll margin below that cursor.
 2235    cx.update_editor(|editor, window, cx| {
 2236        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2237            selections.select_ranges([
 2238                Point::new(0, 0)..Point::new(0, 0),
 2239                Point::new(6, 0)..Point::new(6, 0),
 2240            ]);
 2241        })
 2242    });
 2243    cx.update_editor(|editor, window, cx| {
 2244        assert_eq!(
 2245            editor.snapshot(window, cx).scroll_position(),
 2246            gpui::Point::new(0., 3.0)
 2247        );
 2248    });
 2249
 2250    // Move down. The editor cursor scrolls down to track the newest cursor.
 2251    cx.update_editor(|editor, window, cx| {
 2252        editor.move_down(&Default::default(), window, cx);
 2253    });
 2254    cx.update_editor(|editor, window, cx| {
 2255        assert_eq!(
 2256            editor.snapshot(window, cx).scroll_position(),
 2257            gpui::Point::new(0., 4.0)
 2258        );
 2259    });
 2260
 2261    // Add a cursor above the visible area. Since both cursors fit on screen,
 2262    // the editor scrolls to show both.
 2263    cx.update_editor(|editor, window, cx| {
 2264        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2265            selections.select_ranges([
 2266                Point::new(1, 0)..Point::new(1, 0),
 2267                Point::new(6, 0)..Point::new(6, 0),
 2268            ]);
 2269        })
 2270    });
 2271    cx.update_editor(|editor, window, cx| {
 2272        assert_eq!(
 2273            editor.snapshot(window, cx).scroll_position(),
 2274            gpui::Point::new(0., 1.0)
 2275        );
 2276    });
 2277}
 2278
 2279#[gpui::test]
 2280async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2281    init_test(cx, |_| {});
 2282    let mut cx = EditorTestContext::new(cx).await;
 2283
 2284    let line_height = cx.editor(|editor, window, _cx| {
 2285        editor
 2286            .style()
 2287            .unwrap()
 2288            .text
 2289            .line_height_in_pixels(window.rem_size())
 2290    });
 2291    let window = cx.window;
 2292    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2293    cx.set_state(
 2294        &r#"
 2295        ˇone
 2296        two
 2297        threeˇ
 2298        four
 2299        five
 2300        six
 2301        seven
 2302        eight
 2303        nine
 2304        ten
 2305        "#
 2306        .unindent(),
 2307    );
 2308
 2309    cx.update_editor(|editor, window, cx| {
 2310        editor.move_page_down(&MovePageDown::default(), window, cx)
 2311    });
 2312    cx.assert_editor_state(
 2313        &r#"
 2314        one
 2315        two
 2316        three
 2317        ˇfour
 2318        five
 2319        sixˇ
 2320        seven
 2321        eight
 2322        nine
 2323        ten
 2324        "#
 2325        .unindent(),
 2326    );
 2327
 2328    cx.update_editor(|editor, window, cx| {
 2329        editor.move_page_down(&MovePageDown::default(), window, cx)
 2330    });
 2331    cx.assert_editor_state(
 2332        &r#"
 2333        one
 2334        two
 2335        three
 2336        four
 2337        five
 2338        six
 2339        ˇseven
 2340        eight
 2341        nineˇ
 2342        ten
 2343        "#
 2344        .unindent(),
 2345    );
 2346
 2347    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2348    cx.assert_editor_state(
 2349        &r#"
 2350        one
 2351        two
 2352        three
 2353        ˇfour
 2354        five
 2355        sixˇ
 2356        seven
 2357        eight
 2358        nine
 2359        ten
 2360        "#
 2361        .unindent(),
 2362    );
 2363
 2364    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2365    cx.assert_editor_state(
 2366        &r#"
 2367        ˇone
 2368        two
 2369        threeˇ
 2370        four
 2371        five
 2372        six
 2373        seven
 2374        eight
 2375        nine
 2376        ten
 2377        "#
 2378        .unindent(),
 2379    );
 2380
 2381    // Test select collapsing
 2382    cx.update_editor(|editor, window, cx| {
 2383        editor.move_page_down(&MovePageDown::default(), window, cx);
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386    });
 2387    cx.assert_editor_state(
 2388        &r#"
 2389        one
 2390        two
 2391        three
 2392        four
 2393        five
 2394        six
 2395        seven
 2396        eight
 2397        nine
 2398        ˇten
 2399        ˇ"#
 2400        .unindent(),
 2401    );
 2402}
 2403
 2404#[gpui::test]
 2405async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2406    init_test(cx, |_| {});
 2407    let mut cx = EditorTestContext::new(cx).await;
 2408    cx.set_state("one «two threeˇ» four");
 2409    cx.update_editor(|editor, window, cx| {
 2410        editor.delete_to_beginning_of_line(
 2411            &DeleteToBeginningOfLine {
 2412                stop_at_indent: false,
 2413            },
 2414            window,
 2415            cx,
 2416        );
 2417        assert_eq!(editor.text(cx), " four");
 2418    });
 2419}
 2420
 2421#[gpui::test]
 2422fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2423    init_test(cx, |_| {});
 2424
 2425    let editor = cx.add_window(|window, cx| {
 2426        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2427        build_editor(buffer.clone(), window, cx)
 2428    });
 2429
 2430    _ = editor.update(cx, |editor, window, cx| {
 2431        editor.change_selections(None, window, cx, |s| {
 2432            s.select_display_ranges([
 2433                // an empty selection - the preceding word fragment is deleted
 2434                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2435                // characters selected - they are deleted
 2436                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2437            ])
 2438        });
 2439        editor.delete_to_previous_word_start(
 2440            &DeleteToPreviousWordStart {
 2441                ignore_newlines: false,
 2442            },
 2443            window,
 2444            cx,
 2445        );
 2446        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2447    });
 2448
 2449    _ = editor.update(cx, |editor, window, cx| {
 2450        editor.change_selections(None, window, cx, |s| {
 2451            s.select_display_ranges([
 2452                // an empty selection - the following word fragment is deleted
 2453                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2454                // characters selected - they are deleted
 2455                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2456            ])
 2457        });
 2458        editor.delete_to_next_word_end(
 2459            &DeleteToNextWordEnd {
 2460                ignore_newlines: false,
 2461            },
 2462            window,
 2463            cx,
 2464        );
 2465        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2466    });
 2467}
 2468
 2469#[gpui::test]
 2470fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2471    init_test(cx, |_| {});
 2472
 2473    let editor = cx.add_window(|window, cx| {
 2474        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2475        build_editor(buffer.clone(), window, cx)
 2476    });
 2477    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2478        ignore_newlines: false,
 2479    };
 2480    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2481        ignore_newlines: true,
 2482    };
 2483
 2484    _ = editor.update(cx, |editor, window, cx| {
 2485        editor.change_selections(None, window, cx, |s| {
 2486            s.select_display_ranges([
 2487                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2488            ])
 2489        });
 2490        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2491        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2492        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2493        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2494        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2495        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2496        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2497        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2498        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2499        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2502    });
 2503}
 2504
 2505#[gpui::test]
 2506fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2507    init_test(cx, |_| {});
 2508
 2509    let editor = cx.add_window(|window, cx| {
 2510        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2511        build_editor(buffer.clone(), window, cx)
 2512    });
 2513    let del_to_next_word_end = DeleteToNextWordEnd {
 2514        ignore_newlines: false,
 2515    };
 2516    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2517        ignore_newlines: true,
 2518    };
 2519
 2520    _ = editor.update(cx, |editor, window, cx| {
 2521        editor.change_selections(None, window, cx, |s| {
 2522            s.select_display_ranges([
 2523                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2524            ])
 2525        });
 2526        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2527        assert_eq!(
 2528            editor.buffer.read(cx).read(cx).text(),
 2529            "one\n   two\nthree\n   four"
 2530        );
 2531        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2532        assert_eq!(
 2533            editor.buffer.read(cx).read(cx).text(),
 2534            "\n   two\nthree\n   four"
 2535        );
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2543        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2544        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2545        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2547    });
 2548}
 2549
 2550#[gpui::test]
 2551fn test_newline(cx: &mut TestAppContext) {
 2552    init_test(cx, |_| {});
 2553
 2554    let editor = cx.add_window(|window, cx| {
 2555        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2556        build_editor(buffer.clone(), window, cx)
 2557    });
 2558
 2559    _ = editor.update(cx, |editor, window, cx| {
 2560        editor.change_selections(None, window, cx, |s| {
 2561            s.select_display_ranges([
 2562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2563                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2564                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2565            ])
 2566        });
 2567
 2568        editor.newline(&Newline, window, cx);
 2569        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2570    });
 2571}
 2572
 2573#[gpui::test]
 2574fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2575    init_test(cx, |_| {});
 2576
 2577    let editor = cx.add_window(|window, cx| {
 2578        let buffer = MultiBuffer::build_simple(
 2579            "
 2580                a
 2581                b(
 2582                    X
 2583                )
 2584                c(
 2585                    X
 2586                )
 2587            "
 2588            .unindent()
 2589            .as_str(),
 2590            cx,
 2591        );
 2592        let mut editor = build_editor(buffer.clone(), window, cx);
 2593        editor.change_selections(None, window, cx, |s| {
 2594            s.select_ranges([
 2595                Point::new(2, 4)..Point::new(2, 5),
 2596                Point::new(5, 4)..Point::new(5, 5),
 2597            ])
 2598        });
 2599        editor
 2600    });
 2601
 2602    _ = editor.update(cx, |editor, window, cx| {
 2603        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2604        editor.buffer.update(cx, |buffer, cx| {
 2605            buffer.edit(
 2606                [
 2607                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2608                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2609                ],
 2610                None,
 2611                cx,
 2612            );
 2613            assert_eq!(
 2614                buffer.read(cx).text(),
 2615                "
 2616                    a
 2617                    b()
 2618                    c()
 2619                "
 2620                .unindent()
 2621            );
 2622        });
 2623        assert_eq!(
 2624            editor.selections.ranges(cx),
 2625            &[
 2626                Point::new(1, 2)..Point::new(1, 2),
 2627                Point::new(2, 2)..Point::new(2, 2),
 2628            ],
 2629        );
 2630
 2631        editor.newline(&Newline, window, cx);
 2632        assert_eq!(
 2633            editor.text(cx),
 2634            "
 2635                a
 2636                b(
 2637                )
 2638                c(
 2639                )
 2640            "
 2641            .unindent()
 2642        );
 2643
 2644        // The selections are moved after the inserted newlines
 2645        assert_eq!(
 2646            editor.selections.ranges(cx),
 2647            &[
 2648                Point::new(2, 0)..Point::new(2, 0),
 2649                Point::new(4, 0)..Point::new(4, 0),
 2650            ],
 2651        );
 2652    });
 2653}
 2654
 2655#[gpui::test]
 2656async fn test_newline_above(cx: &mut TestAppContext) {
 2657    init_test(cx, |settings| {
 2658        settings.defaults.tab_size = NonZeroU32::new(4)
 2659    });
 2660
 2661    let language = Arc::new(
 2662        Language::new(
 2663            LanguageConfig::default(),
 2664            Some(tree_sitter_rust::LANGUAGE.into()),
 2665        )
 2666        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2667        .unwrap(),
 2668    );
 2669
 2670    let mut cx = EditorTestContext::new(cx).await;
 2671    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2672    cx.set_state(indoc! {"
 2673        const a: ˇA = (
 2674 2675                «const_functionˇ»(ˇ),
 2676                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2677 2678        ˇ);ˇ
 2679    "});
 2680
 2681    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2682    cx.assert_editor_state(indoc! {"
 2683        ˇ
 2684        const a: A = (
 2685            ˇ
 2686            (
 2687                ˇ
 2688                ˇ
 2689                const_function(),
 2690                ˇ
 2691                ˇ
 2692                ˇ
 2693                ˇ
 2694                something_else,
 2695                ˇ
 2696            )
 2697            ˇ
 2698            ˇ
 2699        );
 2700    "});
 2701}
 2702
 2703#[gpui::test]
 2704async fn test_newline_below(cx: &mut TestAppContext) {
 2705    init_test(cx, |settings| {
 2706        settings.defaults.tab_size = NonZeroU32::new(4)
 2707    });
 2708
 2709    let language = Arc::new(
 2710        Language::new(
 2711            LanguageConfig::default(),
 2712            Some(tree_sitter_rust::LANGUAGE.into()),
 2713        )
 2714        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2715        .unwrap(),
 2716    );
 2717
 2718    let mut cx = EditorTestContext::new(cx).await;
 2719    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2720    cx.set_state(indoc! {"
 2721        const a: ˇA = (
 2722 2723                «const_functionˇ»(ˇ),
 2724                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2725 2726        ˇ);ˇ
 2727    "});
 2728
 2729    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2730    cx.assert_editor_state(indoc! {"
 2731        const a: A = (
 2732            ˇ
 2733            (
 2734                ˇ
 2735                const_function(),
 2736                ˇ
 2737                ˇ
 2738                something_else,
 2739                ˇ
 2740                ˇ
 2741                ˇ
 2742                ˇ
 2743            )
 2744            ˇ
 2745        );
 2746        ˇ
 2747        ˇ
 2748    "});
 2749}
 2750
 2751#[gpui::test]
 2752async fn test_newline_comments(cx: &mut TestAppContext) {
 2753    init_test(cx, |settings| {
 2754        settings.defaults.tab_size = NonZeroU32::new(4)
 2755    });
 2756
 2757    let language = Arc::new(Language::new(
 2758        LanguageConfig {
 2759            line_comments: vec!["// ".into()],
 2760            ..LanguageConfig::default()
 2761        },
 2762        None,
 2763    ));
 2764    {
 2765        let mut cx = EditorTestContext::new(cx).await;
 2766        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2767        cx.set_state(indoc! {"
 2768        // Fooˇ
 2769    "});
 2770
 2771        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2772        cx.assert_editor_state(indoc! {"
 2773        // Foo
 2774        // ˇ
 2775    "});
 2776        // Ensure that we add comment prefix when existing line contains space
 2777        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2778        cx.assert_editor_state(
 2779            indoc! {"
 2780        // Foo
 2781        //s
 2782        // ˇ
 2783    "}
 2784            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2785            .as_str(),
 2786        );
 2787        // Ensure that we add comment prefix when existing line does not contain space
 2788        cx.set_state(indoc! {"
 2789        // Foo
 2790        //ˇ
 2791    "});
 2792        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2793        cx.assert_editor_state(indoc! {"
 2794        // Foo
 2795        //
 2796        // ˇ
 2797    "});
 2798        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2799        cx.set_state(indoc! {"
 2800        ˇ// Foo
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804
 2805        ˇ// Foo
 2806    "});
 2807    }
 2808    // Ensure that comment continuations can be disabled.
 2809    update_test_language_settings(cx, |settings| {
 2810        settings.defaults.extend_comment_on_newline = Some(false);
 2811    });
 2812    let mut cx = EditorTestContext::new(cx).await;
 2813    cx.set_state(indoc! {"
 2814        // Fooˇ
 2815    "});
 2816    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2817    cx.assert_editor_state(indoc! {"
 2818        // Foo
 2819        ˇ
 2820    "});
 2821}
 2822
 2823#[gpui::test]
 2824async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2825    init_test(cx, |settings| {
 2826        settings.defaults.tab_size = NonZeroU32::new(4)
 2827    });
 2828
 2829    let language = Arc::new(Language::new(
 2830        LanguageConfig {
 2831            line_comments: vec!["// ".into(), "/// ".into()],
 2832            ..LanguageConfig::default()
 2833        },
 2834        None,
 2835    ));
 2836    {
 2837        let mut cx = EditorTestContext::new(cx).await;
 2838        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2839        cx.set_state(indoc! {"
 2840        //ˇ
 2841    "});
 2842        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2843        cx.assert_editor_state(indoc! {"
 2844        //
 2845        // ˇ
 2846    "});
 2847
 2848        cx.set_state(indoc! {"
 2849        ///ˇ
 2850    "});
 2851        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2852        cx.assert_editor_state(indoc! {"
 2853        ///
 2854        /// ˇ
 2855    "});
 2856    }
 2857}
 2858
 2859#[gpui::test]
 2860async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2861    init_test(cx, |settings| {
 2862        settings.defaults.tab_size = NonZeroU32::new(4)
 2863    });
 2864
 2865    let language = Arc::new(Language::new(
 2866        LanguageConfig {
 2867            documentation: Some(language::DocumentationConfig {
 2868                start: "/**".into(),
 2869                end: "*/".into(),
 2870                prefix: "* ".into(),
 2871                tab_size: NonZeroU32::new(1).unwrap(),
 2872            }),
 2873            ..LanguageConfig::default()
 2874        },
 2875        None,
 2876    ));
 2877    {
 2878        let mut cx = EditorTestContext::new(cx).await;
 2879        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2880        cx.set_state(indoc! {"
 2881        /**ˇ
 2882    "});
 2883
 2884        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2885        cx.assert_editor_state(indoc! {"
 2886        /**
 2887         * ˇ
 2888    "});
 2889        // Ensure that if cursor is before the comment start,
 2890        // we do not actually insert a comment prefix.
 2891        cx.set_state(indoc! {"
 2892        ˇ/**
 2893    "});
 2894        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2895        cx.assert_editor_state(indoc! {"
 2896
 2897        ˇ/**
 2898    "});
 2899        // Ensure that if cursor is between it doesn't add comment prefix.
 2900        cx.set_state(indoc! {"
 2901        /*ˇ*
 2902    "});
 2903        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2904        cx.assert_editor_state(indoc! {"
 2905        /*
 2906        ˇ*
 2907    "});
 2908        // Ensure that if suffix exists on same line after cursor it adds new line.
 2909        cx.set_state(indoc! {"
 2910        /**ˇ*/
 2911    "});
 2912        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2913        cx.assert_editor_state(indoc! {"
 2914        /**
 2915         * ˇ
 2916         */
 2917    "});
 2918        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2919        cx.set_state(indoc! {"
 2920        /**ˇ */
 2921    "});
 2922        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2923        cx.assert_editor_state(indoc! {"
 2924        /**
 2925         * ˇ
 2926         */
 2927    "});
 2928        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2929        cx.set_state(indoc! {"
 2930        /** ˇ*/
 2931    "});
 2932        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2933        cx.assert_editor_state(
 2934            indoc! {"
 2935        /**s
 2936         * ˇ
 2937         */
 2938    "}
 2939            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2940            .as_str(),
 2941        );
 2942        // Ensure that delimiter space is preserved when newline on already
 2943        // spaced delimiter.
 2944        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2945        cx.assert_editor_state(
 2946            indoc! {"
 2947        /**s
 2948         *s
 2949         * ˇ
 2950         */
 2951    "}
 2952            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2953            .as_str(),
 2954        );
 2955        // Ensure that delimiter space is preserved when space is not
 2956        // on existing delimiter.
 2957        cx.set_state(indoc! {"
 2958        /**
 2959 2960         */
 2961    "});
 2962        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2963        cx.assert_editor_state(indoc! {"
 2964        /**
 2965         *
 2966         * ˇ
 2967         */
 2968    "});
 2969        // Ensure that if suffix exists on same line after cursor it
 2970        // doesn't add extra new line if prefix is not on same line.
 2971        cx.set_state(indoc! {"
 2972        /**
 2973        ˇ*/
 2974    "});
 2975        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2976        cx.assert_editor_state(indoc! {"
 2977        /**
 2978
 2979        ˇ*/
 2980    "});
 2981        // Ensure that it detects suffix after existing prefix.
 2982        cx.set_state(indoc! {"
 2983        /**ˇ/
 2984    "});
 2985        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2986        cx.assert_editor_state(indoc! {"
 2987        /**
 2988        ˇ/
 2989    "});
 2990        // Ensure that if suffix exists on same line before
 2991        // cursor it does not add comment prefix.
 2992        cx.set_state(indoc! {"
 2993        /** */ˇ
 2994    "});
 2995        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2996        cx.assert_editor_state(indoc! {"
 2997        /** */
 2998        ˇ
 2999    "});
 3000        // Ensure that if suffix exists on same line before
 3001        // cursor it does not add comment prefix.
 3002        cx.set_state(indoc! {"
 3003        /**
 3004         *
 3005         */ˇ
 3006    "});
 3007        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3008        cx.assert_editor_state(indoc! {"
 3009        /**
 3010         *
 3011         */
 3012         ˇ
 3013    "});
 3014
 3015        // Ensure that inline comment followed by code
 3016        // doesn't add comment prefix on newline
 3017        cx.set_state(indoc! {"
 3018        /** */ textˇ
 3019    "});
 3020        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3021        cx.assert_editor_state(indoc! {"
 3022        /** */ text
 3023        ˇ
 3024    "});
 3025
 3026        // Ensure that text after comment end tag
 3027        // doesn't add comment prefix on newline
 3028        cx.set_state(indoc! {"
 3029        /**
 3030         *
 3031         */ˇtext
 3032    "});
 3033        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3034        cx.assert_editor_state(indoc! {"
 3035        /**
 3036         *
 3037         */
 3038         ˇtext
 3039    "});
 3040    }
 3041    // Ensure that comment continuations can be disabled.
 3042    update_test_language_settings(cx, |settings| {
 3043        settings.defaults.extend_comment_on_newline = Some(false);
 3044    });
 3045    let mut cx = EditorTestContext::new(cx).await;
 3046    cx.set_state(indoc! {"
 3047        /**ˇ
 3048    "});
 3049    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050    cx.assert_editor_state(indoc! {"
 3051        /**
 3052        ˇ
 3053    "});
 3054}
 3055
 3056#[gpui::test]
 3057fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3058    init_test(cx, |_| {});
 3059
 3060    let editor = cx.add_window(|window, cx| {
 3061        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3062        let mut editor = build_editor(buffer.clone(), window, cx);
 3063        editor.change_selections(None, window, cx, |s| {
 3064            s.select_ranges([3..4, 11..12, 19..20])
 3065        });
 3066        editor
 3067    });
 3068
 3069    _ = editor.update(cx, |editor, window, cx| {
 3070        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3071        editor.buffer.update(cx, |buffer, cx| {
 3072            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3073            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3074        });
 3075        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3076
 3077        editor.insert("Z", window, cx);
 3078        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3079
 3080        // The selections are moved after the inserted characters
 3081        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3082    });
 3083}
 3084
 3085#[gpui::test]
 3086async fn test_tab(cx: &mut TestAppContext) {
 3087    init_test(cx, |settings| {
 3088        settings.defaults.tab_size = NonZeroU32::new(3)
 3089    });
 3090
 3091    let mut cx = EditorTestContext::new(cx).await;
 3092    cx.set_state(indoc! {"
 3093        ˇabˇc
 3094        ˇ🏀ˇ🏀ˇefg
 3095 3096    "});
 3097    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3098    cx.assert_editor_state(indoc! {"
 3099           ˇab ˇc
 3100           ˇ🏀  ˇ🏀  ˇefg
 3101        d  ˇ
 3102    "});
 3103
 3104    cx.set_state(indoc! {"
 3105        a
 3106        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3107    "});
 3108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3109    cx.assert_editor_state(indoc! {"
 3110        a
 3111           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3112    "});
 3113}
 3114
 3115#[gpui::test]
 3116async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3117    init_test(cx, |_| {});
 3118
 3119    let mut cx = EditorTestContext::new(cx).await;
 3120    let language = Arc::new(
 3121        Language::new(
 3122            LanguageConfig::default(),
 3123            Some(tree_sitter_rust::LANGUAGE.into()),
 3124        )
 3125        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3126        .unwrap(),
 3127    );
 3128    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3129
 3130    // test when all cursors are not at suggested indent
 3131    // then simply move to their suggested indent location
 3132    cx.set_state(indoc! {"
 3133        const a: B = (
 3134            c(
 3135        ˇ
 3136        ˇ    )
 3137        );
 3138    "});
 3139    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3140    cx.assert_editor_state(indoc! {"
 3141        const a: B = (
 3142            c(
 3143                ˇ
 3144            ˇ)
 3145        );
 3146    "});
 3147
 3148    // test cursor already at suggested indent not moving when
 3149    // other cursors are yet to reach their suggested indents
 3150    cx.set_state(indoc! {"
 3151        ˇ
 3152        const a: B = (
 3153            c(
 3154                d(
 3155        ˇ
 3156                )
 3157        ˇ
 3158        ˇ    )
 3159        );
 3160    "});
 3161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3162    cx.assert_editor_state(indoc! {"
 3163        ˇ
 3164        const a: B = (
 3165            c(
 3166                d(
 3167                    ˇ
 3168                )
 3169                ˇ
 3170            ˇ)
 3171        );
 3172    "});
 3173    // test when all cursors are at suggested indent then tab is inserted
 3174    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3175    cx.assert_editor_state(indoc! {"
 3176            ˇ
 3177        const a: B = (
 3178            c(
 3179                d(
 3180                        ˇ
 3181                )
 3182                    ˇ
 3183                ˇ)
 3184        );
 3185    "});
 3186
 3187    // test when current indent is less than suggested indent,
 3188    // we adjust line to match suggested indent and move cursor to it
 3189    //
 3190    // when no other cursor is at word boundary, all of them should move
 3191    cx.set_state(indoc! {"
 3192        const a: B = (
 3193            c(
 3194                d(
 3195        ˇ
 3196        ˇ   )
 3197        ˇ   )
 3198        );
 3199    "});
 3200    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3201    cx.assert_editor_state(indoc! {"
 3202        const a: B = (
 3203            c(
 3204                d(
 3205                    ˇ
 3206                ˇ)
 3207            ˇ)
 3208        );
 3209    "});
 3210
 3211    // test when current indent is less than suggested indent,
 3212    // we adjust line to match suggested indent and move cursor to it
 3213    //
 3214    // when some other cursor is at word boundary, it should not move
 3215    cx.set_state(indoc! {"
 3216        const a: B = (
 3217            c(
 3218                d(
 3219        ˇ
 3220        ˇ   )
 3221           ˇ)
 3222        );
 3223    "});
 3224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226        const a: B = (
 3227            c(
 3228                d(
 3229                    ˇ
 3230                ˇ)
 3231            ˇ)
 3232        );
 3233    "});
 3234
 3235    // test when current indent is more than suggested indent,
 3236    // we just move cursor to current indent instead of suggested indent
 3237    //
 3238    // when no other cursor is at word boundary, all of them should move
 3239    cx.set_state(indoc! {"
 3240        const a: B = (
 3241            c(
 3242                d(
 3243        ˇ
 3244        ˇ                )
 3245        ˇ   )
 3246        );
 3247    "});
 3248    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3249    cx.assert_editor_state(indoc! {"
 3250        const a: B = (
 3251            c(
 3252                d(
 3253                    ˇ
 3254                        ˇ)
 3255            ˇ)
 3256        );
 3257    "});
 3258    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3259    cx.assert_editor_state(indoc! {"
 3260        const a: B = (
 3261            c(
 3262                d(
 3263                        ˇ
 3264                            ˇ)
 3265                ˇ)
 3266        );
 3267    "});
 3268
 3269    // test when current indent is more than suggested indent,
 3270    // we just move cursor to current indent instead of suggested indent
 3271    //
 3272    // when some other cursor is at word boundary, it doesn't move
 3273    cx.set_state(indoc! {"
 3274        const a: B = (
 3275            c(
 3276                d(
 3277        ˇ
 3278        ˇ                )
 3279            ˇ)
 3280        );
 3281    "});
 3282    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3283    cx.assert_editor_state(indoc! {"
 3284        const a: B = (
 3285            c(
 3286                d(
 3287                    ˇ
 3288                        ˇ)
 3289            ˇ)
 3290        );
 3291    "});
 3292
 3293    // handle auto-indent when there are multiple cursors on the same line
 3294    cx.set_state(indoc! {"
 3295        const a: B = (
 3296            c(
 3297        ˇ    ˇ
 3298        ˇ    )
 3299        );
 3300    "});
 3301    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3302    cx.assert_editor_state(indoc! {"
 3303        const a: B = (
 3304            c(
 3305                ˇ
 3306            ˇ)
 3307        );
 3308    "});
 3309}
 3310
 3311#[gpui::test]
 3312async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3313    init_test(cx, |settings| {
 3314        settings.defaults.tab_size = NonZeroU32::new(3)
 3315    });
 3316
 3317    let mut cx = EditorTestContext::new(cx).await;
 3318    cx.set_state(indoc! {"
 3319         ˇ
 3320        \t ˇ
 3321        \t  ˇ
 3322        \t   ˇ
 3323         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3324    "});
 3325
 3326    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3327    cx.assert_editor_state(indoc! {"
 3328           ˇ
 3329        \t   ˇ
 3330        \t   ˇ
 3331        \t      ˇ
 3332         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3333    "});
 3334}
 3335
 3336#[gpui::test]
 3337async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3338    init_test(cx, |settings| {
 3339        settings.defaults.tab_size = NonZeroU32::new(4)
 3340    });
 3341
 3342    let language = Arc::new(
 3343        Language::new(
 3344            LanguageConfig::default(),
 3345            Some(tree_sitter_rust::LANGUAGE.into()),
 3346        )
 3347        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3348        .unwrap(),
 3349    );
 3350
 3351    let mut cx = EditorTestContext::new(cx).await;
 3352    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3353    cx.set_state(indoc! {"
 3354        fn a() {
 3355            if b {
 3356        \t ˇc
 3357            }
 3358        }
 3359    "});
 3360
 3361    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3362    cx.assert_editor_state(indoc! {"
 3363        fn a() {
 3364            if b {
 3365                ˇc
 3366            }
 3367        }
 3368    "});
 3369}
 3370
 3371#[gpui::test]
 3372async fn test_indent_outdent(cx: &mut TestAppContext) {
 3373    init_test(cx, |settings| {
 3374        settings.defaults.tab_size = NonZeroU32::new(4);
 3375    });
 3376
 3377    let mut cx = EditorTestContext::new(cx).await;
 3378
 3379    cx.set_state(indoc! {"
 3380          «oneˇ» «twoˇ»
 3381        three
 3382         four
 3383    "});
 3384    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3385    cx.assert_editor_state(indoc! {"
 3386            «oneˇ» «twoˇ»
 3387        three
 3388         four
 3389    "});
 3390
 3391    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3392    cx.assert_editor_state(indoc! {"
 3393        «oneˇ» «twoˇ»
 3394        three
 3395         four
 3396    "});
 3397
 3398    // select across line ending
 3399    cx.set_state(indoc! {"
 3400        one two
 3401        t«hree
 3402        ˇ» four
 3403    "});
 3404    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3405    cx.assert_editor_state(indoc! {"
 3406        one two
 3407            t«hree
 3408        ˇ» four
 3409    "});
 3410
 3411    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413        one two
 3414        t«hree
 3415        ˇ» four
 3416    "});
 3417
 3418    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3419    cx.set_state(indoc! {"
 3420        one two
 3421        ˇthree
 3422            four
 3423    "});
 3424    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3425    cx.assert_editor_state(indoc! {"
 3426        one two
 3427            ˇthree
 3428            four
 3429    "});
 3430
 3431    cx.set_state(indoc! {"
 3432        one two
 3433        ˇ    three
 3434            four
 3435    "});
 3436    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3437    cx.assert_editor_state(indoc! {"
 3438        one two
 3439        ˇthree
 3440            four
 3441    "});
 3442}
 3443
 3444#[gpui::test]
 3445async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3446    init_test(cx, |settings| {
 3447        settings.defaults.hard_tabs = Some(true);
 3448    });
 3449
 3450    let mut cx = EditorTestContext::new(cx).await;
 3451
 3452    // select two ranges on one line
 3453    cx.set_state(indoc! {"
 3454        «oneˇ» «twoˇ»
 3455        three
 3456        four
 3457    "});
 3458    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3459    cx.assert_editor_state(indoc! {"
 3460        \t«oneˇ» «twoˇ»
 3461        three
 3462        four
 3463    "});
 3464    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3465    cx.assert_editor_state(indoc! {"
 3466        \t\t«oneˇ» «twoˇ»
 3467        three
 3468        four
 3469    "});
 3470    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3471    cx.assert_editor_state(indoc! {"
 3472        \t«oneˇ» «twoˇ»
 3473        three
 3474        four
 3475    "});
 3476    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3477    cx.assert_editor_state(indoc! {"
 3478        «oneˇ» «twoˇ»
 3479        three
 3480        four
 3481    "});
 3482
 3483    // select across a line ending
 3484    cx.set_state(indoc! {"
 3485        one two
 3486        t«hree
 3487        ˇ»four
 3488    "});
 3489    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3490    cx.assert_editor_state(indoc! {"
 3491        one two
 3492        \tt«hree
 3493        ˇ»four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498        \t\tt«hree
 3499        ˇ»four
 3500    "});
 3501    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3502    cx.assert_editor_state(indoc! {"
 3503        one two
 3504        \tt«hree
 3505        ˇ»four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        t«hree
 3511        ˇ»four
 3512    "});
 3513
 3514    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3515    cx.set_state(indoc! {"
 3516        one two
 3517        ˇthree
 3518        four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523        ˇthree
 3524        four
 3525    "});
 3526    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3527    cx.assert_editor_state(indoc! {"
 3528        one two
 3529        \tˇthree
 3530        four
 3531    "});
 3532    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3533    cx.assert_editor_state(indoc! {"
 3534        one two
 3535        ˇthree
 3536        four
 3537    "});
 3538}
 3539
 3540#[gpui::test]
 3541fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3542    init_test(cx, |settings| {
 3543        settings.languages.extend([
 3544            (
 3545                "TOML".into(),
 3546                LanguageSettingsContent {
 3547                    tab_size: NonZeroU32::new(2),
 3548                    ..Default::default()
 3549                },
 3550            ),
 3551            (
 3552                "Rust".into(),
 3553                LanguageSettingsContent {
 3554                    tab_size: NonZeroU32::new(4),
 3555                    ..Default::default()
 3556                },
 3557            ),
 3558        ]);
 3559    });
 3560
 3561    let toml_language = Arc::new(Language::new(
 3562        LanguageConfig {
 3563            name: "TOML".into(),
 3564            ..Default::default()
 3565        },
 3566        None,
 3567    ));
 3568    let rust_language = Arc::new(Language::new(
 3569        LanguageConfig {
 3570            name: "Rust".into(),
 3571            ..Default::default()
 3572        },
 3573        None,
 3574    ));
 3575
 3576    let toml_buffer =
 3577        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3578    let rust_buffer =
 3579        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3580    let multibuffer = cx.new(|cx| {
 3581        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3582        multibuffer.push_excerpts(
 3583            toml_buffer.clone(),
 3584            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3585            cx,
 3586        );
 3587        multibuffer.push_excerpts(
 3588            rust_buffer.clone(),
 3589            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3590            cx,
 3591        );
 3592        multibuffer
 3593    });
 3594
 3595    cx.add_window(|window, cx| {
 3596        let mut editor = build_editor(multibuffer, window, cx);
 3597
 3598        assert_eq!(
 3599            editor.text(cx),
 3600            indoc! {"
 3601                a = 1
 3602                b = 2
 3603
 3604                const c: usize = 3;
 3605            "}
 3606        );
 3607
 3608        select_ranges(
 3609            &mut editor,
 3610            indoc! {"
 3611                «aˇ» = 1
 3612                b = 2
 3613
 3614                «const c:ˇ» usize = 3;
 3615            "},
 3616            window,
 3617            cx,
 3618        );
 3619
 3620        editor.tab(&Tab, window, cx);
 3621        assert_text_with_selections(
 3622            &mut editor,
 3623            indoc! {"
 3624                  «aˇ» = 1
 3625                b = 2
 3626
 3627                    «const c:ˇ» usize = 3;
 3628            "},
 3629            cx,
 3630        );
 3631        editor.backtab(&Backtab, window, cx);
 3632        assert_text_with_selections(
 3633            &mut editor,
 3634            indoc! {"
 3635                «aˇ» = 1
 3636                b = 2
 3637
 3638                «const c:ˇ» usize = 3;
 3639            "},
 3640            cx,
 3641        );
 3642
 3643        editor
 3644    });
 3645}
 3646
 3647#[gpui::test]
 3648async fn test_backspace(cx: &mut TestAppContext) {
 3649    init_test(cx, |_| {});
 3650
 3651    let mut cx = EditorTestContext::new(cx).await;
 3652
 3653    // Basic backspace
 3654    cx.set_state(indoc! {"
 3655        onˇe two three
 3656        fou«rˇ» five six
 3657        seven «ˇeight nine
 3658        »ten
 3659    "});
 3660    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3661    cx.assert_editor_state(indoc! {"
 3662        oˇe two three
 3663        fouˇ five six
 3664        seven ˇten
 3665    "});
 3666
 3667    // Test backspace inside and around indents
 3668    cx.set_state(indoc! {"
 3669        zero
 3670            ˇone
 3671                ˇtwo
 3672            ˇ ˇ ˇ  three
 3673        ˇ  ˇ  four
 3674    "});
 3675    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3676    cx.assert_editor_state(indoc! {"
 3677        zero
 3678        ˇone
 3679            ˇtwo
 3680        ˇ  threeˇ  four
 3681    "});
 3682}
 3683
 3684#[gpui::test]
 3685async fn test_delete(cx: &mut TestAppContext) {
 3686    init_test(cx, |_| {});
 3687
 3688    let mut cx = EditorTestContext::new(cx).await;
 3689    cx.set_state(indoc! {"
 3690        onˇe two three
 3691        fou«rˇ» five six
 3692        seven «ˇeight nine
 3693        »ten
 3694    "});
 3695    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3696    cx.assert_editor_state(indoc! {"
 3697        onˇ two three
 3698        fouˇ five six
 3699        seven ˇten
 3700    "});
 3701}
 3702
 3703#[gpui::test]
 3704fn test_delete_line(cx: &mut TestAppContext) {
 3705    init_test(cx, |_| {});
 3706
 3707    let editor = cx.add_window(|window, cx| {
 3708        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3709        build_editor(buffer, window, cx)
 3710    });
 3711    _ = editor.update(cx, |editor, window, cx| {
 3712        editor.change_selections(None, window, cx, |s| {
 3713            s.select_display_ranges([
 3714                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3715                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3716                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3717            ])
 3718        });
 3719        editor.delete_line(&DeleteLine, window, cx);
 3720        assert_eq!(editor.display_text(cx), "ghi");
 3721        assert_eq!(
 3722            editor.selections.display_ranges(cx),
 3723            vec![
 3724                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3725                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3726            ]
 3727        );
 3728    });
 3729
 3730    let editor = cx.add_window(|window, cx| {
 3731        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3732        build_editor(buffer, window, cx)
 3733    });
 3734    _ = editor.update(cx, |editor, window, cx| {
 3735        editor.change_selections(None, window, cx, |s| {
 3736            s.select_display_ranges([
 3737                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3738            ])
 3739        });
 3740        editor.delete_line(&DeleteLine, window, cx);
 3741        assert_eq!(editor.display_text(cx), "ghi\n");
 3742        assert_eq!(
 3743            editor.selections.display_ranges(cx),
 3744            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3745        );
 3746    });
 3747}
 3748
 3749#[gpui::test]
 3750fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3751    init_test(cx, |_| {});
 3752
 3753    cx.add_window(|window, cx| {
 3754        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3755        let mut editor = build_editor(buffer.clone(), window, cx);
 3756        let buffer = buffer.read(cx).as_singleton().unwrap();
 3757
 3758        assert_eq!(
 3759            editor.selections.ranges::<Point>(cx),
 3760            &[Point::new(0, 0)..Point::new(0, 0)]
 3761        );
 3762
 3763        // When on single line, replace newline at end by space
 3764        editor.join_lines(&JoinLines, window, cx);
 3765        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3766        assert_eq!(
 3767            editor.selections.ranges::<Point>(cx),
 3768            &[Point::new(0, 3)..Point::new(0, 3)]
 3769        );
 3770
 3771        // When multiple lines are selected, remove newlines that are spanned by the selection
 3772        editor.change_selections(None, window, cx, |s| {
 3773            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3774        });
 3775        editor.join_lines(&JoinLines, window, cx);
 3776        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3777        assert_eq!(
 3778            editor.selections.ranges::<Point>(cx),
 3779            &[Point::new(0, 11)..Point::new(0, 11)]
 3780        );
 3781
 3782        // Undo should be transactional
 3783        editor.undo(&Undo, window, cx);
 3784        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 5)..Point::new(2, 2)]
 3788        );
 3789
 3790        // When joining an empty line don't insert a space
 3791        editor.change_selections(None, window, cx, |s| {
 3792            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3793        });
 3794        editor.join_lines(&JoinLines, window, cx);
 3795        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3796        assert_eq!(
 3797            editor.selections.ranges::<Point>(cx),
 3798            [Point::new(2, 3)..Point::new(2, 3)]
 3799        );
 3800
 3801        // We can remove trailing newlines
 3802        editor.join_lines(&JoinLines, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            [Point::new(2, 3)..Point::new(2, 3)]
 3807        );
 3808
 3809        // We don't blow up on the last line
 3810        editor.join_lines(&JoinLines, window, cx);
 3811        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3812        assert_eq!(
 3813            editor.selections.ranges::<Point>(cx),
 3814            [Point::new(2, 3)..Point::new(2, 3)]
 3815        );
 3816
 3817        // reset to test indentation
 3818        editor.buffer.update(cx, |buffer, cx| {
 3819            buffer.edit(
 3820                [
 3821                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3822                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3823                ],
 3824                None,
 3825                cx,
 3826            )
 3827        });
 3828
 3829        // We remove any leading spaces
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3831        editor.change_selections(None, window, cx, |s| {
 3832            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3833        });
 3834        editor.join_lines(&JoinLines, window, cx);
 3835        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3836
 3837        // We don't insert a space for a line containing only spaces
 3838        editor.join_lines(&JoinLines, window, cx);
 3839        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3840
 3841        // We ignore any leading tabs
 3842        editor.join_lines(&JoinLines, window, cx);
 3843        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3844
 3845        editor
 3846    });
 3847}
 3848
 3849#[gpui::test]
 3850fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3851    init_test(cx, |_| {});
 3852
 3853    cx.add_window(|window, cx| {
 3854        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3855        let mut editor = build_editor(buffer.clone(), window, cx);
 3856        let buffer = buffer.read(cx).as_singleton().unwrap();
 3857
 3858        editor.change_selections(None, window, cx, |s| {
 3859            s.select_ranges([
 3860                Point::new(0, 2)..Point::new(1, 1),
 3861                Point::new(1, 2)..Point::new(1, 2),
 3862                Point::new(3, 1)..Point::new(3, 2),
 3863            ])
 3864        });
 3865
 3866        editor.join_lines(&JoinLines, window, cx);
 3867        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3868
 3869        assert_eq!(
 3870            editor.selections.ranges::<Point>(cx),
 3871            [
 3872                Point::new(0, 7)..Point::new(0, 7),
 3873                Point::new(1, 3)..Point::new(1, 3)
 3874            ]
 3875        );
 3876        editor
 3877    });
 3878}
 3879
 3880#[gpui::test]
 3881async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3882    init_test(cx, |_| {});
 3883
 3884    let mut cx = EditorTestContext::new(cx).await;
 3885
 3886    let diff_base = r#"
 3887        Line 0
 3888        Line 1
 3889        Line 2
 3890        Line 3
 3891        "#
 3892    .unindent();
 3893
 3894    cx.set_state(
 3895        &r#"
 3896        ˇLine 0
 3897        Line 1
 3898        Line 2
 3899        Line 3
 3900        "#
 3901        .unindent(),
 3902    );
 3903
 3904    cx.set_head_text(&diff_base);
 3905    executor.run_until_parked();
 3906
 3907    // Join lines
 3908    cx.update_editor(|editor, window, cx| {
 3909        editor.join_lines(&JoinLines, window, cx);
 3910    });
 3911    executor.run_until_parked();
 3912
 3913    cx.assert_editor_state(
 3914        &r#"
 3915        Line 0ˇ Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919        .unindent(),
 3920    );
 3921    // Join again
 3922    cx.update_editor(|editor, window, cx| {
 3923        editor.join_lines(&JoinLines, window, cx);
 3924    });
 3925    executor.run_until_parked();
 3926
 3927    cx.assert_editor_state(
 3928        &r#"
 3929        Line 0 Line 1ˇ Line 2
 3930        Line 3
 3931        "#
 3932        .unindent(),
 3933    );
 3934}
 3935
 3936#[gpui::test]
 3937async fn test_custom_newlines_cause_no_false_positive_diffs(
 3938    executor: BackgroundExecutor,
 3939    cx: &mut TestAppContext,
 3940) {
 3941    init_test(cx, |_| {});
 3942    let mut cx = EditorTestContext::new(cx).await;
 3943    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3944    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3945    executor.run_until_parked();
 3946
 3947    cx.update_editor(|editor, window, cx| {
 3948        let snapshot = editor.snapshot(window, cx);
 3949        assert_eq!(
 3950            snapshot
 3951                .buffer_snapshot
 3952                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3953                .collect::<Vec<_>>(),
 3954            Vec::new(),
 3955            "Should not have any diffs for files with custom newlines"
 3956        );
 3957    });
 3958}
 3959
 3960#[gpui::test]
 3961async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3962    init_test(cx, |_| {});
 3963
 3964    let mut cx = EditorTestContext::new(cx).await;
 3965
 3966    // Test sort_lines_case_insensitive()
 3967    cx.set_state(indoc! {"
 3968        «z
 3969        y
 3970        x
 3971        Z
 3972        Y
 3973        Xˇ»
 3974    "});
 3975    cx.update_editor(|e, window, cx| {
 3976        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3977    });
 3978    cx.assert_editor_state(indoc! {"
 3979        «x
 3980        X
 3981        y
 3982        Y
 3983        z
 3984        Zˇ»
 3985    "});
 3986
 3987    // Test reverse_lines()
 3988    cx.set_state(indoc! {"
 3989        «5
 3990        4
 3991        3
 3992        2
 3993        1ˇ»
 3994    "});
 3995    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3996    cx.assert_editor_state(indoc! {"
 3997        «1
 3998        2
 3999        3
 4000        4
 4001        5ˇ»
 4002    "});
 4003
 4004    // Skip testing shuffle_line()
 4005
 4006    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 4007    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 4008
 4009    // Don't manipulate when cursor is on single line, but expand the selection
 4010    cx.set_state(indoc! {"
 4011        ddˇdd
 4012        ccc
 4013        bb
 4014        a
 4015    "});
 4016    cx.update_editor(|e, window, cx| {
 4017        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4018    });
 4019    cx.assert_editor_state(indoc! {"
 4020        «ddddˇ»
 4021        ccc
 4022        bb
 4023        a
 4024    "});
 4025
 4026    // Basic manipulate case
 4027    // Start selection moves to column 0
 4028    // End of selection shrinks to fit shorter line
 4029    cx.set_state(indoc! {"
 4030        dd«d
 4031        ccc
 4032        bb
 4033        aaaaaˇ»
 4034    "});
 4035    cx.update_editor(|e, window, cx| {
 4036        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4037    });
 4038    cx.assert_editor_state(indoc! {"
 4039        «aaaaa
 4040        bb
 4041        ccc
 4042        dddˇ»
 4043    "});
 4044
 4045    // Manipulate case with newlines
 4046    cx.set_state(indoc! {"
 4047        dd«d
 4048        ccc
 4049
 4050        bb
 4051        aaaaa
 4052
 4053        ˇ»
 4054    "});
 4055    cx.update_editor(|e, window, cx| {
 4056        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4057    });
 4058    cx.assert_editor_state(indoc! {"
 4059        «
 4060
 4061        aaaaa
 4062        bb
 4063        ccc
 4064        dddˇ»
 4065
 4066    "});
 4067
 4068    // Adding new line
 4069    cx.set_state(indoc! {"
 4070        aa«a
 4071        bbˇ»b
 4072    "});
 4073    cx.update_editor(|e, window, cx| {
 4074        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4075    });
 4076    cx.assert_editor_state(indoc! {"
 4077        «aaa
 4078        bbb
 4079        added_lineˇ»
 4080    "});
 4081
 4082    // Removing line
 4083    cx.set_state(indoc! {"
 4084        aa«a
 4085        bbbˇ»
 4086    "});
 4087    cx.update_editor(|e, window, cx| {
 4088        e.manipulate_lines(window, cx, |lines| {
 4089            lines.pop();
 4090        })
 4091    });
 4092    cx.assert_editor_state(indoc! {"
 4093        «aaaˇ»
 4094    "});
 4095
 4096    // Removing all lines
 4097    cx.set_state(indoc! {"
 4098        aa«a
 4099        bbbˇ»
 4100    "});
 4101    cx.update_editor(|e, window, cx| {
 4102        e.manipulate_lines(window, cx, |lines| {
 4103            lines.drain(..);
 4104        })
 4105    });
 4106    cx.assert_editor_state(indoc! {"
 4107        ˇ
 4108    "});
 4109}
 4110
 4111#[gpui::test]
 4112async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4113    init_test(cx, |_| {});
 4114
 4115    let mut cx = EditorTestContext::new(cx).await;
 4116
 4117    // Consider continuous selection as single selection
 4118    cx.set_state(indoc! {"
 4119        Aaa«aa
 4120        cˇ»c«c
 4121        bb
 4122        aaaˇ»aa
 4123    "});
 4124    cx.update_editor(|e, window, cx| {
 4125        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4126    });
 4127    cx.assert_editor_state(indoc! {"
 4128        «Aaaaa
 4129        ccc
 4130        bb
 4131        aaaaaˇ»
 4132    "});
 4133
 4134    cx.set_state(indoc! {"
 4135        Aaa«aa
 4136        cˇ»c«c
 4137        bb
 4138        aaaˇ»aa
 4139    "});
 4140    cx.update_editor(|e, window, cx| {
 4141        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4142    });
 4143    cx.assert_editor_state(indoc! {"
 4144        «Aaaaa
 4145        ccc
 4146        bbˇ»
 4147    "});
 4148
 4149    // Consider non continuous selection as distinct dedup operations
 4150    cx.set_state(indoc! {"
 4151        «aaaaa
 4152        bb
 4153        aaaaa
 4154        aaaaaˇ»
 4155
 4156        aaa«aaˇ»
 4157    "});
 4158    cx.update_editor(|e, window, cx| {
 4159        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4160    });
 4161    cx.assert_editor_state(indoc! {"
 4162        «aaaaa
 4163        bbˇ»
 4164
 4165        «aaaaaˇ»
 4166    "});
 4167}
 4168
 4169#[gpui::test]
 4170async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174
 4175    cx.set_state(indoc! {"
 4176        «Aaa
 4177        aAa
 4178        Aaaˇ»
 4179    "});
 4180    cx.update_editor(|e, window, cx| {
 4181        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4182    });
 4183    cx.assert_editor_state(indoc! {"
 4184        «Aaa
 4185        aAaˇ»
 4186    "});
 4187
 4188    cx.set_state(indoc! {"
 4189        «Aaa
 4190        aAa
 4191        aaAˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «Aaaˇ»
 4198    "});
 4199}
 4200
 4201#[gpui::test]
 4202async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4203    init_test(cx, |_| {});
 4204
 4205    let mut cx = EditorTestContext::new(cx).await;
 4206
 4207    // Manipulate with multiple selections on a single line
 4208    cx.set_state(indoc! {"
 4209        dd«dd
 4210        cˇ»c«c
 4211        bb
 4212        aaaˇ»aa
 4213    "});
 4214    cx.update_editor(|e, window, cx| {
 4215        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4216    });
 4217    cx.assert_editor_state(indoc! {"
 4218        «aaaaa
 4219        bb
 4220        ccc
 4221        ddddˇ»
 4222    "});
 4223
 4224    // Manipulate with multiple disjoin selections
 4225    cx.set_state(indoc! {"
 4226 4227        4
 4228        3
 4229        2
 4230        1ˇ»
 4231
 4232        dd«dd
 4233        ccc
 4234        bb
 4235        aaaˇ»aa
 4236    "});
 4237    cx.update_editor(|e, window, cx| {
 4238        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4239    });
 4240    cx.assert_editor_state(indoc! {"
 4241        «1
 4242        2
 4243        3
 4244        4
 4245        5ˇ»
 4246
 4247        «aaaaa
 4248        bb
 4249        ccc
 4250        ddddˇ»
 4251    "});
 4252
 4253    // Adding lines on each selection
 4254    cx.set_state(indoc! {"
 4255 4256        1ˇ»
 4257
 4258        bb«bb
 4259        aaaˇ»aa
 4260    "});
 4261    cx.update_editor(|e, window, cx| {
 4262        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        «2
 4266        1
 4267        added lineˇ»
 4268
 4269        «bbbb
 4270        aaaaa
 4271        added lineˇ»
 4272    "});
 4273
 4274    // Removing lines on each selection
 4275    cx.set_state(indoc! {"
 4276 4277        1ˇ»
 4278
 4279        bb«bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.manipulate_lines(window, cx, |lines| {
 4284            lines.pop();
 4285        })
 4286    });
 4287    cx.assert_editor_state(indoc! {"
 4288        «2ˇ»
 4289
 4290        «bbbbˇ»
 4291    "});
 4292}
 4293
 4294#[gpui::test]
 4295async fn test_toggle_case(cx: &mut TestAppContext) {
 4296    init_test(cx, |_| {});
 4297
 4298    let mut cx = EditorTestContext::new(cx).await;
 4299
 4300    // If all lower case -> upper case
 4301    cx.set_state(indoc! {"
 4302        «hello worldˇ»
 4303    "});
 4304    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4305    cx.assert_editor_state(indoc! {"
 4306        «HELLO WORLDˇ»
 4307    "});
 4308
 4309    // If all upper case -> lower case
 4310    cx.set_state(indoc! {"
 4311        «HELLO WORLDˇ»
 4312    "});
 4313    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4314    cx.assert_editor_state(indoc! {"
 4315        «hello worldˇ»
 4316    "});
 4317
 4318    // If any upper case characters are identified -> lower case
 4319    // This matches JetBrains IDEs
 4320    cx.set_state(indoc! {"
 4321        «hEllo worldˇ»
 4322    "});
 4323    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4324    cx.assert_editor_state(indoc! {"
 4325        «hello worldˇ»
 4326    "});
 4327}
 4328
 4329#[gpui::test]
 4330async fn test_manipulate_text(cx: &mut TestAppContext) {
 4331    init_test(cx, |_| {});
 4332
 4333    let mut cx = EditorTestContext::new(cx).await;
 4334
 4335    // Test convert_to_upper_case()
 4336    cx.set_state(indoc! {"
 4337        «hello worldˇ»
 4338    "});
 4339    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4340    cx.assert_editor_state(indoc! {"
 4341        «HELLO WORLDˇ»
 4342    "});
 4343
 4344    // Test convert_to_lower_case()
 4345    cx.set_state(indoc! {"
 4346        «HELLO WORLDˇ»
 4347    "});
 4348    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4349    cx.assert_editor_state(indoc! {"
 4350        «hello worldˇ»
 4351    "});
 4352
 4353    // Test multiple line, single selection case
 4354    cx.set_state(indoc! {"
 4355        «The quick brown
 4356        fox jumps over
 4357        the lazy dogˇ»
 4358    "});
 4359    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4360    cx.assert_editor_state(indoc! {"
 4361        «The Quick Brown
 4362        Fox Jumps Over
 4363        The Lazy Dogˇ»
 4364    "});
 4365
 4366    // Test multiple line, single selection case
 4367    cx.set_state(indoc! {"
 4368        «The quick brown
 4369        fox jumps over
 4370        the lazy dogˇ»
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «TheQuickBrown
 4377        FoxJumpsOver
 4378        TheLazyDogˇ»
 4379    "});
 4380
 4381    // From here on out, test more complex cases of manipulate_text()
 4382
 4383    // Test no selection case - should affect words cursors are in
 4384    // Cursor at beginning, middle, and end of word
 4385    cx.set_state(indoc! {"
 4386        ˇhello big beauˇtiful worldˇ
 4387    "});
 4388    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4389    cx.assert_editor_state(indoc! {"
 4390        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4391    "});
 4392
 4393    // Test multiple selections on a single line and across multiple lines
 4394    cx.set_state(indoc! {"
 4395        «Theˇ» quick «brown
 4396        foxˇ» jumps «overˇ»
 4397        the «lazyˇ» dog
 4398    "});
 4399    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4400    cx.assert_editor_state(indoc! {"
 4401        «THEˇ» quick «BROWN
 4402        FOXˇ» jumps «OVERˇ»
 4403        the «LAZYˇ» dog
 4404    "});
 4405
 4406    // Test case where text length grows
 4407    cx.set_state(indoc! {"
 4408        «tschüߡ»
 4409    "});
 4410    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4411    cx.assert_editor_state(indoc! {"
 4412        «TSCHÜSSˇ»
 4413    "});
 4414
 4415    // Test to make sure we don't crash when text shrinks
 4416    cx.set_state(indoc! {"
 4417        aaa_bbbˇ
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «aaaBbbˇ»
 4424    "});
 4425
 4426    // Test to make sure we all aware of the fact that each word can grow and shrink
 4427    // Final selections should be aware of this fact
 4428    cx.set_state(indoc! {"
 4429        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4430    "});
 4431    cx.update_editor(|e, window, cx| {
 4432        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4433    });
 4434    cx.assert_editor_state(indoc! {"
 4435        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4436    "});
 4437
 4438    cx.set_state(indoc! {"
 4439        «hElLo, WoRld!ˇ»
 4440    "});
 4441    cx.update_editor(|e, window, cx| {
 4442        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4443    });
 4444    cx.assert_editor_state(indoc! {"
 4445        «HeLlO, wOrLD!ˇ»
 4446    "});
 4447}
 4448
 4449#[gpui::test]
 4450fn test_duplicate_line(cx: &mut TestAppContext) {
 4451    init_test(cx, |_| {});
 4452
 4453    let editor = cx.add_window(|window, cx| {
 4454        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4455        build_editor(buffer, window, cx)
 4456    });
 4457    _ = editor.update(cx, |editor, window, cx| {
 4458        editor.change_selections(None, window, cx, |s| {
 4459            s.select_display_ranges([
 4460                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4461                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4462                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4463                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4464            ])
 4465        });
 4466        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4467        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4468        assert_eq!(
 4469            editor.selections.display_ranges(cx),
 4470            vec![
 4471                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4472                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4473                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4474                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4475            ]
 4476        );
 4477    });
 4478
 4479    let editor = cx.add_window(|window, cx| {
 4480        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4481        build_editor(buffer, window, cx)
 4482    });
 4483    _ = editor.update(cx, |editor, window, cx| {
 4484        editor.change_selections(None, window, cx, |s| {
 4485            s.select_display_ranges([
 4486                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4487                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4488            ])
 4489        });
 4490        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4491        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4492        assert_eq!(
 4493            editor.selections.display_ranges(cx),
 4494            vec![
 4495                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4496                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4497            ]
 4498        );
 4499    });
 4500
 4501    // With `move_upwards` the selections stay in place, except for
 4502    // the lines inserted above them
 4503    let editor = cx.add_window(|window, cx| {
 4504        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4505        build_editor(buffer, window, cx)
 4506    });
 4507    _ = editor.update(cx, |editor, window, cx| {
 4508        editor.change_selections(None, window, cx, |s| {
 4509            s.select_display_ranges([
 4510                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4511                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4512                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4513                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4514            ])
 4515        });
 4516        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4517        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4518        assert_eq!(
 4519            editor.selections.display_ranges(cx),
 4520            vec![
 4521                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4522                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4523                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4524                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4525            ]
 4526        );
 4527    });
 4528
 4529    let editor = cx.add_window(|window, cx| {
 4530        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4531        build_editor(buffer, window, cx)
 4532    });
 4533    _ = editor.update(cx, |editor, window, cx| {
 4534        editor.change_selections(None, window, cx, |s| {
 4535            s.select_display_ranges([
 4536                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4537                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4538            ])
 4539        });
 4540        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4541        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4542        assert_eq!(
 4543            editor.selections.display_ranges(cx),
 4544            vec![
 4545                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4546                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4547            ]
 4548        );
 4549    });
 4550
 4551    let editor = cx.add_window(|window, cx| {
 4552        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4553        build_editor(buffer, window, cx)
 4554    });
 4555    _ = editor.update(cx, |editor, window, cx| {
 4556        editor.change_selections(None, window, cx, |s| {
 4557            s.select_display_ranges([
 4558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4559                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4560            ])
 4561        });
 4562        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4563        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4564        assert_eq!(
 4565            editor.selections.display_ranges(cx),
 4566            vec![
 4567                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4568                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4569            ]
 4570        );
 4571    });
 4572}
 4573
 4574#[gpui::test]
 4575fn test_move_line_up_down(cx: &mut TestAppContext) {
 4576    init_test(cx, |_| {});
 4577
 4578    let editor = cx.add_window(|window, cx| {
 4579        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4580        build_editor(buffer, window, cx)
 4581    });
 4582    _ = editor.update(cx, |editor, window, cx| {
 4583        editor.fold_creases(
 4584            vec![
 4585                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4586                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4587                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4588            ],
 4589            true,
 4590            window,
 4591            cx,
 4592        );
 4593        editor.change_selections(None, window, cx, |s| {
 4594            s.select_display_ranges([
 4595                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4596                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4597                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4598                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4599            ])
 4600        });
 4601        assert_eq!(
 4602            editor.display_text(cx),
 4603            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4604        );
 4605
 4606        editor.move_line_up(&MoveLineUp, window, cx);
 4607        assert_eq!(
 4608            editor.display_text(cx),
 4609            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4610        );
 4611        assert_eq!(
 4612            editor.selections.display_ranges(cx),
 4613            vec![
 4614                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4615                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4616                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4617                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4618            ]
 4619        );
 4620    });
 4621
 4622    _ = editor.update(cx, |editor, window, cx| {
 4623        editor.move_line_down(&MoveLineDown, window, cx);
 4624        assert_eq!(
 4625            editor.display_text(cx),
 4626            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4627        );
 4628        assert_eq!(
 4629            editor.selections.display_ranges(cx),
 4630            vec![
 4631                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4632                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4633                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4634                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4635            ]
 4636        );
 4637    });
 4638
 4639    _ = editor.update(cx, |editor, window, cx| {
 4640        editor.move_line_down(&MoveLineDown, window, cx);
 4641        assert_eq!(
 4642            editor.display_text(cx),
 4643            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4644        );
 4645        assert_eq!(
 4646            editor.selections.display_ranges(cx),
 4647            vec![
 4648                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4649                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4650                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4651                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4652            ]
 4653        );
 4654    });
 4655
 4656    _ = editor.update(cx, |editor, window, cx| {
 4657        editor.move_line_up(&MoveLineUp, window, cx);
 4658        assert_eq!(
 4659            editor.display_text(cx),
 4660            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4661        );
 4662        assert_eq!(
 4663            editor.selections.display_ranges(cx),
 4664            vec![
 4665                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4666                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4667                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4668                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4669            ]
 4670        );
 4671    });
 4672}
 4673
 4674#[gpui::test]
 4675fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4676    init_test(cx, |_| {});
 4677
 4678    let editor = cx.add_window(|window, cx| {
 4679        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4680        build_editor(buffer, window, cx)
 4681    });
 4682    _ = editor.update(cx, |editor, window, cx| {
 4683        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4684        editor.insert_blocks(
 4685            [BlockProperties {
 4686                style: BlockStyle::Fixed,
 4687                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4688                height: Some(1),
 4689                render: Arc::new(|_| div().into_any()),
 4690                priority: 0,
 4691                render_in_minimap: true,
 4692            }],
 4693            Some(Autoscroll::fit()),
 4694            cx,
 4695        );
 4696        editor.change_selections(None, window, cx, |s| {
 4697            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4698        });
 4699        editor.move_line_down(&MoveLineDown, window, cx);
 4700    });
 4701}
 4702
 4703#[gpui::test]
 4704async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4705    init_test(cx, |_| {});
 4706
 4707    let mut cx = EditorTestContext::new(cx).await;
 4708    cx.set_state(
 4709        &"
 4710            ˇzero
 4711            one
 4712            two
 4713            three
 4714            four
 4715            five
 4716        "
 4717        .unindent(),
 4718    );
 4719
 4720    // Create a four-line block that replaces three lines of text.
 4721    cx.update_editor(|editor, window, cx| {
 4722        let snapshot = editor.snapshot(window, cx);
 4723        let snapshot = &snapshot.buffer_snapshot;
 4724        let placement = BlockPlacement::Replace(
 4725            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4726        );
 4727        editor.insert_blocks(
 4728            [BlockProperties {
 4729                placement,
 4730                height: Some(4),
 4731                style: BlockStyle::Sticky,
 4732                render: Arc::new(|_| gpui::div().into_any_element()),
 4733                priority: 0,
 4734                render_in_minimap: true,
 4735            }],
 4736            None,
 4737            cx,
 4738        );
 4739    });
 4740
 4741    // Move down so that the cursor touches the block.
 4742    cx.update_editor(|editor, window, cx| {
 4743        editor.move_down(&Default::default(), window, cx);
 4744    });
 4745    cx.assert_editor_state(
 4746        &"
 4747            zero
 4748            «one
 4749            two
 4750            threeˇ»
 4751            four
 4752            five
 4753        "
 4754        .unindent(),
 4755    );
 4756
 4757    // Move down past the block.
 4758    cx.update_editor(|editor, window, cx| {
 4759        editor.move_down(&Default::default(), window, cx);
 4760    });
 4761    cx.assert_editor_state(
 4762        &"
 4763            zero
 4764            one
 4765            two
 4766            three
 4767            ˇfour
 4768            five
 4769        "
 4770        .unindent(),
 4771    );
 4772}
 4773
 4774#[gpui::test]
 4775fn test_transpose(cx: &mut TestAppContext) {
 4776    init_test(cx, |_| {});
 4777
 4778    _ = cx.add_window(|window, cx| {
 4779        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4780        editor.set_style(EditorStyle::default(), window, cx);
 4781        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4782        editor.transpose(&Default::default(), window, cx);
 4783        assert_eq!(editor.text(cx), "bac");
 4784        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4785
 4786        editor.transpose(&Default::default(), window, cx);
 4787        assert_eq!(editor.text(cx), "bca");
 4788        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4789
 4790        editor.transpose(&Default::default(), window, cx);
 4791        assert_eq!(editor.text(cx), "bac");
 4792        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4793
 4794        editor
 4795    });
 4796
 4797    _ = cx.add_window(|window, cx| {
 4798        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4799        editor.set_style(EditorStyle::default(), window, cx);
 4800        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4801        editor.transpose(&Default::default(), window, cx);
 4802        assert_eq!(editor.text(cx), "acb\nde");
 4803        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4804
 4805        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4806        editor.transpose(&Default::default(), window, cx);
 4807        assert_eq!(editor.text(cx), "acbd\ne");
 4808        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4809
 4810        editor.transpose(&Default::default(), window, cx);
 4811        assert_eq!(editor.text(cx), "acbde\n");
 4812        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4813
 4814        editor.transpose(&Default::default(), window, cx);
 4815        assert_eq!(editor.text(cx), "acbd\ne");
 4816        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4817
 4818        editor
 4819    });
 4820
 4821    _ = cx.add_window(|window, cx| {
 4822        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4823        editor.set_style(EditorStyle::default(), window, cx);
 4824        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4825        editor.transpose(&Default::default(), window, cx);
 4826        assert_eq!(editor.text(cx), "bacd\ne");
 4827        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4828
 4829        editor.transpose(&Default::default(), window, cx);
 4830        assert_eq!(editor.text(cx), "bcade\n");
 4831        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4832
 4833        editor.transpose(&Default::default(), window, cx);
 4834        assert_eq!(editor.text(cx), "bcda\ne");
 4835        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4836
 4837        editor.transpose(&Default::default(), window, cx);
 4838        assert_eq!(editor.text(cx), "bcade\n");
 4839        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4840
 4841        editor.transpose(&Default::default(), window, cx);
 4842        assert_eq!(editor.text(cx), "bcaed\n");
 4843        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4844
 4845        editor
 4846    });
 4847
 4848    _ = cx.add_window(|window, cx| {
 4849        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4850        editor.set_style(EditorStyle::default(), window, cx);
 4851        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4852        editor.transpose(&Default::default(), window, cx);
 4853        assert_eq!(editor.text(cx), "🏀🍐✋");
 4854        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4855
 4856        editor.transpose(&Default::default(), window, cx);
 4857        assert_eq!(editor.text(cx), "🏀✋🍐");
 4858        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4859
 4860        editor.transpose(&Default::default(), window, cx);
 4861        assert_eq!(editor.text(cx), "🏀🍐✋");
 4862        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4863
 4864        editor
 4865    });
 4866}
 4867
 4868#[gpui::test]
 4869async fn test_rewrap(cx: &mut TestAppContext) {
 4870    init_test(cx, |settings| {
 4871        settings.languages.extend([
 4872            (
 4873                "Markdown".into(),
 4874                LanguageSettingsContent {
 4875                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4876                    ..Default::default()
 4877                },
 4878            ),
 4879            (
 4880                "Plain Text".into(),
 4881                LanguageSettingsContent {
 4882                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4883                    ..Default::default()
 4884                },
 4885            ),
 4886        ])
 4887    });
 4888
 4889    let mut cx = EditorTestContext::new(cx).await;
 4890
 4891    let language_with_c_comments = Arc::new(Language::new(
 4892        LanguageConfig {
 4893            line_comments: vec!["// ".into()],
 4894            ..LanguageConfig::default()
 4895        },
 4896        None,
 4897    ));
 4898    let language_with_pound_comments = Arc::new(Language::new(
 4899        LanguageConfig {
 4900            line_comments: vec!["# ".into()],
 4901            ..LanguageConfig::default()
 4902        },
 4903        None,
 4904    ));
 4905    let markdown_language = Arc::new(Language::new(
 4906        LanguageConfig {
 4907            name: "Markdown".into(),
 4908            ..LanguageConfig::default()
 4909        },
 4910        None,
 4911    ));
 4912    let language_with_doc_comments = Arc::new(Language::new(
 4913        LanguageConfig {
 4914            line_comments: vec!["// ".into(), "/// ".into()],
 4915            ..LanguageConfig::default()
 4916        },
 4917        Some(tree_sitter_rust::LANGUAGE.into()),
 4918    ));
 4919
 4920    let plaintext_language = Arc::new(Language::new(
 4921        LanguageConfig {
 4922            name: "Plain Text".into(),
 4923            ..LanguageConfig::default()
 4924        },
 4925        None,
 4926    ));
 4927
 4928    assert_rewrap(
 4929        indoc! {"
 4930            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4931        "},
 4932        indoc! {"
 4933            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4934            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4935            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4936            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4937            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4938            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4939            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4940            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4941            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4942            // porttitor id. Aliquam id accumsan eros.
 4943        "},
 4944        language_with_c_comments.clone(),
 4945        &mut cx,
 4946    );
 4947
 4948    // Test that rewrapping works inside of a selection
 4949    assert_rewrap(
 4950        indoc! {"
 4951            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
 4952        "},
 4953        indoc! {"
 4954            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4955            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4956            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4957            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4958            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4959            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4960            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4961            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4962            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4963            // porttitor id. Aliquam id accumsan eros.ˇ»
 4964        "},
 4965        language_with_c_comments.clone(),
 4966        &mut cx,
 4967    );
 4968
 4969    // Test that cursors that expand to the same region are collapsed.
 4970    assert_rewrap(
 4971        indoc! {"
 4972            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4973            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4974            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4975            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4976        "},
 4977        indoc! {"
 4978            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4979            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4980            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4981            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4982            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4983            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4984            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4985            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4986            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4987            // porttitor id. Aliquam id accumsan eros.
 4988        "},
 4989        language_with_c_comments.clone(),
 4990        &mut cx,
 4991    );
 4992
 4993    // Test that non-contiguous selections are treated separately.
 4994    assert_rewrap(
 4995        indoc! {"
 4996            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4997            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4998            //
 4999            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5000            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5001        "},
 5002        indoc! {"
 5003            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5004            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5005            // auctor, eu lacinia sapien scelerisque.
 5006            //
 5007            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5008            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5009            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5010            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5011            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5012            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5013            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5014        "},
 5015        language_with_c_comments.clone(),
 5016        &mut cx,
 5017    );
 5018
 5019    // Test that different comment prefixes are supported.
 5020    assert_rewrap(
 5021        indoc! {"
 5022            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5023        "},
 5024        indoc! {"
 5025            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5026            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5027            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5028            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5029            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5030            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5031            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5032            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5033            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5034            # accumsan eros.
 5035        "},
 5036        language_with_pound_comments.clone(),
 5037        &mut cx,
 5038    );
 5039
 5040    // Test that rewrapping is ignored outside of comments in most languages.
 5041    assert_rewrap(
 5042        indoc! {"
 5043            /// Adds two numbers.
 5044            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5045            fn add(a: u32, b: u32) -> u32 {
 5046                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5047            }
 5048        "},
 5049        indoc! {"
 5050            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5051            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5052            fn add(a: u32, b: u32) -> u32 {
 5053                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5054            }
 5055        "},
 5056        language_with_doc_comments.clone(),
 5057        &mut cx,
 5058    );
 5059
 5060    // Test that rewrapping works in Markdown and Plain Text languages.
 5061    assert_rewrap(
 5062        indoc! {"
 5063            # Hello
 5064
 5065            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5066        "},
 5067        indoc! {"
 5068            # Hello
 5069
 5070            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5071            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5072            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5073            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5074            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5075            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5076            Integer sit amet scelerisque nisi.
 5077        "},
 5078        markdown_language,
 5079        &mut cx,
 5080    );
 5081
 5082    assert_rewrap(
 5083        indoc! {"
 5084            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5085        "},
 5086        indoc! {"
 5087            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5088            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5089            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5090            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5091            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5092            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5093            Integer sit amet scelerisque nisi.
 5094        "},
 5095        plaintext_language,
 5096        &mut cx,
 5097    );
 5098
 5099    // Test rewrapping unaligned comments in a selection.
 5100    assert_rewrap(
 5101        indoc! {"
 5102            fn foo() {
 5103                if true {
 5104            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5105            // Praesent semper egestas tellus id dignissim.ˇ»
 5106                    do_something();
 5107                } else {
 5108                    //
 5109                }
 5110            }
 5111        "},
 5112        indoc! {"
 5113            fn foo() {
 5114                if true {
 5115            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5116                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5117                    // egestas tellus id dignissim.ˇ»
 5118                    do_something();
 5119                } else {
 5120                    //
 5121                }
 5122            }
 5123        "},
 5124        language_with_doc_comments.clone(),
 5125        &mut cx,
 5126    );
 5127
 5128    assert_rewrap(
 5129        indoc! {"
 5130            fn foo() {
 5131                if true {
 5132            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5133            // Praesent semper egestas tellus id dignissim.»
 5134                    do_something();
 5135                } else {
 5136                    //
 5137                }
 5138
 5139            }
 5140        "},
 5141        indoc! {"
 5142            fn foo() {
 5143                if true {
 5144            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5145                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5146                    // egestas tellus id dignissim.»
 5147                    do_something();
 5148                } else {
 5149                    //
 5150                }
 5151
 5152            }
 5153        "},
 5154        language_with_doc_comments.clone(),
 5155        &mut cx,
 5156    );
 5157
 5158    #[track_caller]
 5159    fn assert_rewrap(
 5160        unwrapped_text: &str,
 5161        wrapped_text: &str,
 5162        language: Arc<Language>,
 5163        cx: &mut EditorTestContext,
 5164    ) {
 5165        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5166        cx.set_state(unwrapped_text);
 5167        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5168        cx.assert_editor_state(wrapped_text);
 5169    }
 5170}
 5171
 5172#[gpui::test]
 5173async fn test_hard_wrap(cx: &mut TestAppContext) {
 5174    init_test(cx, |_| {});
 5175    let mut cx = EditorTestContext::new(cx).await;
 5176
 5177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5178    cx.update_editor(|editor, _, cx| {
 5179        editor.set_hard_wrap(Some(14), cx);
 5180    });
 5181
 5182    cx.set_state(indoc!(
 5183        "
 5184        one two three ˇ
 5185        "
 5186    ));
 5187    cx.simulate_input("four");
 5188    cx.run_until_parked();
 5189
 5190    cx.assert_editor_state(indoc!(
 5191        "
 5192        one two three
 5193        fourˇ
 5194        "
 5195    ));
 5196
 5197    cx.update_editor(|editor, window, cx| {
 5198        editor.newline(&Default::default(), window, cx);
 5199    });
 5200    cx.run_until_parked();
 5201    cx.assert_editor_state(indoc!(
 5202        "
 5203        one two three
 5204        four
 5205        ˇ
 5206        "
 5207    ));
 5208
 5209    cx.simulate_input("five");
 5210    cx.run_until_parked();
 5211    cx.assert_editor_state(indoc!(
 5212        "
 5213        one two three
 5214        four
 5215        fiveˇ
 5216        "
 5217    ));
 5218
 5219    cx.update_editor(|editor, window, cx| {
 5220        editor.newline(&Default::default(), window, cx);
 5221    });
 5222    cx.run_until_parked();
 5223    cx.simulate_input("# ");
 5224    cx.run_until_parked();
 5225    cx.assert_editor_state(indoc!(
 5226        "
 5227        one two three
 5228        four
 5229        five
 5230        # ˇ
 5231        "
 5232    ));
 5233
 5234    cx.update_editor(|editor, window, cx| {
 5235        editor.newline(&Default::default(), window, cx);
 5236    });
 5237    cx.run_until_parked();
 5238    cx.assert_editor_state(indoc!(
 5239        "
 5240        one two three
 5241        four
 5242        five
 5243        #\x20
 5244 5245        "
 5246    ));
 5247
 5248    cx.simulate_input(" 6");
 5249    cx.run_until_parked();
 5250    cx.assert_editor_state(indoc!(
 5251        "
 5252        one two three
 5253        four
 5254        five
 5255        #
 5256        # 6ˇ
 5257        "
 5258    ));
 5259}
 5260
 5261#[gpui::test]
 5262async fn test_clipboard(cx: &mut TestAppContext) {
 5263    init_test(cx, |_| {});
 5264
 5265    let mut cx = EditorTestContext::new(cx).await;
 5266
 5267    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5268    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5269    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5270
 5271    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5272    cx.set_state("two ˇfour ˇsix ˇ");
 5273    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5274    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5275
 5276    // Paste again but with only two cursors. Since the number of cursors doesn't
 5277    // match the number of slices in the clipboard, the entire clipboard text
 5278    // is pasted at each cursor.
 5279    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5280    cx.update_editor(|e, window, cx| {
 5281        e.handle_input("( ", window, cx);
 5282        e.paste(&Paste, window, cx);
 5283        e.handle_input(") ", window, cx);
 5284    });
 5285    cx.assert_editor_state(
 5286        &([
 5287            "( one✅ ",
 5288            "three ",
 5289            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5290            "three ",
 5291            "five ) ˇ",
 5292        ]
 5293        .join("\n")),
 5294    );
 5295
 5296    // Cut with three selections, one of which is full-line.
 5297    cx.set_state(indoc! {"
 5298        1«2ˇ»3
 5299        4ˇ567
 5300        «8ˇ»9"});
 5301    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5302    cx.assert_editor_state(indoc! {"
 5303        1ˇ3
 5304        ˇ9"});
 5305
 5306    // Paste with three selections, noticing how the copied selection that was full-line
 5307    // gets inserted before the second cursor.
 5308    cx.set_state(indoc! {"
 5309        1ˇ3
 5310 5311        «oˇ»ne"});
 5312    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5313    cx.assert_editor_state(indoc! {"
 5314        12ˇ3
 5315        4567
 5316 5317        8ˇne"});
 5318
 5319    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5320    cx.set_state(indoc! {"
 5321        The quick brown
 5322        fox juˇmps over
 5323        the lazy dog"});
 5324    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5325    assert_eq!(
 5326        cx.read_from_clipboard()
 5327            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5328        Some("fox jumps over\n".to_string())
 5329    );
 5330
 5331    // Paste with three selections, noticing how the copied full-line selection is inserted
 5332    // before the empty selections but replaces the selection that is non-empty.
 5333    cx.set_state(indoc! {"
 5334        Tˇhe quick brown
 5335        «foˇ»x jumps over
 5336        tˇhe lazy dog"});
 5337    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5338    cx.assert_editor_state(indoc! {"
 5339        fox jumps over
 5340        Tˇhe quick brown
 5341        fox jumps over
 5342        ˇx jumps over
 5343        fox jumps over
 5344        tˇhe lazy dog"});
 5345}
 5346
 5347#[gpui::test]
 5348async fn test_copy_trim(cx: &mut TestAppContext) {
 5349    init_test(cx, |_| {});
 5350
 5351    let mut cx = EditorTestContext::new(cx).await;
 5352    cx.set_state(
 5353        r#"            «for selection in selections.iter() {
 5354            let mut start = selection.start;
 5355            let mut end = selection.end;
 5356            let is_entire_line = selection.is_empty();
 5357            if is_entire_line {
 5358                start = Point::new(start.row, 0);ˇ»
 5359                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5360            }
 5361        "#,
 5362    );
 5363    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5364    assert_eq!(
 5365        cx.read_from_clipboard()
 5366            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5367        Some(
 5368            "for selection in selections.iter() {
 5369            let mut start = selection.start;
 5370            let mut end = selection.end;
 5371            let is_entire_line = selection.is_empty();
 5372            if is_entire_line {
 5373                start = Point::new(start.row, 0);"
 5374                .to_string()
 5375        ),
 5376        "Regular copying preserves all indentation selected",
 5377    );
 5378    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5379    assert_eq!(
 5380        cx.read_from_clipboard()
 5381            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5382        Some(
 5383            "for selection in selections.iter() {
 5384let mut start = selection.start;
 5385let mut end = selection.end;
 5386let is_entire_line = selection.is_empty();
 5387if is_entire_line {
 5388    start = Point::new(start.row, 0);"
 5389                .to_string()
 5390        ),
 5391        "Copying with stripping should strip all leading whitespaces"
 5392    );
 5393
 5394    cx.set_state(
 5395        r#"       «     for selection in selections.iter() {
 5396            let mut start = selection.start;
 5397            let mut end = selection.end;
 5398            let is_entire_line = selection.is_empty();
 5399            if is_entire_line {
 5400                start = Point::new(start.row, 0);ˇ»
 5401                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5402            }
 5403        "#,
 5404    );
 5405    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5406    assert_eq!(
 5407        cx.read_from_clipboard()
 5408            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5409        Some(
 5410            "     for selection in selections.iter() {
 5411            let mut start = selection.start;
 5412            let mut end = selection.end;
 5413            let is_entire_line = selection.is_empty();
 5414            if is_entire_line {
 5415                start = Point::new(start.row, 0);"
 5416                .to_string()
 5417        ),
 5418        "Regular copying preserves all indentation selected",
 5419    );
 5420    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5421    assert_eq!(
 5422        cx.read_from_clipboard()
 5423            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5424        Some(
 5425            "for selection in selections.iter() {
 5426let mut start = selection.start;
 5427let mut end = selection.end;
 5428let is_entire_line = selection.is_empty();
 5429if is_entire_line {
 5430    start = Point::new(start.row, 0);"
 5431                .to_string()
 5432        ),
 5433        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5434    );
 5435
 5436    cx.set_state(
 5437        r#"       «ˇ     for selection in selections.iter() {
 5438            let mut start = selection.start;
 5439            let mut end = selection.end;
 5440            let is_entire_line = selection.is_empty();
 5441            if is_entire_line {
 5442                start = Point::new(start.row, 0);»
 5443                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5444            }
 5445        "#,
 5446    );
 5447    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5448    assert_eq!(
 5449        cx.read_from_clipboard()
 5450            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5451        Some(
 5452            "     for selection in selections.iter() {
 5453            let mut start = selection.start;
 5454            let mut end = selection.end;
 5455            let is_entire_line = selection.is_empty();
 5456            if is_entire_line {
 5457                start = Point::new(start.row, 0);"
 5458                .to_string()
 5459        ),
 5460        "Regular copying for reverse selection works the same",
 5461    );
 5462    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5463    assert_eq!(
 5464        cx.read_from_clipboard()
 5465            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5466        Some(
 5467            "for selection in selections.iter() {
 5468let mut start = selection.start;
 5469let mut end = selection.end;
 5470let is_entire_line = selection.is_empty();
 5471if is_entire_line {
 5472    start = Point::new(start.row, 0);"
 5473                .to_string()
 5474        ),
 5475        "Copying with stripping for reverse selection works the same"
 5476    );
 5477
 5478    cx.set_state(
 5479        r#"            for selection «in selections.iter() {
 5480            let mut start = selection.start;
 5481            let mut end = selection.end;
 5482            let is_entire_line = selection.is_empty();
 5483            if is_entire_line {
 5484                start = Point::new(start.row, 0);ˇ»
 5485                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5486            }
 5487        "#,
 5488    );
 5489    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5490    assert_eq!(
 5491        cx.read_from_clipboard()
 5492            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5493        Some(
 5494            "in selections.iter() {
 5495            let mut start = selection.start;
 5496            let mut end = selection.end;
 5497            let is_entire_line = selection.is_empty();
 5498            if is_entire_line {
 5499                start = Point::new(start.row, 0);"
 5500                .to_string()
 5501        ),
 5502        "When selecting past the indent, the copying works as usual",
 5503    );
 5504    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5505    assert_eq!(
 5506        cx.read_from_clipboard()
 5507            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5508        Some(
 5509            "in selections.iter() {
 5510            let mut start = selection.start;
 5511            let mut end = selection.end;
 5512            let is_entire_line = selection.is_empty();
 5513            if is_entire_line {
 5514                start = Point::new(start.row, 0);"
 5515                .to_string()
 5516        ),
 5517        "When selecting past the indent, nothing is trimmed"
 5518    );
 5519
 5520    cx.set_state(
 5521        r#"            «for selection in selections.iter() {
 5522            let mut start = selection.start;
 5523
 5524            let mut end = selection.end;
 5525            let is_entire_line = selection.is_empty();
 5526            if is_entire_line {
 5527                start = Point::new(start.row, 0);
 5528ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5529            }
 5530        "#,
 5531    );
 5532    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5533    assert_eq!(
 5534        cx.read_from_clipboard()
 5535            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5536        Some(
 5537            "for selection in selections.iter() {
 5538let mut start = selection.start;
 5539
 5540let mut end = selection.end;
 5541let is_entire_line = selection.is_empty();
 5542if is_entire_line {
 5543    start = Point::new(start.row, 0);
 5544"
 5545            .to_string()
 5546        ),
 5547        "Copying with stripping should ignore empty lines"
 5548    );
 5549}
 5550
 5551#[gpui::test]
 5552async fn test_paste_multiline(cx: &mut TestAppContext) {
 5553    init_test(cx, |_| {});
 5554
 5555    let mut cx = EditorTestContext::new(cx).await;
 5556    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5557
 5558    // Cut an indented block, without the leading whitespace.
 5559    cx.set_state(indoc! {"
 5560        const a: B = (
 5561            c(),
 5562            «d(
 5563                e,
 5564                f
 5565            )ˇ»
 5566        );
 5567    "});
 5568    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5569    cx.assert_editor_state(indoc! {"
 5570        const a: B = (
 5571            c(),
 5572            ˇ
 5573        );
 5574    "});
 5575
 5576    // Paste it at the same position.
 5577    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5578    cx.assert_editor_state(indoc! {"
 5579        const a: B = (
 5580            c(),
 5581            d(
 5582                e,
 5583                f
 5584 5585        );
 5586    "});
 5587
 5588    // Paste it at a line with a lower indent level.
 5589    cx.set_state(indoc! {"
 5590        ˇ
 5591        const a: B = (
 5592            c(),
 5593        );
 5594    "});
 5595    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5596    cx.assert_editor_state(indoc! {"
 5597        d(
 5598            e,
 5599            f
 5600 5601        const a: B = (
 5602            c(),
 5603        );
 5604    "});
 5605
 5606    // Cut an indented block, with the leading whitespace.
 5607    cx.set_state(indoc! {"
 5608        const a: B = (
 5609            c(),
 5610        «    d(
 5611                e,
 5612                f
 5613            )
 5614        ˇ»);
 5615    "});
 5616    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5617    cx.assert_editor_state(indoc! {"
 5618        const a: B = (
 5619            c(),
 5620        ˇ);
 5621    "});
 5622
 5623    // Paste it at the same position.
 5624    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5625    cx.assert_editor_state(indoc! {"
 5626        const a: B = (
 5627            c(),
 5628            d(
 5629                e,
 5630                f
 5631            )
 5632        ˇ);
 5633    "});
 5634
 5635    // Paste it at a line with a higher indent level.
 5636    cx.set_state(indoc! {"
 5637        const a: B = (
 5638            c(),
 5639            d(
 5640                e,
 5641 5642            )
 5643        );
 5644    "});
 5645    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5646    cx.assert_editor_state(indoc! {"
 5647        const a: B = (
 5648            c(),
 5649            d(
 5650                e,
 5651                f    d(
 5652                    e,
 5653                    f
 5654                )
 5655        ˇ
 5656            )
 5657        );
 5658    "});
 5659
 5660    // Copy an indented block, starting mid-line
 5661    cx.set_state(indoc! {"
 5662        const a: B = (
 5663            c(),
 5664            somethin«g(
 5665                e,
 5666                f
 5667            )ˇ»
 5668        );
 5669    "});
 5670    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5671
 5672    // Paste it on a line with a lower indent level
 5673    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5674    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5675    cx.assert_editor_state(indoc! {"
 5676        const a: B = (
 5677            c(),
 5678            something(
 5679                e,
 5680                f
 5681            )
 5682        );
 5683        g(
 5684            e,
 5685            f
 5686"});
 5687}
 5688
 5689#[gpui::test]
 5690async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5691    init_test(cx, |_| {});
 5692
 5693    cx.write_to_clipboard(ClipboardItem::new_string(
 5694        "    d(\n        e\n    );\n".into(),
 5695    ));
 5696
 5697    let mut cx = EditorTestContext::new(cx).await;
 5698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5699
 5700    cx.set_state(indoc! {"
 5701        fn a() {
 5702            b();
 5703            if c() {
 5704                ˇ
 5705            }
 5706        }
 5707    "});
 5708
 5709    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5710    cx.assert_editor_state(indoc! {"
 5711        fn a() {
 5712            b();
 5713            if c() {
 5714                d(
 5715                    e
 5716                );
 5717        ˇ
 5718            }
 5719        }
 5720    "});
 5721
 5722    cx.set_state(indoc! {"
 5723        fn a() {
 5724            b();
 5725            ˇ
 5726        }
 5727    "});
 5728
 5729    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5730    cx.assert_editor_state(indoc! {"
 5731        fn a() {
 5732            b();
 5733            d(
 5734                e
 5735            );
 5736        ˇ
 5737        }
 5738    "});
 5739}
 5740
 5741#[gpui::test]
 5742fn test_select_all(cx: &mut TestAppContext) {
 5743    init_test(cx, |_| {});
 5744
 5745    let editor = cx.add_window(|window, cx| {
 5746        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5747        build_editor(buffer, window, cx)
 5748    });
 5749    _ = editor.update(cx, |editor, window, cx| {
 5750        editor.select_all(&SelectAll, window, cx);
 5751        assert_eq!(
 5752            editor.selections.display_ranges(cx),
 5753            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5754        );
 5755    });
 5756}
 5757
 5758#[gpui::test]
 5759fn test_select_line(cx: &mut TestAppContext) {
 5760    init_test(cx, |_| {});
 5761
 5762    let editor = cx.add_window(|window, cx| {
 5763        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5764        build_editor(buffer, window, cx)
 5765    });
 5766    _ = editor.update(cx, |editor, window, cx| {
 5767        editor.change_selections(None, window, cx, |s| {
 5768            s.select_display_ranges([
 5769                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5771                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5772                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5773            ])
 5774        });
 5775        editor.select_line(&SelectLine, window, cx);
 5776        assert_eq!(
 5777            editor.selections.display_ranges(cx),
 5778            vec![
 5779                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5780                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5781            ]
 5782        );
 5783    });
 5784
 5785    _ = editor.update(cx, |editor, window, cx| {
 5786        editor.select_line(&SelectLine, window, cx);
 5787        assert_eq!(
 5788            editor.selections.display_ranges(cx),
 5789            vec![
 5790                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5791                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5792            ]
 5793        );
 5794    });
 5795
 5796    _ = editor.update(cx, |editor, window, cx| {
 5797        editor.select_line(&SelectLine, window, cx);
 5798        assert_eq!(
 5799            editor.selections.display_ranges(cx),
 5800            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5801        );
 5802    });
 5803}
 5804
 5805#[gpui::test]
 5806async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5807    init_test(cx, |_| {});
 5808    let mut cx = EditorTestContext::new(cx).await;
 5809
 5810    #[track_caller]
 5811    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5812        cx.set_state(initial_state);
 5813        cx.update_editor(|e, window, cx| {
 5814            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5815        });
 5816        cx.assert_editor_state(expected_state);
 5817    }
 5818
 5819    // Selection starts and ends at the middle of lines, left-to-right
 5820    test(
 5821        &mut cx,
 5822        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5823        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5824    );
 5825    // Same thing, right-to-left
 5826    test(
 5827        &mut cx,
 5828        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5829        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5830    );
 5831
 5832    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5833    test(
 5834        &mut cx,
 5835        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5836        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5837    );
 5838    // Same thing, right-to-left
 5839    test(
 5840        &mut cx,
 5841        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5842        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5843    );
 5844
 5845    // Whole buffer, left-to-right, last line ends with newline
 5846    test(
 5847        &mut cx,
 5848        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5849        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5850    );
 5851    // Same thing, right-to-left
 5852    test(
 5853        &mut cx,
 5854        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5855        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5856    );
 5857
 5858    // Starts at the end of a line, ends at the start of another
 5859    test(
 5860        &mut cx,
 5861        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5862        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5863    );
 5864}
 5865
 5866#[gpui::test]
 5867async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5868    init_test(cx, |_| {});
 5869
 5870    let editor = cx.add_window(|window, cx| {
 5871        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5872        build_editor(buffer, window, cx)
 5873    });
 5874
 5875    // setup
 5876    _ = editor.update(cx, |editor, window, cx| {
 5877        editor.fold_creases(
 5878            vec![
 5879                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5880                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5881                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5882            ],
 5883            true,
 5884            window,
 5885            cx,
 5886        );
 5887        assert_eq!(
 5888            editor.display_text(cx),
 5889            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5890        );
 5891    });
 5892
 5893    _ = editor.update(cx, |editor, window, cx| {
 5894        editor.change_selections(None, window, cx, |s| {
 5895            s.select_display_ranges([
 5896                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5897                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5898                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5899                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5900            ])
 5901        });
 5902        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5903        assert_eq!(
 5904            editor.display_text(cx),
 5905            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5906        );
 5907    });
 5908    EditorTestContext::for_editor(editor, cx)
 5909        .await
 5910        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5911
 5912    _ = editor.update(cx, |editor, window, cx| {
 5913        editor.change_selections(None, window, cx, |s| {
 5914            s.select_display_ranges([
 5915                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5916            ])
 5917        });
 5918        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5919        assert_eq!(
 5920            editor.display_text(cx),
 5921            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5922        );
 5923        assert_eq!(
 5924            editor.selections.display_ranges(cx),
 5925            [
 5926                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5927                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5928                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5929                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5930                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5931                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5932                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5933            ]
 5934        );
 5935    });
 5936    EditorTestContext::for_editor(editor, cx)
 5937        .await
 5938        .assert_editor_state(
 5939            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5940        );
 5941}
 5942
 5943#[gpui::test]
 5944async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5945    init_test(cx, |_| {});
 5946
 5947    let mut cx = EditorTestContext::new(cx).await;
 5948
 5949    cx.set_state(indoc!(
 5950        r#"abc
 5951           defˇghi
 5952
 5953           jk
 5954           nlmo
 5955           "#
 5956    ));
 5957
 5958    cx.update_editor(|editor, window, cx| {
 5959        editor.add_selection_above(&Default::default(), window, cx);
 5960    });
 5961
 5962    cx.assert_editor_state(indoc!(
 5963        r#"abcˇ
 5964           defˇghi
 5965
 5966           jk
 5967           nlmo
 5968           "#
 5969    ));
 5970
 5971    cx.update_editor(|editor, window, cx| {
 5972        editor.add_selection_above(&Default::default(), window, cx);
 5973    });
 5974
 5975    cx.assert_editor_state(indoc!(
 5976        r#"abcˇ
 5977            defˇghi
 5978
 5979            jk
 5980            nlmo
 5981            "#
 5982    ));
 5983
 5984    cx.update_editor(|editor, window, cx| {
 5985        editor.add_selection_below(&Default::default(), window, cx);
 5986    });
 5987
 5988    cx.assert_editor_state(indoc!(
 5989        r#"abc
 5990           defˇghi
 5991
 5992           jk
 5993           nlmo
 5994           "#
 5995    ));
 5996
 5997    cx.update_editor(|editor, window, cx| {
 5998        editor.undo_selection(&Default::default(), window, cx);
 5999    });
 6000
 6001    cx.assert_editor_state(indoc!(
 6002        r#"abcˇ
 6003           defˇghi
 6004
 6005           jk
 6006           nlmo
 6007           "#
 6008    ));
 6009
 6010    cx.update_editor(|editor, window, cx| {
 6011        editor.redo_selection(&Default::default(), window, cx);
 6012    });
 6013
 6014    cx.assert_editor_state(indoc!(
 6015        r#"abc
 6016           defˇghi
 6017
 6018           jk
 6019           nlmo
 6020           "#
 6021    ));
 6022
 6023    cx.update_editor(|editor, window, cx| {
 6024        editor.add_selection_below(&Default::default(), window, cx);
 6025    });
 6026
 6027    cx.assert_editor_state(indoc!(
 6028        r#"abc
 6029           defˇghi
 6030
 6031           jk
 6032           nlmˇo
 6033           "#
 6034    ));
 6035
 6036    cx.update_editor(|editor, window, cx| {
 6037        editor.add_selection_below(&Default::default(), window, cx);
 6038    });
 6039
 6040    cx.assert_editor_state(indoc!(
 6041        r#"abc
 6042           defˇghi
 6043
 6044           jk
 6045           nlmˇo
 6046           "#
 6047    ));
 6048
 6049    // change selections
 6050    cx.set_state(indoc!(
 6051        r#"abc
 6052           def«ˇg»hi
 6053
 6054           jk
 6055           nlmo
 6056           "#
 6057    ));
 6058
 6059    cx.update_editor(|editor, window, cx| {
 6060        editor.add_selection_below(&Default::default(), window, cx);
 6061    });
 6062
 6063    cx.assert_editor_state(indoc!(
 6064        r#"abc
 6065           def«ˇg»hi
 6066
 6067           jk
 6068           nlm«ˇo»
 6069           "#
 6070    ));
 6071
 6072    cx.update_editor(|editor, window, cx| {
 6073        editor.add_selection_below(&Default::default(), window, cx);
 6074    });
 6075
 6076    cx.assert_editor_state(indoc!(
 6077        r#"abc
 6078           def«ˇg»hi
 6079
 6080           jk
 6081           nlm«ˇo»
 6082           "#
 6083    ));
 6084
 6085    cx.update_editor(|editor, window, cx| {
 6086        editor.add_selection_above(&Default::default(), window, cx);
 6087    });
 6088
 6089    cx.assert_editor_state(indoc!(
 6090        r#"abc
 6091           def«ˇg»hi
 6092
 6093           jk
 6094           nlmo
 6095           "#
 6096    ));
 6097
 6098    cx.update_editor(|editor, window, cx| {
 6099        editor.add_selection_above(&Default::default(), window, cx);
 6100    });
 6101
 6102    cx.assert_editor_state(indoc!(
 6103        r#"abc
 6104           def«ˇg»hi
 6105
 6106           jk
 6107           nlmo
 6108           "#
 6109    ));
 6110
 6111    // Change selections again
 6112    cx.set_state(indoc!(
 6113        r#"a«bc
 6114           defgˇ»hi
 6115
 6116           jk
 6117           nlmo
 6118           "#
 6119    ));
 6120
 6121    cx.update_editor(|editor, window, cx| {
 6122        editor.add_selection_below(&Default::default(), window, cx);
 6123    });
 6124
 6125    cx.assert_editor_state(indoc!(
 6126        r#"a«bcˇ»
 6127           d«efgˇ»hi
 6128
 6129           j«kˇ»
 6130           nlmo
 6131           "#
 6132    ));
 6133
 6134    cx.update_editor(|editor, window, cx| {
 6135        editor.add_selection_below(&Default::default(), window, cx);
 6136    });
 6137    cx.assert_editor_state(indoc!(
 6138        r#"a«bcˇ»
 6139           d«efgˇ»hi
 6140
 6141           j«kˇ»
 6142           n«lmoˇ»
 6143           "#
 6144    ));
 6145    cx.update_editor(|editor, window, cx| {
 6146        editor.add_selection_above(&Default::default(), window, cx);
 6147    });
 6148
 6149    cx.assert_editor_state(indoc!(
 6150        r#"a«bcˇ»
 6151           d«efgˇ»hi
 6152
 6153           j«kˇ»
 6154           nlmo
 6155           "#
 6156    ));
 6157
 6158    // Change selections again
 6159    cx.set_state(indoc!(
 6160        r#"abc
 6161           d«ˇefghi
 6162
 6163           jk
 6164           nlm»o
 6165           "#
 6166    ));
 6167
 6168    cx.update_editor(|editor, window, cx| {
 6169        editor.add_selection_above(&Default::default(), window, cx);
 6170    });
 6171
 6172    cx.assert_editor_state(indoc!(
 6173        r#"a«ˇbc»
 6174           d«ˇef»ghi
 6175
 6176           j«ˇk»
 6177           n«ˇlm»o
 6178           "#
 6179    ));
 6180
 6181    cx.update_editor(|editor, window, cx| {
 6182        editor.add_selection_below(&Default::default(), window, cx);
 6183    });
 6184
 6185    cx.assert_editor_state(indoc!(
 6186        r#"abc
 6187           d«ˇef»ghi
 6188
 6189           j«ˇk»
 6190           n«ˇlm»o
 6191           "#
 6192    ));
 6193}
 6194
 6195#[gpui::test]
 6196async fn test_select_next(cx: &mut TestAppContext) {
 6197    init_test(cx, |_| {});
 6198
 6199    let mut cx = EditorTestContext::new(cx).await;
 6200    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6201
 6202    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6203        .unwrap();
 6204    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6205
 6206    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6207        .unwrap();
 6208    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6209
 6210    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6211    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6212
 6213    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6214    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6215
 6216    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6217        .unwrap();
 6218    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6219
 6220    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6221        .unwrap();
 6222    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6223
 6224    // Test selection direction should be preserved
 6225    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6226
 6227    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6228        .unwrap();
 6229    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6230}
 6231
 6232#[gpui::test]
 6233async fn test_select_all_matches(cx: &mut TestAppContext) {
 6234    init_test(cx, |_| {});
 6235
 6236    let mut cx = EditorTestContext::new(cx).await;
 6237
 6238    // Test caret-only selections
 6239    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6240    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6241        .unwrap();
 6242    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6243
 6244    // Test left-to-right selections
 6245    cx.set_state("abc\n«abcˇ»\nabc");
 6246    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6247        .unwrap();
 6248    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6249
 6250    // Test right-to-left selections
 6251    cx.set_state("abc\n«ˇabc»\nabc");
 6252    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6253        .unwrap();
 6254    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6255
 6256    // Test selecting whitespace with caret selection
 6257    cx.set_state("abc\nˇ   abc\nabc");
 6258    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6259        .unwrap();
 6260    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6261
 6262    // Test selecting whitespace with left-to-right selection
 6263    cx.set_state("abc\n«ˇ  »abc\nabc");
 6264    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6265        .unwrap();
 6266    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6267
 6268    // Test no matches with right-to-left selection
 6269    cx.set_state("abc\n«  ˇ»abc\nabc");
 6270    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6271        .unwrap();
 6272    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6273}
 6274
 6275#[gpui::test]
 6276async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6277    init_test(cx, |_| {});
 6278
 6279    let mut cx = EditorTestContext::new(cx).await;
 6280
 6281    let large_body_1 = "\nd".repeat(200);
 6282    let large_body_2 = "\ne".repeat(200);
 6283
 6284    cx.set_state(&format!(
 6285        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6286    ));
 6287    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6288        let scroll_position = editor.scroll_position(cx);
 6289        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6290        scroll_position
 6291    });
 6292
 6293    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6294        .unwrap();
 6295    cx.assert_editor_state(&format!(
 6296        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6297    ));
 6298    let scroll_position_after_selection =
 6299        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6300    assert_eq!(
 6301        initial_scroll_position, scroll_position_after_selection,
 6302        "Scroll position should not change after selecting all matches"
 6303    );
 6304}
 6305
 6306#[gpui::test]
 6307async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6308    init_test(cx, |_| {});
 6309
 6310    let mut cx = EditorLspTestContext::new_rust(
 6311        lsp::ServerCapabilities {
 6312            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6313            ..Default::default()
 6314        },
 6315        cx,
 6316    )
 6317    .await;
 6318
 6319    cx.set_state(indoc! {"
 6320        line 1
 6321        line 2
 6322        linˇe 3
 6323        line 4
 6324        line 5
 6325    "});
 6326
 6327    // Make an edit
 6328    cx.update_editor(|editor, window, cx| {
 6329        editor.handle_input("X", window, cx);
 6330    });
 6331
 6332    // Move cursor to a different position
 6333    cx.update_editor(|editor, window, cx| {
 6334        editor.change_selections(None, window, cx, |s| {
 6335            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6336        });
 6337    });
 6338
 6339    cx.assert_editor_state(indoc! {"
 6340        line 1
 6341        line 2
 6342        linXe 3
 6343        line 4
 6344        liˇne 5
 6345    "});
 6346
 6347    cx.lsp
 6348        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6349            Ok(Some(vec![lsp::TextEdit::new(
 6350                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6351                "PREFIX ".to_string(),
 6352            )]))
 6353        });
 6354
 6355    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6356        .unwrap()
 6357        .await
 6358        .unwrap();
 6359
 6360    cx.assert_editor_state(indoc! {"
 6361        PREFIX line 1
 6362        line 2
 6363        linXe 3
 6364        line 4
 6365        liˇne 5
 6366    "});
 6367
 6368    // Undo formatting
 6369    cx.update_editor(|editor, window, cx| {
 6370        editor.undo(&Default::default(), window, cx);
 6371    });
 6372
 6373    // Verify cursor moved back to position after edit
 6374    cx.assert_editor_state(indoc! {"
 6375        line 1
 6376        line 2
 6377        linXˇe 3
 6378        line 4
 6379        line 5
 6380    "});
 6381}
 6382
 6383#[gpui::test]
 6384async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6385    init_test(cx, |_| {});
 6386
 6387    let mut cx = EditorTestContext::new(cx).await;
 6388    cx.set_state(
 6389        r#"let foo = 2;
 6390lˇet foo = 2;
 6391let fooˇ = 2;
 6392let foo = 2;
 6393let foo = ˇ2;"#,
 6394    );
 6395
 6396    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6397        .unwrap();
 6398    cx.assert_editor_state(
 6399        r#"let foo = 2;
 6400«letˇ» foo = 2;
 6401let «fooˇ» = 2;
 6402let foo = 2;
 6403let foo = «2ˇ»;"#,
 6404    );
 6405
 6406    // noop for multiple selections with different contents
 6407    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6408        .unwrap();
 6409    cx.assert_editor_state(
 6410        r#"let foo = 2;
 6411«letˇ» foo = 2;
 6412let «fooˇ» = 2;
 6413let foo = 2;
 6414let foo = «2ˇ»;"#,
 6415    );
 6416
 6417    // Test last selection direction should be preserved
 6418    cx.set_state(
 6419        r#"let foo = 2;
 6420let foo = 2;
 6421let «fooˇ» = 2;
 6422let «ˇfoo» = 2;
 6423let foo = 2;"#,
 6424    );
 6425
 6426    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6427        .unwrap();
 6428    cx.assert_editor_state(
 6429        r#"let foo = 2;
 6430let foo = 2;
 6431let «fooˇ» = 2;
 6432let «ˇfoo» = 2;
 6433let «ˇfoo» = 2;"#,
 6434    );
 6435}
 6436
 6437#[gpui::test]
 6438async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6439    init_test(cx, |_| {});
 6440
 6441    let mut cx =
 6442        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6443
 6444    cx.assert_editor_state(indoc! {"
 6445        ˇbbb
 6446        ccc
 6447
 6448        bbb
 6449        ccc
 6450        "});
 6451    cx.dispatch_action(SelectPrevious::default());
 6452    cx.assert_editor_state(indoc! {"
 6453                «bbbˇ»
 6454                ccc
 6455
 6456                bbb
 6457                ccc
 6458                "});
 6459    cx.dispatch_action(SelectPrevious::default());
 6460    cx.assert_editor_state(indoc! {"
 6461                «bbbˇ»
 6462                ccc
 6463
 6464                «bbbˇ»
 6465                ccc
 6466                "});
 6467}
 6468
 6469#[gpui::test]
 6470async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6471    init_test(cx, |_| {});
 6472
 6473    let mut cx = EditorTestContext::new(cx).await;
 6474    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6475
 6476    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6477        .unwrap();
 6478    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6479
 6480    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6481        .unwrap();
 6482    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6483
 6484    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6485    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6486
 6487    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6488    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6489
 6490    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6491        .unwrap();
 6492    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6493
 6494    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6495        .unwrap();
 6496    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6497}
 6498
 6499#[gpui::test]
 6500async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6501    init_test(cx, |_| {});
 6502
 6503    let mut cx = EditorTestContext::new(cx).await;
 6504    cx.set_state("");
 6505
 6506    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6507        .unwrap();
 6508    cx.assert_editor_state("«aˇ»");
 6509    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6510        .unwrap();
 6511    cx.assert_editor_state("«aˇ»");
 6512}
 6513
 6514#[gpui::test]
 6515async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6516    init_test(cx, |_| {});
 6517
 6518    let mut cx = EditorTestContext::new(cx).await;
 6519    cx.set_state(
 6520        r#"let foo = 2;
 6521lˇet foo = 2;
 6522let fooˇ = 2;
 6523let foo = 2;
 6524let foo = ˇ2;"#,
 6525    );
 6526
 6527    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6528        .unwrap();
 6529    cx.assert_editor_state(
 6530        r#"let foo = 2;
 6531«letˇ» foo = 2;
 6532let «fooˇ» = 2;
 6533let foo = 2;
 6534let foo = «2ˇ»;"#,
 6535    );
 6536
 6537    // noop for multiple selections with different contents
 6538    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6539        .unwrap();
 6540    cx.assert_editor_state(
 6541        r#"let foo = 2;
 6542«letˇ» foo = 2;
 6543let «fooˇ» = 2;
 6544let foo = 2;
 6545let foo = «2ˇ»;"#,
 6546    );
 6547}
 6548
 6549#[gpui::test]
 6550async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6551    init_test(cx, |_| {});
 6552
 6553    let mut cx = EditorTestContext::new(cx).await;
 6554    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6555
 6556    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6557        .unwrap();
 6558    // selection direction is preserved
 6559    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6560
 6561    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6562        .unwrap();
 6563    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6564
 6565    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6566    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6567
 6568    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6569    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6570
 6571    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6572        .unwrap();
 6573    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6574
 6575    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6576        .unwrap();
 6577    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6578}
 6579
 6580#[gpui::test]
 6581async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6582    init_test(cx, |_| {});
 6583
 6584    let language = Arc::new(Language::new(
 6585        LanguageConfig::default(),
 6586        Some(tree_sitter_rust::LANGUAGE.into()),
 6587    ));
 6588
 6589    let text = r#"
 6590        use mod1::mod2::{mod3, mod4};
 6591
 6592        fn fn_1(param1: bool, param2: &str) {
 6593            let var1 = "text";
 6594        }
 6595    "#
 6596    .unindent();
 6597
 6598    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6599    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6600    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6601
 6602    editor
 6603        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6604        .await;
 6605
 6606    editor.update_in(cx, |editor, window, cx| {
 6607        editor.change_selections(None, window, cx, |s| {
 6608            s.select_display_ranges([
 6609                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6610                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6611                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6612            ]);
 6613        });
 6614        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6615    });
 6616    editor.update(cx, |editor, cx| {
 6617        assert_text_with_selections(
 6618            editor,
 6619            indoc! {r#"
 6620                use mod1::mod2::{mod3, «mod4ˇ»};
 6621
 6622                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6623                    let var1 = "«ˇtext»";
 6624                }
 6625            "#},
 6626            cx,
 6627        );
 6628    });
 6629
 6630    editor.update_in(cx, |editor, window, cx| {
 6631        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6632    });
 6633    editor.update(cx, |editor, cx| {
 6634        assert_text_with_selections(
 6635            editor,
 6636            indoc! {r#"
 6637                use mod1::mod2::«{mod3, mod4}ˇ»;
 6638
 6639                «ˇfn fn_1(param1: bool, param2: &str) {
 6640                    let var1 = "text";
 6641 6642            "#},
 6643            cx,
 6644        );
 6645    });
 6646
 6647    editor.update_in(cx, |editor, window, cx| {
 6648        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6649    });
 6650    assert_eq!(
 6651        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6652        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6653    );
 6654
 6655    // Trying to expand the selected syntax node one more time has no effect.
 6656    editor.update_in(cx, |editor, window, cx| {
 6657        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6658    });
 6659    assert_eq!(
 6660        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6661        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6662    );
 6663
 6664    editor.update_in(cx, |editor, window, cx| {
 6665        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6666    });
 6667    editor.update(cx, |editor, cx| {
 6668        assert_text_with_selections(
 6669            editor,
 6670            indoc! {r#"
 6671                use mod1::mod2::«{mod3, mod4}ˇ»;
 6672
 6673                «ˇfn fn_1(param1: bool, param2: &str) {
 6674                    let var1 = "text";
 6675 6676            "#},
 6677            cx,
 6678        );
 6679    });
 6680
 6681    editor.update_in(cx, |editor, window, cx| {
 6682        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6683    });
 6684    editor.update(cx, |editor, cx| {
 6685        assert_text_with_selections(
 6686            editor,
 6687            indoc! {r#"
 6688                use mod1::mod2::{mod3, «mod4ˇ»};
 6689
 6690                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6691                    let var1 = "«ˇtext»";
 6692                }
 6693            "#},
 6694            cx,
 6695        );
 6696    });
 6697
 6698    editor.update_in(cx, |editor, window, cx| {
 6699        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6700    });
 6701    editor.update(cx, |editor, cx| {
 6702        assert_text_with_selections(
 6703            editor,
 6704            indoc! {r#"
 6705                use mod1::mod2::{mod3, mo«ˇ»d4};
 6706
 6707                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6708                    let var1 = "te«ˇ»xt";
 6709                }
 6710            "#},
 6711            cx,
 6712        );
 6713    });
 6714
 6715    // Trying to shrink the selected syntax node one more time has no effect.
 6716    editor.update_in(cx, |editor, window, cx| {
 6717        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6718    });
 6719    editor.update_in(cx, |editor, _, cx| {
 6720        assert_text_with_selections(
 6721            editor,
 6722            indoc! {r#"
 6723                use mod1::mod2::{mod3, mo«ˇ»d4};
 6724
 6725                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6726                    let var1 = "te«ˇ»xt";
 6727                }
 6728            "#},
 6729            cx,
 6730        );
 6731    });
 6732
 6733    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6734    // a fold.
 6735    editor.update_in(cx, |editor, window, cx| {
 6736        editor.fold_creases(
 6737            vec![
 6738                Crease::simple(
 6739                    Point::new(0, 21)..Point::new(0, 24),
 6740                    FoldPlaceholder::test(),
 6741                ),
 6742                Crease::simple(
 6743                    Point::new(3, 20)..Point::new(3, 22),
 6744                    FoldPlaceholder::test(),
 6745                ),
 6746            ],
 6747            true,
 6748            window,
 6749            cx,
 6750        );
 6751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6752    });
 6753    editor.update(cx, |editor, cx| {
 6754        assert_text_with_selections(
 6755            editor,
 6756            indoc! {r#"
 6757                use mod1::mod2::«{mod3, mod4}ˇ»;
 6758
 6759                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6760                    let var1 = "«ˇtext»";
 6761                }
 6762            "#},
 6763            cx,
 6764        );
 6765    });
 6766}
 6767
 6768#[gpui::test]
 6769async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6770    init_test(cx, |_| {});
 6771
 6772    let language = Arc::new(Language::new(
 6773        LanguageConfig::default(),
 6774        Some(tree_sitter_rust::LANGUAGE.into()),
 6775    ));
 6776
 6777    let text = "let a = 2;";
 6778
 6779    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6780    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6781    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6782
 6783    editor
 6784        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6785        .await;
 6786
 6787    // Test case 1: Cursor at end of word
 6788    editor.update_in(cx, |editor, window, cx| {
 6789        editor.change_selections(None, window, cx, |s| {
 6790            s.select_display_ranges([
 6791                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6792            ]);
 6793        });
 6794    });
 6795    editor.update(cx, |editor, cx| {
 6796        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6797    });
 6798    editor.update_in(cx, |editor, window, cx| {
 6799        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6800    });
 6801    editor.update(cx, |editor, cx| {
 6802        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 6803    });
 6804    editor.update_in(cx, |editor, window, cx| {
 6805        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6806    });
 6807    editor.update(cx, |editor, cx| {
 6808        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6809    });
 6810
 6811    // Test case 2: Cursor at end of statement
 6812    editor.update_in(cx, |editor, window, cx| {
 6813        editor.change_selections(None, window, cx, |s| {
 6814            s.select_display_ranges([
 6815                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 6816            ]);
 6817        });
 6818    });
 6819    editor.update(cx, |editor, cx| {
 6820        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 6821    });
 6822    editor.update_in(cx, |editor, window, cx| {
 6823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6824    });
 6825    editor.update(cx, |editor, cx| {
 6826        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6827    });
 6828}
 6829
 6830#[gpui::test]
 6831async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6832    init_test(cx, |_| {});
 6833
 6834    let language = Arc::new(Language::new(
 6835        LanguageConfig::default(),
 6836        Some(tree_sitter_rust::LANGUAGE.into()),
 6837    ));
 6838
 6839    let text = r#"
 6840        use mod1::mod2::{mod3, mod4};
 6841
 6842        fn fn_1(param1: bool, param2: &str) {
 6843            let var1 = "hello world";
 6844        }
 6845    "#
 6846    .unindent();
 6847
 6848    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6849    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6850    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6851
 6852    editor
 6853        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6854        .await;
 6855
 6856    // Test 1: Cursor on a letter of a string word
 6857    editor.update_in(cx, |editor, window, cx| {
 6858        editor.change_selections(None, window, cx, |s| {
 6859            s.select_display_ranges([
 6860                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6861            ]);
 6862        });
 6863    });
 6864    editor.update_in(cx, |editor, window, cx| {
 6865        assert_text_with_selections(
 6866            editor,
 6867            indoc! {r#"
 6868                use mod1::mod2::{mod3, mod4};
 6869
 6870                fn fn_1(param1: bool, param2: &str) {
 6871                    let var1 = "hˇello world";
 6872                }
 6873            "#},
 6874            cx,
 6875        );
 6876        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6877        assert_text_with_selections(
 6878            editor,
 6879            indoc! {r#"
 6880                use mod1::mod2::{mod3, mod4};
 6881
 6882                fn fn_1(param1: bool, param2: &str) {
 6883                    let var1 = "«ˇhello» world";
 6884                }
 6885            "#},
 6886            cx,
 6887        );
 6888    });
 6889
 6890    // Test 2: Partial selection within a word
 6891    editor.update_in(cx, |editor, window, cx| {
 6892        editor.change_selections(None, window, cx, |s| {
 6893            s.select_display_ranges([
 6894                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 6895            ]);
 6896        });
 6897    });
 6898    editor.update_in(cx, |editor, window, cx| {
 6899        assert_text_with_selections(
 6900            editor,
 6901            indoc! {r#"
 6902                use mod1::mod2::{mod3, mod4};
 6903
 6904                fn fn_1(param1: bool, param2: &str) {
 6905                    let var1 = "h«elˇ»lo world";
 6906                }
 6907            "#},
 6908            cx,
 6909        );
 6910        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6911        assert_text_with_selections(
 6912            editor,
 6913            indoc! {r#"
 6914                use mod1::mod2::{mod3, mod4};
 6915
 6916                fn fn_1(param1: bool, param2: &str) {
 6917                    let var1 = "«ˇhello» world";
 6918                }
 6919            "#},
 6920            cx,
 6921        );
 6922    });
 6923
 6924    // Test 3: Complete word already selected
 6925    editor.update_in(cx, |editor, window, cx| {
 6926        editor.change_selections(None, window, cx, |s| {
 6927            s.select_display_ranges([
 6928                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 6929            ]);
 6930        });
 6931    });
 6932    editor.update_in(cx, |editor, window, cx| {
 6933        assert_text_with_selections(
 6934            editor,
 6935            indoc! {r#"
 6936                use mod1::mod2::{mod3, mod4};
 6937
 6938                fn fn_1(param1: bool, param2: &str) {
 6939                    let var1 = "«helloˇ» world";
 6940                }
 6941            "#},
 6942            cx,
 6943        );
 6944        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6945        assert_text_with_selections(
 6946            editor,
 6947            indoc! {r#"
 6948                use mod1::mod2::{mod3, mod4};
 6949
 6950                fn fn_1(param1: bool, param2: &str) {
 6951                    let var1 = "«hello worldˇ»";
 6952                }
 6953            "#},
 6954            cx,
 6955        );
 6956    });
 6957
 6958    // Test 4: Selection spanning across words
 6959    editor.update_in(cx, |editor, window, cx| {
 6960        editor.change_selections(None, window, cx, |s| {
 6961            s.select_display_ranges([
 6962                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 6963            ]);
 6964        });
 6965    });
 6966    editor.update_in(cx, |editor, window, cx| {
 6967        assert_text_with_selections(
 6968            editor,
 6969            indoc! {r#"
 6970                use mod1::mod2::{mod3, mod4};
 6971
 6972                fn fn_1(param1: bool, param2: &str) {
 6973                    let var1 = "hel«lo woˇ»rld";
 6974                }
 6975            "#},
 6976            cx,
 6977        );
 6978        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6979        assert_text_with_selections(
 6980            editor,
 6981            indoc! {r#"
 6982                use mod1::mod2::{mod3, mod4};
 6983
 6984                fn fn_1(param1: bool, param2: &str) {
 6985                    let var1 = "«ˇhello world»";
 6986                }
 6987            "#},
 6988            cx,
 6989        );
 6990    });
 6991
 6992    // Test 5: Expansion beyond string
 6993    editor.update_in(cx, |editor, window, cx| {
 6994        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6995        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6996        assert_text_with_selections(
 6997            editor,
 6998            indoc! {r#"
 6999                use mod1::mod2::{mod3, mod4};
 7000
 7001                fn fn_1(param1: bool, param2: &str) {
 7002                    «ˇlet var1 = "hello world";»
 7003                }
 7004            "#},
 7005            cx,
 7006        );
 7007    });
 7008}
 7009
 7010#[gpui::test]
 7011async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7012    init_test(cx, |_| {});
 7013
 7014    let base_text = r#"
 7015        impl A {
 7016            // this is an uncommitted comment
 7017
 7018            fn b() {
 7019                c();
 7020            }
 7021
 7022            // this is another uncommitted comment
 7023
 7024            fn d() {
 7025                // e
 7026                // f
 7027            }
 7028        }
 7029
 7030        fn g() {
 7031            // h
 7032        }
 7033    "#
 7034    .unindent();
 7035
 7036    let text = r#"
 7037        ˇimpl A {
 7038
 7039            fn b() {
 7040                c();
 7041            }
 7042
 7043            fn d() {
 7044                // e
 7045                // f
 7046            }
 7047        }
 7048
 7049        fn g() {
 7050            // h
 7051        }
 7052    "#
 7053    .unindent();
 7054
 7055    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7056    cx.set_state(&text);
 7057    cx.set_head_text(&base_text);
 7058    cx.update_editor(|editor, window, cx| {
 7059        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7060    });
 7061
 7062    cx.assert_state_with_diff(
 7063        "
 7064        ˇimpl A {
 7065      -     // this is an uncommitted comment
 7066
 7067            fn b() {
 7068                c();
 7069            }
 7070
 7071      -     // this is another uncommitted comment
 7072      -
 7073            fn d() {
 7074                // e
 7075                // f
 7076            }
 7077        }
 7078
 7079        fn g() {
 7080            // h
 7081        }
 7082    "
 7083        .unindent(),
 7084    );
 7085
 7086    let expected_display_text = "
 7087        impl A {
 7088            // this is an uncommitted comment
 7089
 7090            fn b() {
 7091 7092            }
 7093
 7094            // this is another uncommitted comment
 7095
 7096            fn d() {
 7097 7098            }
 7099        }
 7100
 7101        fn g() {
 7102 7103        }
 7104        "
 7105    .unindent();
 7106
 7107    cx.update_editor(|editor, window, cx| {
 7108        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7109        assert_eq!(editor.display_text(cx), expected_display_text);
 7110    });
 7111}
 7112
 7113#[gpui::test]
 7114async fn test_autoindent(cx: &mut TestAppContext) {
 7115    init_test(cx, |_| {});
 7116
 7117    let language = Arc::new(
 7118        Language::new(
 7119            LanguageConfig {
 7120                brackets: BracketPairConfig {
 7121                    pairs: vec![
 7122                        BracketPair {
 7123                            start: "{".to_string(),
 7124                            end: "}".to_string(),
 7125                            close: false,
 7126                            surround: false,
 7127                            newline: true,
 7128                        },
 7129                        BracketPair {
 7130                            start: "(".to_string(),
 7131                            end: ")".to_string(),
 7132                            close: false,
 7133                            surround: false,
 7134                            newline: true,
 7135                        },
 7136                    ],
 7137                    ..Default::default()
 7138                },
 7139                ..Default::default()
 7140            },
 7141            Some(tree_sitter_rust::LANGUAGE.into()),
 7142        )
 7143        .with_indents_query(
 7144            r#"
 7145                (_ "(" ")" @end) @indent
 7146                (_ "{" "}" @end) @indent
 7147            "#,
 7148        )
 7149        .unwrap(),
 7150    );
 7151
 7152    let text = "fn a() {}";
 7153
 7154    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7155    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7156    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7157    editor
 7158        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7159        .await;
 7160
 7161    editor.update_in(cx, |editor, window, cx| {
 7162        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7163        editor.newline(&Newline, window, cx);
 7164        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7165        assert_eq!(
 7166            editor.selections.ranges(cx),
 7167            &[
 7168                Point::new(1, 4)..Point::new(1, 4),
 7169                Point::new(3, 4)..Point::new(3, 4),
 7170                Point::new(5, 0)..Point::new(5, 0)
 7171            ]
 7172        );
 7173    });
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179
 7180    {
 7181        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7182        cx.set_state(indoc! {"
 7183            impl A {
 7184
 7185                fn b() {}
 7186
 7187            «fn c() {
 7188
 7189            }ˇ»
 7190            }
 7191        "});
 7192
 7193        cx.update_editor(|editor, window, cx| {
 7194            editor.autoindent(&Default::default(), window, cx);
 7195        });
 7196
 7197        cx.assert_editor_state(indoc! {"
 7198            impl A {
 7199
 7200                fn b() {}
 7201
 7202                «fn c() {
 7203
 7204                }ˇ»
 7205            }
 7206        "});
 7207    }
 7208
 7209    {
 7210        let mut cx = EditorTestContext::new_multibuffer(
 7211            cx,
 7212            [indoc! { "
 7213                impl A {
 7214                «
 7215                // a
 7216                fn b(){}
 7217                »
 7218                «
 7219                    }
 7220                    fn c(){}
 7221                »
 7222            "}],
 7223        );
 7224
 7225        let buffer = cx.update_editor(|editor, _, cx| {
 7226            let buffer = editor.buffer().update(cx, |buffer, _| {
 7227                buffer.all_buffers().iter().next().unwrap().clone()
 7228            });
 7229            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7230            buffer
 7231        });
 7232
 7233        cx.run_until_parked();
 7234        cx.update_editor(|editor, window, cx| {
 7235            editor.select_all(&Default::default(), window, cx);
 7236            editor.autoindent(&Default::default(), window, cx)
 7237        });
 7238        cx.run_until_parked();
 7239
 7240        cx.update(|_, cx| {
 7241            assert_eq!(
 7242                buffer.read(cx).text(),
 7243                indoc! { "
 7244                    impl A {
 7245
 7246                        // a
 7247                        fn b(){}
 7248
 7249
 7250                    }
 7251                    fn c(){}
 7252
 7253                " }
 7254            )
 7255        });
 7256    }
 7257}
 7258
 7259#[gpui::test]
 7260async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7261    init_test(cx, |_| {});
 7262
 7263    let mut cx = EditorTestContext::new(cx).await;
 7264
 7265    let language = Arc::new(Language::new(
 7266        LanguageConfig {
 7267            brackets: BracketPairConfig {
 7268                pairs: vec![
 7269                    BracketPair {
 7270                        start: "{".to_string(),
 7271                        end: "}".to_string(),
 7272                        close: true,
 7273                        surround: true,
 7274                        newline: true,
 7275                    },
 7276                    BracketPair {
 7277                        start: "(".to_string(),
 7278                        end: ")".to_string(),
 7279                        close: true,
 7280                        surround: true,
 7281                        newline: true,
 7282                    },
 7283                    BracketPair {
 7284                        start: "/*".to_string(),
 7285                        end: " */".to_string(),
 7286                        close: true,
 7287                        surround: true,
 7288                        newline: true,
 7289                    },
 7290                    BracketPair {
 7291                        start: "[".to_string(),
 7292                        end: "]".to_string(),
 7293                        close: false,
 7294                        surround: false,
 7295                        newline: true,
 7296                    },
 7297                    BracketPair {
 7298                        start: "\"".to_string(),
 7299                        end: "\"".to_string(),
 7300                        close: true,
 7301                        surround: true,
 7302                        newline: false,
 7303                    },
 7304                    BracketPair {
 7305                        start: "<".to_string(),
 7306                        end: ">".to_string(),
 7307                        close: false,
 7308                        surround: true,
 7309                        newline: true,
 7310                    },
 7311                ],
 7312                ..Default::default()
 7313            },
 7314            autoclose_before: "})]".to_string(),
 7315            ..Default::default()
 7316        },
 7317        Some(tree_sitter_rust::LANGUAGE.into()),
 7318    ));
 7319
 7320    cx.language_registry().add(language.clone());
 7321    cx.update_buffer(|buffer, cx| {
 7322        buffer.set_language(Some(language), cx);
 7323    });
 7324
 7325    cx.set_state(
 7326        &r#"
 7327            🏀ˇ
 7328            εˇ
 7329            ❤️ˇ
 7330        "#
 7331        .unindent(),
 7332    );
 7333
 7334    // autoclose multiple nested brackets at multiple cursors
 7335    cx.update_editor(|editor, window, cx| {
 7336        editor.handle_input("{", window, cx);
 7337        editor.handle_input("{", window, cx);
 7338        editor.handle_input("{", window, cx);
 7339    });
 7340    cx.assert_editor_state(
 7341        &"
 7342            🏀{{{ˇ}}}
 7343            ε{{{ˇ}}}
 7344            ❤️{{{ˇ}}}
 7345        "
 7346        .unindent(),
 7347    );
 7348
 7349    // insert a different closing bracket
 7350    cx.update_editor(|editor, window, cx| {
 7351        editor.handle_input(")", window, cx);
 7352    });
 7353    cx.assert_editor_state(
 7354        &"
 7355            🏀{{{)ˇ}}}
 7356            ε{{{)ˇ}}}
 7357            ❤️{{{)ˇ}}}
 7358        "
 7359        .unindent(),
 7360    );
 7361
 7362    // skip over the auto-closed brackets when typing a closing bracket
 7363    cx.update_editor(|editor, window, cx| {
 7364        editor.move_right(&MoveRight, window, cx);
 7365        editor.handle_input("}", window, cx);
 7366        editor.handle_input("}", window, cx);
 7367        editor.handle_input("}", window, cx);
 7368    });
 7369    cx.assert_editor_state(
 7370        &"
 7371            🏀{{{)}}}}ˇ
 7372            ε{{{)}}}}ˇ
 7373            ❤️{{{)}}}}ˇ
 7374        "
 7375        .unindent(),
 7376    );
 7377
 7378    // autoclose multi-character pairs
 7379    cx.set_state(
 7380        &"
 7381            ˇ
 7382            ˇ
 7383        "
 7384        .unindent(),
 7385    );
 7386    cx.update_editor(|editor, window, cx| {
 7387        editor.handle_input("/", window, cx);
 7388        editor.handle_input("*", window, cx);
 7389    });
 7390    cx.assert_editor_state(
 7391        &"
 7392            /*ˇ */
 7393            /*ˇ */
 7394        "
 7395        .unindent(),
 7396    );
 7397
 7398    // one cursor autocloses a multi-character pair, one cursor
 7399    // does not autoclose.
 7400    cx.set_state(
 7401        &"
 7402 7403            ˇ
 7404        "
 7405        .unindent(),
 7406    );
 7407    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7408    cx.assert_editor_state(
 7409        &"
 7410            /*ˇ */
 7411 7412        "
 7413        .unindent(),
 7414    );
 7415
 7416    // Don't autoclose if the next character isn't whitespace and isn't
 7417    // listed in the language's "autoclose_before" section.
 7418    cx.set_state("ˇa b");
 7419    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7420    cx.assert_editor_state("{ˇa b");
 7421
 7422    // Don't autoclose if `close` is false for the bracket pair
 7423    cx.set_state("ˇ");
 7424    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7425    cx.assert_editor_state("");
 7426
 7427    // Surround with brackets if text is selected
 7428    cx.set_state("«aˇ» b");
 7429    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7430    cx.assert_editor_state("{«aˇ»} b");
 7431
 7432    // Autoclose when not immediately after a word character
 7433    cx.set_state("a ˇ");
 7434    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7435    cx.assert_editor_state("a \"ˇ\"");
 7436
 7437    // Autoclose pair where the start and end characters are the same
 7438    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7439    cx.assert_editor_state("a \"\"ˇ");
 7440
 7441    // Don't autoclose when immediately after a word character
 7442    cx.set_state("");
 7443    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7444    cx.assert_editor_state("a\"ˇ");
 7445
 7446    // Do autoclose when after a non-word character
 7447    cx.set_state("");
 7448    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7449    cx.assert_editor_state("{\"ˇ\"");
 7450
 7451    // Non identical pairs autoclose regardless of preceding character
 7452    cx.set_state("");
 7453    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7454    cx.assert_editor_state("a{ˇ}");
 7455
 7456    // Don't autoclose pair if autoclose is disabled
 7457    cx.set_state("ˇ");
 7458    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7459    cx.assert_editor_state("");
 7460
 7461    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7462    cx.set_state("«aˇ» b");
 7463    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7464    cx.assert_editor_state("<«aˇ»> b");
 7465}
 7466
 7467#[gpui::test]
 7468async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7469    init_test(cx, |settings| {
 7470        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7471    });
 7472
 7473    let mut cx = EditorTestContext::new(cx).await;
 7474
 7475    let language = Arc::new(Language::new(
 7476        LanguageConfig {
 7477            brackets: BracketPairConfig {
 7478                pairs: vec![
 7479                    BracketPair {
 7480                        start: "{".to_string(),
 7481                        end: "}".to_string(),
 7482                        close: true,
 7483                        surround: true,
 7484                        newline: true,
 7485                    },
 7486                    BracketPair {
 7487                        start: "(".to_string(),
 7488                        end: ")".to_string(),
 7489                        close: true,
 7490                        surround: true,
 7491                        newline: true,
 7492                    },
 7493                    BracketPair {
 7494                        start: "[".to_string(),
 7495                        end: "]".to_string(),
 7496                        close: false,
 7497                        surround: false,
 7498                        newline: true,
 7499                    },
 7500                ],
 7501                ..Default::default()
 7502            },
 7503            autoclose_before: "})]".to_string(),
 7504            ..Default::default()
 7505        },
 7506        Some(tree_sitter_rust::LANGUAGE.into()),
 7507    ));
 7508
 7509    cx.language_registry().add(language.clone());
 7510    cx.update_buffer(|buffer, cx| {
 7511        buffer.set_language(Some(language), cx);
 7512    });
 7513
 7514    cx.set_state(
 7515        &"
 7516            ˇ
 7517            ˇ
 7518            ˇ
 7519        "
 7520        .unindent(),
 7521    );
 7522
 7523    // ensure only matching closing brackets are skipped over
 7524    cx.update_editor(|editor, window, cx| {
 7525        editor.handle_input("}", window, cx);
 7526        editor.move_left(&MoveLeft, window, cx);
 7527        editor.handle_input(")", window, cx);
 7528        editor.move_left(&MoveLeft, window, cx);
 7529    });
 7530    cx.assert_editor_state(
 7531        &"
 7532            ˇ)}
 7533            ˇ)}
 7534            ˇ)}
 7535        "
 7536        .unindent(),
 7537    );
 7538
 7539    // skip-over closing brackets at multiple cursors
 7540    cx.update_editor(|editor, window, cx| {
 7541        editor.handle_input(")", window, cx);
 7542        editor.handle_input("}", window, cx);
 7543    });
 7544    cx.assert_editor_state(
 7545        &"
 7546            )}ˇ
 7547            )}ˇ
 7548            )}ˇ
 7549        "
 7550        .unindent(),
 7551    );
 7552
 7553    // ignore non-close brackets
 7554    cx.update_editor(|editor, window, cx| {
 7555        editor.handle_input("]", window, cx);
 7556        editor.move_left(&MoveLeft, window, cx);
 7557        editor.handle_input("]", window, cx);
 7558    });
 7559    cx.assert_editor_state(
 7560        &"
 7561            )}]ˇ]
 7562            )}]ˇ]
 7563            )}]ˇ]
 7564        "
 7565        .unindent(),
 7566    );
 7567}
 7568
 7569#[gpui::test]
 7570async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7571    init_test(cx, |_| {});
 7572
 7573    let mut cx = EditorTestContext::new(cx).await;
 7574
 7575    let html_language = Arc::new(
 7576        Language::new(
 7577            LanguageConfig {
 7578                name: "HTML".into(),
 7579                brackets: BracketPairConfig {
 7580                    pairs: vec![
 7581                        BracketPair {
 7582                            start: "<".into(),
 7583                            end: ">".into(),
 7584                            close: true,
 7585                            ..Default::default()
 7586                        },
 7587                        BracketPair {
 7588                            start: "{".into(),
 7589                            end: "}".into(),
 7590                            close: true,
 7591                            ..Default::default()
 7592                        },
 7593                        BracketPair {
 7594                            start: "(".into(),
 7595                            end: ")".into(),
 7596                            close: true,
 7597                            ..Default::default()
 7598                        },
 7599                    ],
 7600                    ..Default::default()
 7601                },
 7602                autoclose_before: "})]>".into(),
 7603                ..Default::default()
 7604            },
 7605            Some(tree_sitter_html::LANGUAGE.into()),
 7606        )
 7607        .with_injection_query(
 7608            r#"
 7609            (script_element
 7610                (raw_text) @injection.content
 7611                (#set! injection.language "javascript"))
 7612            "#,
 7613        )
 7614        .unwrap(),
 7615    );
 7616
 7617    let javascript_language = Arc::new(Language::new(
 7618        LanguageConfig {
 7619            name: "JavaScript".into(),
 7620            brackets: BracketPairConfig {
 7621                pairs: vec![
 7622                    BracketPair {
 7623                        start: "/*".into(),
 7624                        end: " */".into(),
 7625                        close: true,
 7626                        ..Default::default()
 7627                    },
 7628                    BracketPair {
 7629                        start: "{".into(),
 7630                        end: "}".into(),
 7631                        close: true,
 7632                        ..Default::default()
 7633                    },
 7634                    BracketPair {
 7635                        start: "(".into(),
 7636                        end: ")".into(),
 7637                        close: true,
 7638                        ..Default::default()
 7639                    },
 7640                ],
 7641                ..Default::default()
 7642            },
 7643            autoclose_before: "})]>".into(),
 7644            ..Default::default()
 7645        },
 7646        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7647    ));
 7648
 7649    cx.language_registry().add(html_language.clone());
 7650    cx.language_registry().add(javascript_language.clone());
 7651
 7652    cx.update_buffer(|buffer, cx| {
 7653        buffer.set_language(Some(html_language), cx);
 7654    });
 7655
 7656    cx.set_state(
 7657        &r#"
 7658            <body>ˇ
 7659                <script>
 7660                    var x = 1;ˇ
 7661                </script>
 7662            </body>ˇ
 7663        "#
 7664        .unindent(),
 7665    );
 7666
 7667    // Precondition: different languages are active at different locations.
 7668    cx.update_editor(|editor, window, cx| {
 7669        let snapshot = editor.snapshot(window, cx);
 7670        let cursors = editor.selections.ranges::<usize>(cx);
 7671        let languages = cursors
 7672            .iter()
 7673            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7674            .collect::<Vec<_>>();
 7675        assert_eq!(
 7676            languages,
 7677            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7678        );
 7679    });
 7680
 7681    // Angle brackets autoclose in HTML, but not JavaScript.
 7682    cx.update_editor(|editor, window, cx| {
 7683        editor.handle_input("<", window, cx);
 7684        editor.handle_input("a", window, cx);
 7685    });
 7686    cx.assert_editor_state(
 7687        &r#"
 7688            <body><aˇ>
 7689                <script>
 7690                    var x = 1;<aˇ
 7691                </script>
 7692            </body><aˇ>
 7693        "#
 7694        .unindent(),
 7695    );
 7696
 7697    // Curly braces and parens autoclose in both HTML and JavaScript.
 7698    cx.update_editor(|editor, window, cx| {
 7699        editor.handle_input(" b=", window, cx);
 7700        editor.handle_input("{", window, cx);
 7701        editor.handle_input("c", window, cx);
 7702        editor.handle_input("(", window, cx);
 7703    });
 7704    cx.assert_editor_state(
 7705        &r#"
 7706            <body><a b={c(ˇ)}>
 7707                <script>
 7708                    var x = 1;<a b={c(ˇ)}
 7709                </script>
 7710            </body><a b={c(ˇ)}>
 7711        "#
 7712        .unindent(),
 7713    );
 7714
 7715    // Brackets that were already autoclosed are skipped.
 7716    cx.update_editor(|editor, window, cx| {
 7717        editor.handle_input(")", window, cx);
 7718        editor.handle_input("d", window, cx);
 7719        editor.handle_input("}", window, cx);
 7720    });
 7721    cx.assert_editor_state(
 7722        &r#"
 7723            <body><a b={c()d}ˇ>
 7724                <script>
 7725                    var x = 1;<a b={c()d}ˇ
 7726                </script>
 7727            </body><a b={c()d}ˇ>
 7728        "#
 7729        .unindent(),
 7730    );
 7731    cx.update_editor(|editor, window, cx| {
 7732        editor.handle_input(">", window, cx);
 7733    });
 7734    cx.assert_editor_state(
 7735        &r#"
 7736            <body><a b={c()d}>ˇ
 7737                <script>
 7738                    var x = 1;<a b={c()d}>ˇ
 7739                </script>
 7740            </body><a b={c()d}>ˇ
 7741        "#
 7742        .unindent(),
 7743    );
 7744
 7745    // Reset
 7746    cx.set_state(
 7747        &r#"
 7748            <body>ˇ
 7749                <script>
 7750                    var x = 1;ˇ
 7751                </script>
 7752            </body>ˇ
 7753        "#
 7754        .unindent(),
 7755    );
 7756
 7757    cx.update_editor(|editor, window, cx| {
 7758        editor.handle_input("<", window, cx);
 7759    });
 7760    cx.assert_editor_state(
 7761        &r#"
 7762            <body><ˇ>
 7763                <script>
 7764                    var x = 1;<ˇ
 7765                </script>
 7766            </body><ˇ>
 7767        "#
 7768        .unindent(),
 7769    );
 7770
 7771    // When backspacing, the closing angle brackets are removed.
 7772    cx.update_editor(|editor, window, cx| {
 7773        editor.backspace(&Backspace, window, cx);
 7774    });
 7775    cx.assert_editor_state(
 7776        &r#"
 7777            <body>ˇ
 7778                <script>
 7779                    var x = 1;ˇ
 7780                </script>
 7781            </body>ˇ
 7782        "#
 7783        .unindent(),
 7784    );
 7785
 7786    // Block comments autoclose in JavaScript, but not HTML.
 7787    cx.update_editor(|editor, window, cx| {
 7788        editor.handle_input("/", window, cx);
 7789        editor.handle_input("*", window, cx);
 7790    });
 7791    cx.assert_editor_state(
 7792        &r#"
 7793            <body>/*ˇ
 7794                <script>
 7795                    var x = 1;/*ˇ */
 7796                </script>
 7797            </body>/*ˇ
 7798        "#
 7799        .unindent(),
 7800    );
 7801}
 7802
 7803#[gpui::test]
 7804async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7805    init_test(cx, |_| {});
 7806
 7807    let mut cx = EditorTestContext::new(cx).await;
 7808
 7809    let rust_language = Arc::new(
 7810        Language::new(
 7811            LanguageConfig {
 7812                name: "Rust".into(),
 7813                brackets: serde_json::from_value(json!([
 7814                    { "start": "{", "end": "}", "close": true, "newline": true },
 7815                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7816                ]))
 7817                .unwrap(),
 7818                autoclose_before: "})]>".into(),
 7819                ..Default::default()
 7820            },
 7821            Some(tree_sitter_rust::LANGUAGE.into()),
 7822        )
 7823        .with_override_query("(string_literal) @string")
 7824        .unwrap(),
 7825    );
 7826
 7827    cx.language_registry().add(rust_language.clone());
 7828    cx.update_buffer(|buffer, cx| {
 7829        buffer.set_language(Some(rust_language), cx);
 7830    });
 7831
 7832    cx.set_state(
 7833        &r#"
 7834            let x = ˇ
 7835        "#
 7836        .unindent(),
 7837    );
 7838
 7839    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.handle_input("\"", window, cx);
 7842    });
 7843    cx.assert_editor_state(
 7844        &r#"
 7845            let x = "ˇ"
 7846        "#
 7847        .unindent(),
 7848    );
 7849
 7850    // Inserting another quotation mark. The cursor moves across the existing
 7851    // automatically-inserted quotation mark.
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.handle_input("\"", window, cx);
 7854    });
 7855    cx.assert_editor_state(
 7856        &r#"
 7857            let x = ""ˇ
 7858        "#
 7859        .unindent(),
 7860    );
 7861
 7862    // Reset
 7863    cx.set_state(
 7864        &r#"
 7865            let x = ˇ
 7866        "#
 7867        .unindent(),
 7868    );
 7869
 7870    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7871    cx.update_editor(|editor, window, cx| {
 7872        editor.handle_input("\"", window, cx);
 7873        editor.handle_input(" ", window, cx);
 7874        editor.move_left(&Default::default(), window, cx);
 7875        editor.handle_input("\\", window, cx);
 7876        editor.handle_input("\"", window, cx);
 7877    });
 7878    cx.assert_editor_state(
 7879        &r#"
 7880            let x = "\"ˇ "
 7881        "#
 7882        .unindent(),
 7883    );
 7884
 7885    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7886    // mark. Nothing is inserted.
 7887    cx.update_editor(|editor, window, cx| {
 7888        editor.move_right(&Default::default(), window, cx);
 7889        editor.handle_input("\"", window, cx);
 7890    });
 7891    cx.assert_editor_state(
 7892        &r#"
 7893            let x = "\" "ˇ
 7894        "#
 7895        .unindent(),
 7896    );
 7897}
 7898
 7899#[gpui::test]
 7900async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7901    init_test(cx, |_| {});
 7902
 7903    let language = Arc::new(Language::new(
 7904        LanguageConfig {
 7905            brackets: BracketPairConfig {
 7906                pairs: vec![
 7907                    BracketPair {
 7908                        start: "{".to_string(),
 7909                        end: "}".to_string(),
 7910                        close: true,
 7911                        surround: true,
 7912                        newline: true,
 7913                    },
 7914                    BracketPair {
 7915                        start: "/* ".to_string(),
 7916                        end: "*/".to_string(),
 7917                        close: true,
 7918                        surround: true,
 7919                        ..Default::default()
 7920                    },
 7921                ],
 7922                ..Default::default()
 7923            },
 7924            ..Default::default()
 7925        },
 7926        Some(tree_sitter_rust::LANGUAGE.into()),
 7927    ));
 7928
 7929    let text = r#"
 7930        a
 7931        b
 7932        c
 7933    "#
 7934    .unindent();
 7935
 7936    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7937    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7938    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7939    editor
 7940        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7941        .await;
 7942
 7943    editor.update_in(cx, |editor, window, cx| {
 7944        editor.change_selections(None, window, cx, |s| {
 7945            s.select_display_ranges([
 7946                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7947                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7948                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7949            ])
 7950        });
 7951
 7952        editor.handle_input("{", window, cx);
 7953        editor.handle_input("{", window, cx);
 7954        editor.handle_input("{", window, cx);
 7955        assert_eq!(
 7956            editor.text(cx),
 7957            "
 7958                {{{a}}}
 7959                {{{b}}}
 7960                {{{c}}}
 7961            "
 7962            .unindent()
 7963        );
 7964        assert_eq!(
 7965            editor.selections.display_ranges(cx),
 7966            [
 7967                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7968                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7969                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7970            ]
 7971        );
 7972
 7973        editor.undo(&Undo, window, cx);
 7974        editor.undo(&Undo, window, cx);
 7975        editor.undo(&Undo, window, cx);
 7976        assert_eq!(
 7977            editor.text(cx),
 7978            "
 7979                a
 7980                b
 7981                c
 7982            "
 7983            .unindent()
 7984        );
 7985        assert_eq!(
 7986            editor.selections.display_ranges(cx),
 7987            [
 7988                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7989                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7990                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7991            ]
 7992        );
 7993
 7994        // Ensure inserting the first character of a multi-byte bracket pair
 7995        // doesn't surround the selections with the bracket.
 7996        editor.handle_input("/", window, cx);
 7997        assert_eq!(
 7998            editor.text(cx),
 7999            "
 8000                /
 8001                /
 8002                /
 8003            "
 8004            .unindent()
 8005        );
 8006        assert_eq!(
 8007            editor.selections.display_ranges(cx),
 8008            [
 8009                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8010                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8011                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8012            ]
 8013        );
 8014
 8015        editor.undo(&Undo, window, cx);
 8016        assert_eq!(
 8017            editor.text(cx),
 8018            "
 8019                a
 8020                b
 8021                c
 8022            "
 8023            .unindent()
 8024        );
 8025        assert_eq!(
 8026            editor.selections.display_ranges(cx),
 8027            [
 8028                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8029                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8030                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8031            ]
 8032        );
 8033
 8034        // Ensure inserting the last character of a multi-byte bracket pair
 8035        // doesn't surround the selections with the bracket.
 8036        editor.handle_input("*", window, cx);
 8037        assert_eq!(
 8038            editor.text(cx),
 8039            "
 8040                *
 8041                *
 8042                *
 8043            "
 8044            .unindent()
 8045        );
 8046        assert_eq!(
 8047            editor.selections.display_ranges(cx),
 8048            [
 8049                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8050                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8051                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8052            ]
 8053        );
 8054    });
 8055}
 8056
 8057#[gpui::test]
 8058async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8059    init_test(cx, |_| {});
 8060
 8061    let language = Arc::new(Language::new(
 8062        LanguageConfig {
 8063            brackets: BracketPairConfig {
 8064                pairs: vec![BracketPair {
 8065                    start: "{".to_string(),
 8066                    end: "}".to_string(),
 8067                    close: true,
 8068                    surround: true,
 8069                    newline: true,
 8070                }],
 8071                ..Default::default()
 8072            },
 8073            autoclose_before: "}".to_string(),
 8074            ..Default::default()
 8075        },
 8076        Some(tree_sitter_rust::LANGUAGE.into()),
 8077    ));
 8078
 8079    let text = r#"
 8080        a
 8081        b
 8082        c
 8083    "#
 8084    .unindent();
 8085
 8086    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8087    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8088    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8089    editor
 8090        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8091        .await;
 8092
 8093    editor.update_in(cx, |editor, window, cx| {
 8094        editor.change_selections(None, window, cx, |s| {
 8095            s.select_ranges([
 8096                Point::new(0, 1)..Point::new(0, 1),
 8097                Point::new(1, 1)..Point::new(1, 1),
 8098                Point::new(2, 1)..Point::new(2, 1),
 8099            ])
 8100        });
 8101
 8102        editor.handle_input("{", window, cx);
 8103        editor.handle_input("{", window, cx);
 8104        editor.handle_input("_", window, cx);
 8105        assert_eq!(
 8106            editor.text(cx),
 8107            "
 8108                a{{_}}
 8109                b{{_}}
 8110                c{{_}}
 8111            "
 8112            .unindent()
 8113        );
 8114        assert_eq!(
 8115            editor.selections.ranges::<Point>(cx),
 8116            [
 8117                Point::new(0, 4)..Point::new(0, 4),
 8118                Point::new(1, 4)..Point::new(1, 4),
 8119                Point::new(2, 4)..Point::new(2, 4)
 8120            ]
 8121        );
 8122
 8123        editor.backspace(&Default::default(), window, cx);
 8124        editor.backspace(&Default::default(), window, cx);
 8125        assert_eq!(
 8126            editor.text(cx),
 8127            "
 8128                a{}
 8129                b{}
 8130                c{}
 8131            "
 8132            .unindent()
 8133        );
 8134        assert_eq!(
 8135            editor.selections.ranges::<Point>(cx),
 8136            [
 8137                Point::new(0, 2)..Point::new(0, 2),
 8138                Point::new(1, 2)..Point::new(1, 2),
 8139                Point::new(2, 2)..Point::new(2, 2)
 8140            ]
 8141        );
 8142
 8143        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8144        assert_eq!(
 8145            editor.text(cx),
 8146            "
 8147                a
 8148                b
 8149                c
 8150            "
 8151            .unindent()
 8152        );
 8153        assert_eq!(
 8154            editor.selections.ranges::<Point>(cx),
 8155            [
 8156                Point::new(0, 1)..Point::new(0, 1),
 8157                Point::new(1, 1)..Point::new(1, 1),
 8158                Point::new(2, 1)..Point::new(2, 1)
 8159            ]
 8160        );
 8161    });
 8162}
 8163
 8164#[gpui::test]
 8165async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8166    init_test(cx, |settings| {
 8167        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8168    });
 8169
 8170    let mut cx = EditorTestContext::new(cx).await;
 8171
 8172    let language = Arc::new(Language::new(
 8173        LanguageConfig {
 8174            brackets: BracketPairConfig {
 8175                pairs: vec![
 8176                    BracketPair {
 8177                        start: "{".to_string(),
 8178                        end: "}".to_string(),
 8179                        close: true,
 8180                        surround: true,
 8181                        newline: true,
 8182                    },
 8183                    BracketPair {
 8184                        start: "(".to_string(),
 8185                        end: ")".to_string(),
 8186                        close: true,
 8187                        surround: true,
 8188                        newline: true,
 8189                    },
 8190                    BracketPair {
 8191                        start: "[".to_string(),
 8192                        end: "]".to_string(),
 8193                        close: false,
 8194                        surround: true,
 8195                        newline: true,
 8196                    },
 8197                ],
 8198                ..Default::default()
 8199            },
 8200            autoclose_before: "})]".to_string(),
 8201            ..Default::default()
 8202        },
 8203        Some(tree_sitter_rust::LANGUAGE.into()),
 8204    ));
 8205
 8206    cx.language_registry().add(language.clone());
 8207    cx.update_buffer(|buffer, cx| {
 8208        buffer.set_language(Some(language), cx);
 8209    });
 8210
 8211    cx.set_state(
 8212        &"
 8213            {(ˇ)}
 8214            [[ˇ]]
 8215            {(ˇ)}
 8216        "
 8217        .unindent(),
 8218    );
 8219
 8220    cx.update_editor(|editor, window, cx| {
 8221        editor.backspace(&Default::default(), window, cx);
 8222        editor.backspace(&Default::default(), window, cx);
 8223    });
 8224
 8225    cx.assert_editor_state(
 8226        &"
 8227            ˇ
 8228            ˇ]]
 8229            ˇ
 8230        "
 8231        .unindent(),
 8232    );
 8233
 8234    cx.update_editor(|editor, window, cx| {
 8235        editor.handle_input("{", window, cx);
 8236        editor.handle_input("{", window, cx);
 8237        editor.move_right(&MoveRight, window, cx);
 8238        editor.move_right(&MoveRight, window, cx);
 8239        editor.move_left(&MoveLeft, window, cx);
 8240        editor.move_left(&MoveLeft, window, cx);
 8241        editor.backspace(&Default::default(), window, cx);
 8242    });
 8243
 8244    cx.assert_editor_state(
 8245        &"
 8246            {ˇ}
 8247            {ˇ}]]
 8248            {ˇ}
 8249        "
 8250        .unindent(),
 8251    );
 8252
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.backspace(&Default::default(), window, cx);
 8255    });
 8256
 8257    cx.assert_editor_state(
 8258        &"
 8259            ˇ
 8260            ˇ]]
 8261            ˇ
 8262        "
 8263        .unindent(),
 8264    );
 8265}
 8266
 8267#[gpui::test]
 8268async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8269    init_test(cx, |_| {});
 8270
 8271    let language = Arc::new(Language::new(
 8272        LanguageConfig::default(),
 8273        Some(tree_sitter_rust::LANGUAGE.into()),
 8274    ));
 8275
 8276    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8277    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8278    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8279    editor
 8280        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8281        .await;
 8282
 8283    editor.update_in(cx, |editor, window, cx| {
 8284        editor.set_auto_replace_emoji_shortcode(true);
 8285
 8286        editor.handle_input("Hello ", window, cx);
 8287        editor.handle_input(":wave", window, cx);
 8288        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8289
 8290        editor.handle_input(":", window, cx);
 8291        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8292
 8293        editor.handle_input(" :smile", window, cx);
 8294        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8295
 8296        editor.handle_input(":", window, cx);
 8297        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8298
 8299        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8300        editor.handle_input(":wave", window, cx);
 8301        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8302
 8303        editor.handle_input(":", window, cx);
 8304        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8305
 8306        editor.handle_input(":1", window, cx);
 8307        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8308
 8309        editor.handle_input(":", window, cx);
 8310        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8311
 8312        // Ensure shortcode does not get replaced when it is part of a word
 8313        editor.handle_input(" Test:wave", window, cx);
 8314        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8315
 8316        editor.handle_input(":", window, cx);
 8317        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8318
 8319        editor.set_auto_replace_emoji_shortcode(false);
 8320
 8321        // Ensure shortcode does not get replaced when auto replace is off
 8322        editor.handle_input(" :wave", window, cx);
 8323        assert_eq!(
 8324            editor.text(cx),
 8325            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8326        );
 8327
 8328        editor.handle_input(":", window, cx);
 8329        assert_eq!(
 8330            editor.text(cx),
 8331            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8332        );
 8333    });
 8334}
 8335
 8336#[gpui::test]
 8337async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8338    init_test(cx, |_| {});
 8339
 8340    let (text, insertion_ranges) = marked_text_ranges(
 8341        indoc! {"
 8342            ˇ
 8343        "},
 8344        false,
 8345    );
 8346
 8347    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8348    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8349
 8350    _ = editor.update_in(cx, |editor, window, cx| {
 8351        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8352
 8353        editor
 8354            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8355            .unwrap();
 8356
 8357        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8358            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8359            assert_eq!(editor.text(cx), expected_text);
 8360            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8361        }
 8362
 8363        assert(
 8364            editor,
 8365            cx,
 8366            indoc! {"
 8367            type «» =•
 8368            "},
 8369        );
 8370
 8371        assert!(editor.context_menu_visible(), "There should be a matches");
 8372    });
 8373}
 8374
 8375#[gpui::test]
 8376async fn test_snippets(cx: &mut TestAppContext) {
 8377    init_test(cx, |_| {});
 8378
 8379    let (text, insertion_ranges) = marked_text_ranges(
 8380        indoc! {"
 8381            a.ˇ b
 8382            a.ˇ b
 8383            a.ˇ b
 8384        "},
 8385        false,
 8386    );
 8387
 8388    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8390
 8391    editor.update_in(cx, |editor, window, cx| {
 8392        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8393
 8394        editor
 8395            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8396            .unwrap();
 8397
 8398        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8399            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8400            assert_eq!(editor.text(cx), expected_text);
 8401            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8402        }
 8403
 8404        assert(
 8405            editor,
 8406            cx,
 8407            indoc! {"
 8408                a.f(«one», two, «three») b
 8409                a.f(«one», two, «three») b
 8410                a.f(«one», two, «three») b
 8411            "},
 8412        );
 8413
 8414        // Can't move earlier than the first tab stop
 8415        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 8416        assert(
 8417            editor,
 8418            cx,
 8419            indoc! {"
 8420                a.f(«one», two, «three») b
 8421                a.f(«one», two, «three») b
 8422                a.f(«one», two, «three») b
 8423            "},
 8424        );
 8425
 8426        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8427        assert(
 8428            editor,
 8429            cx,
 8430            indoc! {"
 8431                a.f(one, «two», three) b
 8432                a.f(one, «two», three) b
 8433                a.f(one, «two», three) b
 8434            "},
 8435        );
 8436
 8437        editor.move_to_prev_snippet_tabstop(window, cx);
 8438        assert(
 8439            editor,
 8440            cx,
 8441            indoc! {"
 8442                a.f(«one», two, «three») b
 8443                a.f(«one», two, «three») b
 8444                a.f(«one», two, «three») b
 8445            "},
 8446        );
 8447
 8448        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8449        assert(
 8450            editor,
 8451            cx,
 8452            indoc! {"
 8453                a.f(one, «two», three) b
 8454                a.f(one, «two», three) b
 8455                a.f(one, «two», three) b
 8456            "},
 8457        );
 8458        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8459        assert(
 8460            editor,
 8461            cx,
 8462            indoc! {"
 8463                a.f(one, two, three)ˇ b
 8464                a.f(one, two, three)ˇ b
 8465                a.f(one, two, three)ˇ b
 8466            "},
 8467        );
 8468
 8469        // As soon as the last tab stop is reached, snippet state is gone
 8470        editor.move_to_prev_snippet_tabstop(window, cx);
 8471        assert(
 8472            editor,
 8473            cx,
 8474            indoc! {"
 8475                a.f(one, two, three)ˇ b
 8476                a.f(one, two, three)ˇ b
 8477                a.f(one, two, three)ˇ b
 8478            "},
 8479        );
 8480    });
 8481}
 8482
 8483#[gpui::test]
 8484async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8485    init_test(cx, |_| {});
 8486
 8487    let fs = FakeFs::new(cx.executor());
 8488    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8489
 8490    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8491
 8492    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8493    language_registry.add(rust_lang());
 8494    let mut fake_servers = language_registry.register_fake_lsp(
 8495        "Rust",
 8496        FakeLspAdapter {
 8497            capabilities: lsp::ServerCapabilities {
 8498                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8499                ..Default::default()
 8500            },
 8501            ..Default::default()
 8502        },
 8503    );
 8504
 8505    let buffer = project
 8506        .update(cx, |project, cx| {
 8507            project.open_local_buffer(path!("/file.rs"), cx)
 8508        })
 8509        .await
 8510        .unwrap();
 8511
 8512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8513    let (editor, cx) = cx.add_window_view(|window, cx| {
 8514        build_editor_with_project(project.clone(), buffer, window, cx)
 8515    });
 8516    editor.update_in(cx, |editor, window, cx| {
 8517        editor.set_text("one\ntwo\nthree\n", window, cx)
 8518    });
 8519    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8520
 8521    cx.executor().start_waiting();
 8522    let fake_server = fake_servers.next().await.unwrap();
 8523
 8524    {
 8525        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8526            move |params, _| async move {
 8527                assert_eq!(
 8528                    params.text_document.uri,
 8529                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8530                );
 8531                assert_eq!(params.options.tab_size, 4);
 8532                Ok(Some(vec![lsp::TextEdit::new(
 8533                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8534                    ", ".to_string(),
 8535                )]))
 8536            },
 8537        );
 8538        let save = editor
 8539            .update_in(cx, |editor, window, cx| {
 8540                editor.save(true, project.clone(), window, cx)
 8541            })
 8542            .unwrap();
 8543        cx.executor().start_waiting();
 8544        save.await;
 8545
 8546        assert_eq!(
 8547            editor.update(cx, |editor, cx| editor.text(cx)),
 8548            "one, two\nthree\n"
 8549        );
 8550        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8551    }
 8552
 8553    {
 8554        editor.update_in(cx, |editor, window, cx| {
 8555            editor.set_text("one\ntwo\nthree\n", window, cx)
 8556        });
 8557        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8558
 8559        // Ensure we can still save even if formatting hangs.
 8560        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8561            move |params, _| async move {
 8562                assert_eq!(
 8563                    params.text_document.uri,
 8564                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8565                );
 8566                futures::future::pending::<()>().await;
 8567                unreachable!()
 8568            },
 8569        );
 8570        let save = editor
 8571            .update_in(cx, |editor, window, cx| {
 8572                editor.save(true, project.clone(), window, cx)
 8573            })
 8574            .unwrap();
 8575        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8576        cx.executor().start_waiting();
 8577        save.await;
 8578        assert_eq!(
 8579            editor.update(cx, |editor, cx| editor.text(cx)),
 8580            "one\ntwo\nthree\n"
 8581        );
 8582    }
 8583
 8584    // For non-dirty buffer, no formatting request should be sent
 8585    {
 8586        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8587
 8588        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8589            panic!("Should not be invoked on non-dirty buffer");
 8590        });
 8591        let save = editor
 8592            .update_in(cx, |editor, window, cx| {
 8593                editor.save(true, project.clone(), window, cx)
 8594            })
 8595            .unwrap();
 8596        cx.executor().start_waiting();
 8597        save.await;
 8598    }
 8599
 8600    // Set rust language override and assert overridden tabsize is sent to language server
 8601    update_test_language_settings(cx, |settings| {
 8602        settings.languages.insert(
 8603            "Rust".into(),
 8604            LanguageSettingsContent {
 8605                tab_size: NonZeroU32::new(8),
 8606                ..Default::default()
 8607            },
 8608        );
 8609    });
 8610
 8611    {
 8612        editor.update_in(cx, |editor, window, cx| {
 8613            editor.set_text("somehting_new\n", window, cx)
 8614        });
 8615        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8616        let _formatting_request_signal = fake_server
 8617            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8618                assert_eq!(
 8619                    params.text_document.uri,
 8620                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8621                );
 8622                assert_eq!(params.options.tab_size, 8);
 8623                Ok(Some(vec![]))
 8624            });
 8625        let save = editor
 8626            .update_in(cx, |editor, window, cx| {
 8627                editor.save(true, project.clone(), window, cx)
 8628            })
 8629            .unwrap();
 8630        cx.executor().start_waiting();
 8631        save.await;
 8632    }
 8633}
 8634
 8635#[gpui::test]
 8636async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8637    init_test(cx, |_| {});
 8638
 8639    let cols = 4;
 8640    let rows = 10;
 8641    let sample_text_1 = sample_text(rows, cols, 'a');
 8642    assert_eq!(
 8643        sample_text_1,
 8644        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8645    );
 8646    let sample_text_2 = sample_text(rows, cols, 'l');
 8647    assert_eq!(
 8648        sample_text_2,
 8649        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8650    );
 8651    let sample_text_3 = sample_text(rows, cols, 'v');
 8652    assert_eq!(
 8653        sample_text_3,
 8654        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8655    );
 8656
 8657    let fs = FakeFs::new(cx.executor());
 8658    fs.insert_tree(
 8659        path!("/a"),
 8660        json!({
 8661            "main.rs": sample_text_1,
 8662            "other.rs": sample_text_2,
 8663            "lib.rs": sample_text_3,
 8664        }),
 8665    )
 8666    .await;
 8667
 8668    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8669    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8670    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8671
 8672    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8673    language_registry.add(rust_lang());
 8674    let mut fake_servers = language_registry.register_fake_lsp(
 8675        "Rust",
 8676        FakeLspAdapter {
 8677            capabilities: lsp::ServerCapabilities {
 8678                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8679                ..Default::default()
 8680            },
 8681            ..Default::default()
 8682        },
 8683    );
 8684
 8685    let worktree = project.update(cx, |project, cx| {
 8686        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8687        assert_eq!(worktrees.len(), 1);
 8688        worktrees.pop().unwrap()
 8689    });
 8690    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8691
 8692    let buffer_1 = project
 8693        .update(cx, |project, cx| {
 8694            project.open_buffer((worktree_id, "main.rs"), cx)
 8695        })
 8696        .await
 8697        .unwrap();
 8698    let buffer_2 = project
 8699        .update(cx, |project, cx| {
 8700            project.open_buffer((worktree_id, "other.rs"), cx)
 8701        })
 8702        .await
 8703        .unwrap();
 8704    let buffer_3 = project
 8705        .update(cx, |project, cx| {
 8706            project.open_buffer((worktree_id, "lib.rs"), cx)
 8707        })
 8708        .await
 8709        .unwrap();
 8710
 8711    let multi_buffer = cx.new(|cx| {
 8712        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8713        multi_buffer.push_excerpts(
 8714            buffer_1.clone(),
 8715            [
 8716                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8717                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8718                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8719            ],
 8720            cx,
 8721        );
 8722        multi_buffer.push_excerpts(
 8723            buffer_2.clone(),
 8724            [
 8725                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8726                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8727                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8728            ],
 8729            cx,
 8730        );
 8731        multi_buffer.push_excerpts(
 8732            buffer_3.clone(),
 8733            [
 8734                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8735                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8736                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8737            ],
 8738            cx,
 8739        );
 8740        multi_buffer
 8741    });
 8742    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8743        Editor::new(
 8744            EditorMode::full(),
 8745            multi_buffer,
 8746            Some(project.clone()),
 8747            window,
 8748            cx,
 8749        )
 8750    });
 8751
 8752    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8753        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8754            s.select_ranges(Some(1..2))
 8755        });
 8756        editor.insert("|one|two|three|", window, cx);
 8757    });
 8758    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8759    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8760        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8761            s.select_ranges(Some(60..70))
 8762        });
 8763        editor.insert("|four|five|six|", window, cx);
 8764    });
 8765    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8766
 8767    // First two buffers should be edited, but not the third one.
 8768    assert_eq!(
 8769        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8770        "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}",
 8771    );
 8772    buffer_1.update(cx, |buffer, _| {
 8773        assert!(buffer.is_dirty());
 8774        assert_eq!(
 8775            buffer.text(),
 8776            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8777        )
 8778    });
 8779    buffer_2.update(cx, |buffer, _| {
 8780        assert!(buffer.is_dirty());
 8781        assert_eq!(
 8782            buffer.text(),
 8783            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8784        )
 8785    });
 8786    buffer_3.update(cx, |buffer, _| {
 8787        assert!(!buffer.is_dirty());
 8788        assert_eq!(buffer.text(), sample_text_3,)
 8789    });
 8790    cx.executor().run_until_parked();
 8791
 8792    cx.executor().start_waiting();
 8793    let save = multi_buffer_editor
 8794        .update_in(cx, |editor, window, cx| {
 8795            editor.save(true, project.clone(), window, cx)
 8796        })
 8797        .unwrap();
 8798
 8799    let fake_server = fake_servers.next().await.unwrap();
 8800    fake_server
 8801        .server
 8802        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8803            Ok(Some(vec![lsp::TextEdit::new(
 8804                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8805                format!("[{} formatted]", params.text_document.uri),
 8806            )]))
 8807        })
 8808        .detach();
 8809    save.await;
 8810
 8811    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8812    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8813    assert_eq!(
 8814        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8815        uri!(
 8816            "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}"
 8817        ),
 8818    );
 8819    buffer_1.update(cx, |buffer, _| {
 8820        assert!(!buffer.is_dirty());
 8821        assert_eq!(
 8822            buffer.text(),
 8823            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8824        )
 8825    });
 8826    buffer_2.update(cx, |buffer, _| {
 8827        assert!(!buffer.is_dirty());
 8828        assert_eq!(
 8829            buffer.text(),
 8830            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8831        )
 8832    });
 8833    buffer_3.update(cx, |buffer, _| {
 8834        assert!(!buffer.is_dirty());
 8835        assert_eq!(buffer.text(), sample_text_3,)
 8836    });
 8837}
 8838
 8839#[gpui::test]
 8840async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8841    init_test(cx, |_| {});
 8842
 8843    let fs = FakeFs::new(cx.executor());
 8844    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8845
 8846    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8847
 8848    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8849    language_registry.add(rust_lang());
 8850    let mut fake_servers = language_registry.register_fake_lsp(
 8851        "Rust",
 8852        FakeLspAdapter {
 8853            capabilities: lsp::ServerCapabilities {
 8854                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8855                ..Default::default()
 8856            },
 8857            ..Default::default()
 8858        },
 8859    );
 8860
 8861    let buffer = project
 8862        .update(cx, |project, cx| {
 8863            project.open_local_buffer(path!("/file.rs"), cx)
 8864        })
 8865        .await
 8866        .unwrap();
 8867
 8868    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8869    let (editor, cx) = cx.add_window_view(|window, cx| {
 8870        build_editor_with_project(project.clone(), buffer, window, cx)
 8871    });
 8872    editor.update_in(cx, |editor, window, cx| {
 8873        editor.set_text("one\ntwo\nthree\n", window, cx)
 8874    });
 8875    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8876
 8877    cx.executor().start_waiting();
 8878    let fake_server = fake_servers.next().await.unwrap();
 8879
 8880    let save = editor
 8881        .update_in(cx, |editor, window, cx| {
 8882            editor.save(true, project.clone(), window, cx)
 8883        })
 8884        .unwrap();
 8885    fake_server
 8886        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8887            assert_eq!(
 8888                params.text_document.uri,
 8889                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8890            );
 8891            assert_eq!(params.options.tab_size, 4);
 8892            Ok(Some(vec![lsp::TextEdit::new(
 8893                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8894                ", ".to_string(),
 8895            )]))
 8896        })
 8897        .next()
 8898        .await;
 8899    cx.executor().start_waiting();
 8900    save.await;
 8901    assert_eq!(
 8902        editor.update(cx, |editor, cx| editor.text(cx)),
 8903        "one, two\nthree\n"
 8904    );
 8905    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.set_text("one\ntwo\nthree\n", window, cx)
 8909    });
 8910    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8911
 8912    // Ensure we can still save even if formatting hangs.
 8913    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8914        move |params, _| async move {
 8915            assert_eq!(
 8916                params.text_document.uri,
 8917                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8918            );
 8919            futures::future::pending::<()>().await;
 8920            unreachable!()
 8921        },
 8922    );
 8923    let save = editor
 8924        .update_in(cx, |editor, window, cx| {
 8925            editor.save(true, project.clone(), window, cx)
 8926        })
 8927        .unwrap();
 8928    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8929    cx.executor().start_waiting();
 8930    save.await;
 8931    assert_eq!(
 8932        editor.update(cx, |editor, cx| editor.text(cx)),
 8933        "one\ntwo\nthree\n"
 8934    );
 8935    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8936
 8937    // For non-dirty buffer, no formatting request should be sent
 8938    let save = editor
 8939        .update_in(cx, |editor, window, cx| {
 8940            editor.save(true, project.clone(), window, cx)
 8941        })
 8942        .unwrap();
 8943    let _pending_format_request = fake_server
 8944        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8945            panic!("Should not be invoked on non-dirty buffer");
 8946        })
 8947        .next();
 8948    cx.executor().start_waiting();
 8949    save.await;
 8950
 8951    // Set Rust language override and assert overridden tabsize is sent to language server
 8952    update_test_language_settings(cx, |settings| {
 8953        settings.languages.insert(
 8954            "Rust".into(),
 8955            LanguageSettingsContent {
 8956                tab_size: NonZeroU32::new(8),
 8957                ..Default::default()
 8958            },
 8959        );
 8960    });
 8961
 8962    editor.update_in(cx, |editor, window, cx| {
 8963        editor.set_text("somehting_new\n", window, cx)
 8964    });
 8965    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8966    let save = editor
 8967        .update_in(cx, |editor, window, cx| {
 8968            editor.save(true, project.clone(), window, cx)
 8969        })
 8970        .unwrap();
 8971    fake_server
 8972        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8973            assert_eq!(
 8974                params.text_document.uri,
 8975                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8976            );
 8977            assert_eq!(params.options.tab_size, 8);
 8978            Ok(Some(vec![]))
 8979        })
 8980        .next()
 8981        .await;
 8982    cx.executor().start_waiting();
 8983    save.await;
 8984}
 8985
 8986#[gpui::test]
 8987async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8988    init_test(cx, |settings| {
 8989        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8990            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8991        ))
 8992    });
 8993
 8994    let fs = FakeFs::new(cx.executor());
 8995    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8996
 8997    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8998
 8999    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9000    language_registry.add(Arc::new(Language::new(
 9001        LanguageConfig {
 9002            name: "Rust".into(),
 9003            matcher: LanguageMatcher {
 9004                path_suffixes: vec!["rs".to_string()],
 9005                ..Default::default()
 9006            },
 9007            ..LanguageConfig::default()
 9008        },
 9009        Some(tree_sitter_rust::LANGUAGE.into()),
 9010    )));
 9011    update_test_language_settings(cx, |settings| {
 9012        // Enable Prettier formatting for the same buffer, and ensure
 9013        // LSP is called instead of Prettier.
 9014        settings.defaults.prettier = Some(PrettierSettings {
 9015            allowed: true,
 9016            ..PrettierSettings::default()
 9017        });
 9018    });
 9019    let mut fake_servers = language_registry.register_fake_lsp(
 9020        "Rust",
 9021        FakeLspAdapter {
 9022            capabilities: lsp::ServerCapabilities {
 9023                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9024                ..Default::default()
 9025            },
 9026            ..Default::default()
 9027        },
 9028    );
 9029
 9030    let buffer = project
 9031        .update(cx, |project, cx| {
 9032            project.open_local_buffer(path!("/file.rs"), cx)
 9033        })
 9034        .await
 9035        .unwrap();
 9036
 9037    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9038    let (editor, cx) = cx.add_window_view(|window, cx| {
 9039        build_editor_with_project(project.clone(), buffer, window, cx)
 9040    });
 9041    editor.update_in(cx, |editor, window, cx| {
 9042        editor.set_text("one\ntwo\nthree\n", window, cx)
 9043    });
 9044
 9045    cx.executor().start_waiting();
 9046    let fake_server = fake_servers.next().await.unwrap();
 9047
 9048    let format = editor
 9049        .update_in(cx, |editor, window, cx| {
 9050            editor.perform_format(
 9051                project.clone(),
 9052                FormatTrigger::Manual,
 9053                FormatTarget::Buffers,
 9054                window,
 9055                cx,
 9056            )
 9057        })
 9058        .unwrap();
 9059    fake_server
 9060        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9061            assert_eq!(
 9062                params.text_document.uri,
 9063                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9064            );
 9065            assert_eq!(params.options.tab_size, 4);
 9066            Ok(Some(vec![lsp::TextEdit::new(
 9067                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9068                ", ".to_string(),
 9069            )]))
 9070        })
 9071        .next()
 9072        .await;
 9073    cx.executor().start_waiting();
 9074    format.await;
 9075    assert_eq!(
 9076        editor.update(cx, |editor, cx| editor.text(cx)),
 9077        "one, two\nthree\n"
 9078    );
 9079
 9080    editor.update_in(cx, |editor, window, cx| {
 9081        editor.set_text("one\ntwo\nthree\n", window, cx)
 9082    });
 9083    // Ensure we don't lock if formatting hangs.
 9084    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9085        move |params, _| async move {
 9086            assert_eq!(
 9087                params.text_document.uri,
 9088                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9089            );
 9090            futures::future::pending::<()>().await;
 9091            unreachable!()
 9092        },
 9093    );
 9094    let format = editor
 9095        .update_in(cx, |editor, window, cx| {
 9096            editor.perform_format(
 9097                project,
 9098                FormatTrigger::Manual,
 9099                FormatTarget::Buffers,
 9100                window,
 9101                cx,
 9102            )
 9103        })
 9104        .unwrap();
 9105    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9106    cx.executor().start_waiting();
 9107    format.await;
 9108    assert_eq!(
 9109        editor.update(cx, |editor, cx| editor.text(cx)),
 9110        "one\ntwo\nthree\n"
 9111    );
 9112}
 9113
 9114#[gpui::test]
 9115async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9116    init_test(cx, |settings| {
 9117        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9118        settings.defaults.formatter =
 9119            Some(language_settings::SelectedFormatter::List(FormatterList(
 9120                vec![
 9121                    Formatter::LanguageServer { name: None },
 9122                    Formatter::CodeActions(
 9123                        [
 9124                            ("code-action-1".into(), true),
 9125                            ("code-action-2".into(), true),
 9126                        ]
 9127                        .into_iter()
 9128                        .collect(),
 9129                    ),
 9130                ]
 9131                .into(),
 9132            )))
 9133    });
 9134
 9135    let fs = FakeFs::new(cx.executor());
 9136    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9137        .await;
 9138
 9139    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9140    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9141    language_registry.add(rust_lang());
 9142
 9143    let mut fake_servers = language_registry.register_fake_lsp(
 9144        "Rust",
 9145        FakeLspAdapter {
 9146            capabilities: lsp::ServerCapabilities {
 9147                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9148                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9149                    commands: vec!["the-command-for-code-action-1".into()],
 9150                    ..Default::default()
 9151                }),
 9152                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9153                ..Default::default()
 9154            },
 9155            ..Default::default()
 9156        },
 9157    );
 9158
 9159    let buffer = project
 9160        .update(cx, |project, cx| {
 9161            project.open_local_buffer(path!("/file.rs"), cx)
 9162        })
 9163        .await
 9164        .unwrap();
 9165
 9166    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9167    let (editor, cx) = cx.add_window_view(|window, cx| {
 9168        build_editor_with_project(project.clone(), buffer, window, cx)
 9169    });
 9170
 9171    cx.executor().start_waiting();
 9172
 9173    let fake_server = fake_servers.next().await.unwrap();
 9174    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9175        move |_params, _| async move {
 9176            Ok(Some(vec![lsp::TextEdit::new(
 9177                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9178                "applied-formatting\n".to_string(),
 9179            )]))
 9180        },
 9181    );
 9182    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9183        move |params, _| async move {
 9184            assert_eq!(
 9185                params.context.only,
 9186                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9187            );
 9188            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9189            Ok(Some(vec![
 9190                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9191                    kind: Some("code-action-1".into()),
 9192                    edit: Some(lsp::WorkspaceEdit::new(
 9193                        [(
 9194                            uri.clone(),
 9195                            vec![lsp::TextEdit::new(
 9196                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9197                                "applied-code-action-1-edit\n".to_string(),
 9198                            )],
 9199                        )]
 9200                        .into_iter()
 9201                        .collect(),
 9202                    )),
 9203                    command: Some(lsp::Command {
 9204                        command: "the-command-for-code-action-1".into(),
 9205                        ..Default::default()
 9206                    }),
 9207                    ..Default::default()
 9208                }),
 9209                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9210                    kind: Some("code-action-2".into()),
 9211                    edit: Some(lsp::WorkspaceEdit::new(
 9212                        [(
 9213                            uri.clone(),
 9214                            vec![lsp::TextEdit::new(
 9215                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9216                                "applied-code-action-2-edit\n".to_string(),
 9217                            )],
 9218                        )]
 9219                        .into_iter()
 9220                        .collect(),
 9221                    )),
 9222                    ..Default::default()
 9223                }),
 9224            ]))
 9225        },
 9226    );
 9227
 9228    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9229        move |params, _| async move { Ok(params) }
 9230    });
 9231
 9232    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9233    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9234        let fake = fake_server.clone();
 9235        let lock = command_lock.clone();
 9236        move |params, _| {
 9237            assert_eq!(params.command, "the-command-for-code-action-1");
 9238            let fake = fake.clone();
 9239            let lock = lock.clone();
 9240            async move {
 9241                lock.lock().await;
 9242                fake.server
 9243                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9244                        label: None,
 9245                        edit: lsp::WorkspaceEdit {
 9246                            changes: Some(
 9247                                [(
 9248                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9249                                    vec![lsp::TextEdit {
 9250                                        range: lsp::Range::new(
 9251                                            lsp::Position::new(0, 0),
 9252                                            lsp::Position::new(0, 0),
 9253                                        ),
 9254                                        new_text: "applied-code-action-1-command\n".into(),
 9255                                    }],
 9256                                )]
 9257                                .into_iter()
 9258                                .collect(),
 9259                            ),
 9260                            ..Default::default()
 9261                        },
 9262                    })
 9263                    .await
 9264                    .into_response()
 9265                    .unwrap();
 9266                Ok(Some(json!(null)))
 9267            }
 9268        }
 9269    });
 9270
 9271    cx.executor().start_waiting();
 9272    editor
 9273        .update_in(cx, |editor, window, cx| {
 9274            editor.perform_format(
 9275                project.clone(),
 9276                FormatTrigger::Manual,
 9277                FormatTarget::Buffers,
 9278                window,
 9279                cx,
 9280            )
 9281        })
 9282        .unwrap()
 9283        .await;
 9284    editor.update(cx, |editor, cx| {
 9285        assert_eq!(
 9286            editor.text(cx),
 9287            r#"
 9288                applied-code-action-2-edit
 9289                applied-code-action-1-command
 9290                applied-code-action-1-edit
 9291                applied-formatting
 9292                one
 9293                two
 9294                three
 9295            "#
 9296            .unindent()
 9297        );
 9298    });
 9299
 9300    editor.update_in(cx, |editor, window, cx| {
 9301        editor.undo(&Default::default(), window, cx);
 9302        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9303    });
 9304
 9305    // Perform a manual edit while waiting for an LSP command
 9306    // that's being run as part of a formatting code action.
 9307    let lock_guard = command_lock.lock().await;
 9308    let format = editor
 9309        .update_in(cx, |editor, window, cx| {
 9310            editor.perform_format(
 9311                project.clone(),
 9312                FormatTrigger::Manual,
 9313                FormatTarget::Buffers,
 9314                window,
 9315                cx,
 9316            )
 9317        })
 9318        .unwrap();
 9319    cx.run_until_parked();
 9320    editor.update(cx, |editor, cx| {
 9321        assert_eq!(
 9322            editor.text(cx),
 9323            r#"
 9324                applied-code-action-1-edit
 9325                applied-formatting
 9326                one
 9327                two
 9328                three
 9329            "#
 9330            .unindent()
 9331        );
 9332
 9333        editor.buffer.update(cx, |buffer, cx| {
 9334            let ix = buffer.len(cx);
 9335            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9336        });
 9337    });
 9338
 9339    // Allow the LSP command to proceed. Because the buffer was edited,
 9340    // the second code action will not be run.
 9341    drop(lock_guard);
 9342    format.await;
 9343    editor.update_in(cx, |editor, window, cx| {
 9344        assert_eq!(
 9345            editor.text(cx),
 9346            r#"
 9347                applied-code-action-1-command
 9348                applied-code-action-1-edit
 9349                applied-formatting
 9350                one
 9351                two
 9352                three
 9353                edited
 9354            "#
 9355            .unindent()
 9356        );
 9357
 9358        // The manual edit is undone first, because it is the last thing the user did
 9359        // (even though the command completed afterwards).
 9360        editor.undo(&Default::default(), window, cx);
 9361        assert_eq!(
 9362            editor.text(cx),
 9363            r#"
 9364                applied-code-action-1-command
 9365                applied-code-action-1-edit
 9366                applied-formatting
 9367                one
 9368                two
 9369                three
 9370            "#
 9371            .unindent()
 9372        );
 9373
 9374        // All the formatting (including the command, which completed after the manual edit)
 9375        // is undone together.
 9376        editor.undo(&Default::default(), window, cx);
 9377        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9378    });
 9379}
 9380
 9381#[gpui::test]
 9382async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9383    init_test(cx, |settings| {
 9384        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9385            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9386        ))
 9387    });
 9388
 9389    let fs = FakeFs::new(cx.executor());
 9390    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9391
 9392    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9393
 9394    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9395    language_registry.add(Arc::new(Language::new(
 9396        LanguageConfig {
 9397            name: "TypeScript".into(),
 9398            matcher: LanguageMatcher {
 9399                path_suffixes: vec!["ts".to_string()],
 9400                ..Default::default()
 9401            },
 9402            ..LanguageConfig::default()
 9403        },
 9404        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9405    )));
 9406    update_test_language_settings(cx, |settings| {
 9407        settings.defaults.prettier = Some(PrettierSettings {
 9408            allowed: true,
 9409            ..PrettierSettings::default()
 9410        });
 9411    });
 9412    let mut fake_servers = language_registry.register_fake_lsp(
 9413        "TypeScript",
 9414        FakeLspAdapter {
 9415            capabilities: lsp::ServerCapabilities {
 9416                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9417                ..Default::default()
 9418            },
 9419            ..Default::default()
 9420        },
 9421    );
 9422
 9423    let buffer = project
 9424        .update(cx, |project, cx| {
 9425            project.open_local_buffer(path!("/file.ts"), cx)
 9426        })
 9427        .await
 9428        .unwrap();
 9429
 9430    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9431    let (editor, cx) = cx.add_window_view(|window, cx| {
 9432        build_editor_with_project(project.clone(), buffer, window, cx)
 9433    });
 9434    editor.update_in(cx, |editor, window, cx| {
 9435        editor.set_text(
 9436            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9437            window,
 9438            cx,
 9439        )
 9440    });
 9441
 9442    cx.executor().start_waiting();
 9443    let fake_server = fake_servers.next().await.unwrap();
 9444
 9445    let format = editor
 9446        .update_in(cx, |editor, window, cx| {
 9447            editor.perform_code_action_kind(
 9448                project.clone(),
 9449                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9450                window,
 9451                cx,
 9452            )
 9453        })
 9454        .unwrap();
 9455    fake_server
 9456        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9457            assert_eq!(
 9458                params.text_document.uri,
 9459                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9460            );
 9461            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9462                lsp::CodeAction {
 9463                    title: "Organize Imports".to_string(),
 9464                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9465                    edit: Some(lsp::WorkspaceEdit {
 9466                        changes: Some(
 9467                            [(
 9468                                params.text_document.uri.clone(),
 9469                                vec![lsp::TextEdit::new(
 9470                                    lsp::Range::new(
 9471                                        lsp::Position::new(1, 0),
 9472                                        lsp::Position::new(2, 0),
 9473                                    ),
 9474                                    "".to_string(),
 9475                                )],
 9476                            )]
 9477                            .into_iter()
 9478                            .collect(),
 9479                        ),
 9480                        ..Default::default()
 9481                    }),
 9482                    ..Default::default()
 9483                },
 9484            )]))
 9485        })
 9486        .next()
 9487        .await;
 9488    cx.executor().start_waiting();
 9489    format.await;
 9490    assert_eq!(
 9491        editor.update(cx, |editor, cx| editor.text(cx)),
 9492        "import { a } from 'module';\n\nconst x = a;\n"
 9493    );
 9494
 9495    editor.update_in(cx, |editor, window, cx| {
 9496        editor.set_text(
 9497            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9498            window,
 9499            cx,
 9500        )
 9501    });
 9502    // Ensure we don't lock if code action hangs.
 9503    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9504        move |params, _| async move {
 9505            assert_eq!(
 9506                params.text_document.uri,
 9507                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9508            );
 9509            futures::future::pending::<()>().await;
 9510            unreachable!()
 9511        },
 9512    );
 9513    let format = editor
 9514        .update_in(cx, |editor, window, cx| {
 9515            editor.perform_code_action_kind(
 9516                project,
 9517                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9518                window,
 9519                cx,
 9520            )
 9521        })
 9522        .unwrap();
 9523    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9524    cx.executor().start_waiting();
 9525    format.await;
 9526    assert_eq!(
 9527        editor.update(cx, |editor, cx| editor.text(cx)),
 9528        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9529    );
 9530}
 9531
 9532#[gpui::test]
 9533async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9534    init_test(cx, |_| {});
 9535
 9536    let mut cx = EditorLspTestContext::new_rust(
 9537        lsp::ServerCapabilities {
 9538            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9539            ..Default::default()
 9540        },
 9541        cx,
 9542    )
 9543    .await;
 9544
 9545    cx.set_state(indoc! {"
 9546        one.twoˇ
 9547    "});
 9548
 9549    // The format request takes a long time. When it completes, it inserts
 9550    // a newline and an indent before the `.`
 9551    cx.lsp
 9552        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9553            let executor = cx.background_executor().clone();
 9554            async move {
 9555                executor.timer(Duration::from_millis(100)).await;
 9556                Ok(Some(vec![lsp::TextEdit {
 9557                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9558                    new_text: "\n    ".into(),
 9559                }]))
 9560            }
 9561        });
 9562
 9563    // Submit a format request.
 9564    let format_1 = cx
 9565        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9566        .unwrap();
 9567    cx.executor().run_until_parked();
 9568
 9569    // Submit a second format request.
 9570    let format_2 = cx
 9571        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9572        .unwrap();
 9573    cx.executor().run_until_parked();
 9574
 9575    // Wait for both format requests to complete
 9576    cx.executor().advance_clock(Duration::from_millis(200));
 9577    cx.executor().start_waiting();
 9578    format_1.await.unwrap();
 9579    cx.executor().start_waiting();
 9580    format_2.await.unwrap();
 9581
 9582    // The formatting edits only happens once.
 9583    cx.assert_editor_state(indoc! {"
 9584        one
 9585            .twoˇ
 9586    "});
 9587}
 9588
 9589#[gpui::test]
 9590async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9591    init_test(cx, |settings| {
 9592        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9593    });
 9594
 9595    let mut cx = EditorLspTestContext::new_rust(
 9596        lsp::ServerCapabilities {
 9597            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9598            ..Default::default()
 9599        },
 9600        cx,
 9601    )
 9602    .await;
 9603
 9604    // Set up a buffer white some trailing whitespace and no trailing newline.
 9605    cx.set_state(
 9606        &[
 9607            "one ",   //
 9608            "twoˇ",   //
 9609            "three ", //
 9610            "four",   //
 9611        ]
 9612        .join("\n"),
 9613    );
 9614
 9615    // Submit a format request.
 9616    let format = cx
 9617        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9618        .unwrap();
 9619
 9620    // Record which buffer changes have been sent to the language server
 9621    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9622    cx.lsp
 9623        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9624            let buffer_changes = buffer_changes.clone();
 9625            move |params, _| {
 9626                buffer_changes.lock().extend(
 9627                    params
 9628                        .content_changes
 9629                        .into_iter()
 9630                        .map(|e| (e.range.unwrap(), e.text)),
 9631                );
 9632            }
 9633        });
 9634
 9635    // Handle formatting requests to the language server.
 9636    cx.lsp
 9637        .set_request_handler::<lsp::request::Formatting, _, _>({
 9638            let buffer_changes = buffer_changes.clone();
 9639            move |_, _| {
 9640                // When formatting is requested, trailing whitespace has already been stripped,
 9641                // and the trailing newline has already been added.
 9642                assert_eq!(
 9643                    &buffer_changes.lock()[1..],
 9644                    &[
 9645                        (
 9646                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9647                            "".into()
 9648                        ),
 9649                        (
 9650                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9651                            "".into()
 9652                        ),
 9653                        (
 9654                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9655                            "\n".into()
 9656                        ),
 9657                    ]
 9658                );
 9659
 9660                // Insert blank lines between each line of the buffer.
 9661                async move {
 9662                    Ok(Some(vec![
 9663                        lsp::TextEdit {
 9664                            range: lsp::Range::new(
 9665                                lsp::Position::new(1, 0),
 9666                                lsp::Position::new(1, 0),
 9667                            ),
 9668                            new_text: "\n".into(),
 9669                        },
 9670                        lsp::TextEdit {
 9671                            range: lsp::Range::new(
 9672                                lsp::Position::new(2, 0),
 9673                                lsp::Position::new(2, 0),
 9674                            ),
 9675                            new_text: "\n".into(),
 9676                        },
 9677                    ]))
 9678                }
 9679            }
 9680        });
 9681
 9682    // After formatting the buffer, the trailing whitespace is stripped,
 9683    // a newline is appended, and the edits provided by the language server
 9684    // have been applied.
 9685    format.await.unwrap();
 9686    cx.assert_editor_state(
 9687        &[
 9688            "one",   //
 9689            "",      //
 9690            "twoˇ",  //
 9691            "",      //
 9692            "three", //
 9693            "four",  //
 9694            "",      //
 9695        ]
 9696        .join("\n"),
 9697    );
 9698
 9699    // Undoing the formatting undoes the trailing whitespace removal, the
 9700    // trailing newline, and the LSP edits.
 9701    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9702    cx.assert_editor_state(
 9703        &[
 9704            "one ",   //
 9705            "twoˇ",   //
 9706            "three ", //
 9707            "four",   //
 9708        ]
 9709        .join("\n"),
 9710    );
 9711}
 9712
 9713#[gpui::test]
 9714async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9715    cx: &mut TestAppContext,
 9716) {
 9717    init_test(cx, |_| {});
 9718
 9719    cx.update(|cx| {
 9720        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9721            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9722                settings.auto_signature_help = Some(true);
 9723            });
 9724        });
 9725    });
 9726
 9727    let mut cx = EditorLspTestContext::new_rust(
 9728        lsp::ServerCapabilities {
 9729            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9730                ..Default::default()
 9731            }),
 9732            ..Default::default()
 9733        },
 9734        cx,
 9735    )
 9736    .await;
 9737
 9738    let language = Language::new(
 9739        LanguageConfig {
 9740            name: "Rust".into(),
 9741            brackets: BracketPairConfig {
 9742                pairs: vec![
 9743                    BracketPair {
 9744                        start: "{".to_string(),
 9745                        end: "}".to_string(),
 9746                        close: true,
 9747                        surround: true,
 9748                        newline: true,
 9749                    },
 9750                    BracketPair {
 9751                        start: "(".to_string(),
 9752                        end: ")".to_string(),
 9753                        close: true,
 9754                        surround: true,
 9755                        newline: true,
 9756                    },
 9757                    BracketPair {
 9758                        start: "/*".to_string(),
 9759                        end: " */".to_string(),
 9760                        close: true,
 9761                        surround: true,
 9762                        newline: true,
 9763                    },
 9764                    BracketPair {
 9765                        start: "[".to_string(),
 9766                        end: "]".to_string(),
 9767                        close: false,
 9768                        surround: false,
 9769                        newline: true,
 9770                    },
 9771                    BracketPair {
 9772                        start: "\"".to_string(),
 9773                        end: "\"".to_string(),
 9774                        close: true,
 9775                        surround: true,
 9776                        newline: false,
 9777                    },
 9778                    BracketPair {
 9779                        start: "<".to_string(),
 9780                        end: ">".to_string(),
 9781                        close: false,
 9782                        surround: true,
 9783                        newline: true,
 9784                    },
 9785                ],
 9786                ..Default::default()
 9787            },
 9788            autoclose_before: "})]".to_string(),
 9789            ..Default::default()
 9790        },
 9791        Some(tree_sitter_rust::LANGUAGE.into()),
 9792    );
 9793    let language = Arc::new(language);
 9794
 9795    cx.language_registry().add(language.clone());
 9796    cx.update_buffer(|buffer, cx| {
 9797        buffer.set_language(Some(language), cx);
 9798    });
 9799
 9800    cx.set_state(
 9801        &r#"
 9802            fn main() {
 9803                sampleˇ
 9804            }
 9805        "#
 9806        .unindent(),
 9807    );
 9808
 9809    cx.update_editor(|editor, window, cx| {
 9810        editor.handle_input("(", window, cx);
 9811    });
 9812    cx.assert_editor_state(
 9813        &"
 9814            fn main() {
 9815                sample(ˇ)
 9816            }
 9817        "
 9818        .unindent(),
 9819    );
 9820
 9821    let mocked_response = lsp::SignatureHelp {
 9822        signatures: vec![lsp::SignatureInformation {
 9823            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9824            documentation: None,
 9825            parameters: Some(vec![
 9826                lsp::ParameterInformation {
 9827                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9828                    documentation: None,
 9829                },
 9830                lsp::ParameterInformation {
 9831                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9832                    documentation: None,
 9833                },
 9834            ]),
 9835            active_parameter: None,
 9836        }],
 9837        active_signature: Some(0),
 9838        active_parameter: Some(0),
 9839    };
 9840    handle_signature_help_request(&mut cx, mocked_response).await;
 9841
 9842    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9843        .await;
 9844
 9845    cx.editor(|editor, _, _| {
 9846        let signature_help_state = editor.signature_help_state.popover().cloned();
 9847        assert_eq!(
 9848            signature_help_state.unwrap().label,
 9849            "param1: u8, param2: u8"
 9850        );
 9851    });
 9852}
 9853
 9854#[gpui::test]
 9855async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9856    init_test(cx, |_| {});
 9857
 9858    cx.update(|cx| {
 9859        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9860            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9861                settings.auto_signature_help = Some(false);
 9862                settings.show_signature_help_after_edits = Some(false);
 9863            });
 9864        });
 9865    });
 9866
 9867    let mut cx = EditorLspTestContext::new_rust(
 9868        lsp::ServerCapabilities {
 9869            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9870                ..Default::default()
 9871            }),
 9872            ..Default::default()
 9873        },
 9874        cx,
 9875    )
 9876    .await;
 9877
 9878    let language = Language::new(
 9879        LanguageConfig {
 9880            name: "Rust".into(),
 9881            brackets: BracketPairConfig {
 9882                pairs: vec![
 9883                    BracketPair {
 9884                        start: "{".to_string(),
 9885                        end: "}".to_string(),
 9886                        close: true,
 9887                        surround: true,
 9888                        newline: true,
 9889                    },
 9890                    BracketPair {
 9891                        start: "(".to_string(),
 9892                        end: ")".to_string(),
 9893                        close: true,
 9894                        surround: true,
 9895                        newline: true,
 9896                    },
 9897                    BracketPair {
 9898                        start: "/*".to_string(),
 9899                        end: " */".to_string(),
 9900                        close: true,
 9901                        surround: true,
 9902                        newline: true,
 9903                    },
 9904                    BracketPair {
 9905                        start: "[".to_string(),
 9906                        end: "]".to_string(),
 9907                        close: false,
 9908                        surround: false,
 9909                        newline: true,
 9910                    },
 9911                    BracketPair {
 9912                        start: "\"".to_string(),
 9913                        end: "\"".to_string(),
 9914                        close: true,
 9915                        surround: true,
 9916                        newline: false,
 9917                    },
 9918                    BracketPair {
 9919                        start: "<".to_string(),
 9920                        end: ">".to_string(),
 9921                        close: false,
 9922                        surround: true,
 9923                        newline: true,
 9924                    },
 9925                ],
 9926                ..Default::default()
 9927            },
 9928            autoclose_before: "})]".to_string(),
 9929            ..Default::default()
 9930        },
 9931        Some(tree_sitter_rust::LANGUAGE.into()),
 9932    );
 9933    let language = Arc::new(language);
 9934
 9935    cx.language_registry().add(language.clone());
 9936    cx.update_buffer(|buffer, cx| {
 9937        buffer.set_language(Some(language), cx);
 9938    });
 9939
 9940    // Ensure that signature_help is not called when no signature help is enabled.
 9941    cx.set_state(
 9942        &r#"
 9943            fn main() {
 9944                sampleˇ
 9945            }
 9946        "#
 9947        .unindent(),
 9948    );
 9949    cx.update_editor(|editor, window, cx| {
 9950        editor.handle_input("(", window, cx);
 9951    });
 9952    cx.assert_editor_state(
 9953        &"
 9954            fn main() {
 9955                sample(ˇ)
 9956            }
 9957        "
 9958        .unindent(),
 9959    );
 9960    cx.editor(|editor, _, _| {
 9961        assert!(editor.signature_help_state.task().is_none());
 9962    });
 9963
 9964    let mocked_response = lsp::SignatureHelp {
 9965        signatures: vec![lsp::SignatureInformation {
 9966            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9967            documentation: None,
 9968            parameters: Some(vec![
 9969                lsp::ParameterInformation {
 9970                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9971                    documentation: None,
 9972                },
 9973                lsp::ParameterInformation {
 9974                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9975                    documentation: None,
 9976                },
 9977            ]),
 9978            active_parameter: None,
 9979        }],
 9980        active_signature: Some(0),
 9981        active_parameter: Some(0),
 9982    };
 9983
 9984    // Ensure that signature_help is called when enabled afte edits
 9985    cx.update(|_, cx| {
 9986        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9987            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9988                settings.auto_signature_help = Some(false);
 9989                settings.show_signature_help_after_edits = Some(true);
 9990            });
 9991        });
 9992    });
 9993    cx.set_state(
 9994        &r#"
 9995            fn main() {
 9996                sampleˇ
 9997            }
 9998        "#
 9999        .unindent(),
10000    );
10001    cx.update_editor(|editor, window, cx| {
10002        editor.handle_input("(", window, cx);
10003    });
10004    cx.assert_editor_state(
10005        &"
10006            fn main() {
10007                sample(ˇ)
10008            }
10009        "
10010        .unindent(),
10011    );
10012    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10013    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10014        .await;
10015    cx.update_editor(|editor, _, _| {
10016        let signature_help_state = editor.signature_help_state.popover().cloned();
10017        assert!(signature_help_state.is_some());
10018        assert_eq!(
10019            signature_help_state.unwrap().label,
10020            "param1: u8, param2: u8"
10021        );
10022        editor.signature_help_state = SignatureHelpState::default();
10023    });
10024
10025    // Ensure that signature_help is called when auto signature help override is enabled
10026    cx.update(|_, cx| {
10027        cx.update_global::<SettingsStore, _>(|settings, cx| {
10028            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10029                settings.auto_signature_help = Some(true);
10030                settings.show_signature_help_after_edits = Some(false);
10031            });
10032        });
10033    });
10034    cx.set_state(
10035        &r#"
10036            fn main() {
10037                sampleˇ
10038            }
10039        "#
10040        .unindent(),
10041    );
10042    cx.update_editor(|editor, window, cx| {
10043        editor.handle_input("(", window, cx);
10044    });
10045    cx.assert_editor_state(
10046        &"
10047            fn main() {
10048                sample(ˇ)
10049            }
10050        "
10051        .unindent(),
10052    );
10053    handle_signature_help_request(&mut cx, mocked_response).await;
10054    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10055        .await;
10056    cx.editor(|editor, _, _| {
10057        let signature_help_state = editor.signature_help_state.popover().cloned();
10058        assert!(signature_help_state.is_some());
10059        assert_eq!(
10060            signature_help_state.unwrap().label,
10061            "param1: u8, param2: u8"
10062        );
10063    });
10064}
10065
10066#[gpui::test]
10067async fn test_signature_help(cx: &mut TestAppContext) {
10068    init_test(cx, |_| {});
10069    cx.update(|cx| {
10070        cx.update_global::<SettingsStore, _>(|settings, cx| {
10071            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10072                settings.auto_signature_help = Some(true);
10073            });
10074        });
10075    });
10076
10077    let mut cx = EditorLspTestContext::new_rust(
10078        lsp::ServerCapabilities {
10079            signature_help_provider: Some(lsp::SignatureHelpOptions {
10080                ..Default::default()
10081            }),
10082            ..Default::default()
10083        },
10084        cx,
10085    )
10086    .await;
10087
10088    // A test that directly calls `show_signature_help`
10089    cx.update_editor(|editor, window, cx| {
10090        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10091    });
10092
10093    let mocked_response = lsp::SignatureHelp {
10094        signatures: vec![lsp::SignatureInformation {
10095            label: "fn sample(param1: u8, param2: u8)".to_string(),
10096            documentation: None,
10097            parameters: Some(vec![
10098                lsp::ParameterInformation {
10099                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10100                    documentation: None,
10101                },
10102                lsp::ParameterInformation {
10103                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10104                    documentation: None,
10105                },
10106            ]),
10107            active_parameter: None,
10108        }],
10109        active_signature: Some(0),
10110        active_parameter: Some(0),
10111    };
10112    handle_signature_help_request(&mut cx, mocked_response).await;
10113
10114    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10115        .await;
10116
10117    cx.editor(|editor, _, _| {
10118        let signature_help_state = editor.signature_help_state.popover().cloned();
10119        assert!(signature_help_state.is_some());
10120        assert_eq!(
10121            signature_help_state.unwrap().label,
10122            "param1: u8, param2: u8"
10123        );
10124    });
10125
10126    // When exiting outside from inside the brackets, `signature_help` is closed.
10127    cx.set_state(indoc! {"
10128        fn main() {
10129            sample(ˇ);
10130        }
10131
10132        fn sample(param1: u8, param2: u8) {}
10133    "});
10134
10135    cx.update_editor(|editor, window, cx| {
10136        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10137    });
10138
10139    let mocked_response = lsp::SignatureHelp {
10140        signatures: Vec::new(),
10141        active_signature: None,
10142        active_parameter: None,
10143    };
10144    handle_signature_help_request(&mut cx, mocked_response).await;
10145
10146    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10147        .await;
10148
10149    cx.editor(|editor, _, _| {
10150        assert!(!editor.signature_help_state.is_shown());
10151    });
10152
10153    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10154    cx.set_state(indoc! {"
10155        fn main() {
10156            sample(ˇ);
10157        }
10158
10159        fn sample(param1: u8, param2: u8) {}
10160    "});
10161
10162    let mocked_response = lsp::SignatureHelp {
10163        signatures: vec![lsp::SignatureInformation {
10164            label: "fn sample(param1: u8, param2: u8)".to_string(),
10165            documentation: None,
10166            parameters: Some(vec![
10167                lsp::ParameterInformation {
10168                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10169                    documentation: None,
10170                },
10171                lsp::ParameterInformation {
10172                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10173                    documentation: None,
10174                },
10175            ]),
10176            active_parameter: None,
10177        }],
10178        active_signature: Some(0),
10179        active_parameter: Some(0),
10180    };
10181    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10182    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10183        .await;
10184    cx.editor(|editor, _, _| {
10185        assert!(editor.signature_help_state.is_shown());
10186    });
10187
10188    // Restore the popover with more parameter input
10189    cx.set_state(indoc! {"
10190        fn main() {
10191            sample(param1, param2ˇ);
10192        }
10193
10194        fn sample(param1: u8, param2: u8) {}
10195    "});
10196
10197    let mocked_response = lsp::SignatureHelp {
10198        signatures: vec![lsp::SignatureInformation {
10199            label: "fn sample(param1: u8, param2: u8)".to_string(),
10200            documentation: None,
10201            parameters: Some(vec![
10202                lsp::ParameterInformation {
10203                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10204                    documentation: None,
10205                },
10206                lsp::ParameterInformation {
10207                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10208                    documentation: None,
10209                },
10210            ]),
10211            active_parameter: None,
10212        }],
10213        active_signature: Some(0),
10214        active_parameter: Some(1),
10215    };
10216    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10217    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10218        .await;
10219
10220    // When selecting a range, the popover is gone.
10221    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10222    cx.update_editor(|editor, window, cx| {
10223        editor.change_selections(None, window, cx, |s| {
10224            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10225        })
10226    });
10227    cx.assert_editor_state(indoc! {"
10228        fn main() {
10229            sample(param1, «ˇparam2»);
10230        }
10231
10232        fn sample(param1: u8, param2: u8) {}
10233    "});
10234    cx.editor(|editor, _, _| {
10235        assert!(!editor.signature_help_state.is_shown());
10236    });
10237
10238    // When unselecting again, the popover is back if within the brackets.
10239    cx.update_editor(|editor, window, cx| {
10240        editor.change_selections(None, window, cx, |s| {
10241            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10242        })
10243    });
10244    cx.assert_editor_state(indoc! {"
10245        fn main() {
10246            sample(param1, ˇparam2);
10247        }
10248
10249        fn sample(param1: u8, param2: u8) {}
10250    "});
10251    handle_signature_help_request(&mut cx, mocked_response).await;
10252    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10253        .await;
10254    cx.editor(|editor, _, _| {
10255        assert!(editor.signature_help_state.is_shown());
10256    });
10257
10258    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10259    cx.update_editor(|editor, window, cx| {
10260        editor.change_selections(None, window, cx, |s| {
10261            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10262            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10263        })
10264    });
10265    cx.assert_editor_state(indoc! {"
10266        fn main() {
10267            sample(param1, ˇparam2);
10268        }
10269
10270        fn sample(param1: u8, param2: u8) {}
10271    "});
10272
10273    let mocked_response = lsp::SignatureHelp {
10274        signatures: vec![lsp::SignatureInformation {
10275            label: "fn sample(param1: u8, param2: u8)".to_string(),
10276            documentation: None,
10277            parameters: Some(vec![
10278                lsp::ParameterInformation {
10279                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10280                    documentation: None,
10281                },
10282                lsp::ParameterInformation {
10283                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10284                    documentation: None,
10285                },
10286            ]),
10287            active_parameter: None,
10288        }],
10289        active_signature: Some(0),
10290        active_parameter: Some(1),
10291    };
10292    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10293    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10294        .await;
10295    cx.update_editor(|editor, _, cx| {
10296        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10297    });
10298    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10299        .await;
10300    cx.update_editor(|editor, window, cx| {
10301        editor.change_selections(None, window, cx, |s| {
10302            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10303        })
10304    });
10305    cx.assert_editor_state(indoc! {"
10306        fn main() {
10307            sample(param1, «ˇparam2»);
10308        }
10309
10310        fn sample(param1: u8, param2: u8) {}
10311    "});
10312    cx.update_editor(|editor, window, cx| {
10313        editor.change_selections(None, window, cx, |s| {
10314            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10315        })
10316    });
10317    cx.assert_editor_state(indoc! {"
10318        fn main() {
10319            sample(param1, ˇparam2);
10320        }
10321
10322        fn sample(param1: u8, param2: u8) {}
10323    "});
10324    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10325        .await;
10326}
10327
10328#[gpui::test]
10329async fn test_completion_mode(cx: &mut TestAppContext) {
10330    init_test(cx, |_| {});
10331    let mut cx = EditorLspTestContext::new_rust(
10332        lsp::ServerCapabilities {
10333            completion_provider: Some(lsp::CompletionOptions {
10334                resolve_provider: Some(true),
10335                ..Default::default()
10336            }),
10337            ..Default::default()
10338        },
10339        cx,
10340    )
10341    .await;
10342
10343    struct Run {
10344        run_description: &'static str,
10345        initial_state: String,
10346        buffer_marked_text: String,
10347        completion_text: &'static str,
10348        expected_with_insert_mode: String,
10349        expected_with_replace_mode: String,
10350        expected_with_replace_subsequence_mode: String,
10351        expected_with_replace_suffix_mode: String,
10352    }
10353
10354    let runs = [
10355        Run {
10356            run_description: "Start of word matches completion text",
10357            initial_state: "before ediˇ after".into(),
10358            buffer_marked_text: "before <edi|> after".into(),
10359            completion_text: "editor",
10360            expected_with_insert_mode: "before editorˇ after".into(),
10361            expected_with_replace_mode: "before editorˇ after".into(),
10362            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10363            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10364        },
10365        Run {
10366            run_description: "Accept same text at the middle of the word",
10367            initial_state: "before ediˇtor after".into(),
10368            buffer_marked_text: "before <edi|tor> after".into(),
10369            completion_text: "editor",
10370            expected_with_insert_mode: "before editorˇtor after".into(),
10371            expected_with_replace_mode: "before editorˇ after".into(),
10372            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10373            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10374        },
10375        Run {
10376            run_description: "End of word matches completion text -- cursor at end",
10377            initial_state: "before torˇ after".into(),
10378            buffer_marked_text: "before <tor|> after".into(),
10379            completion_text: "editor",
10380            expected_with_insert_mode: "before editorˇ after".into(),
10381            expected_with_replace_mode: "before editorˇ after".into(),
10382            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10383            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10384        },
10385        Run {
10386            run_description: "End of word matches completion text -- cursor at start",
10387            initial_state: "before ˇtor after".into(),
10388            buffer_marked_text: "before <|tor> after".into(),
10389            completion_text: "editor",
10390            expected_with_insert_mode: "before editorˇtor after".into(),
10391            expected_with_replace_mode: "before editorˇ after".into(),
10392            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10393            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10394        },
10395        Run {
10396            run_description: "Prepend text containing whitespace",
10397            initial_state: "pˇfield: bool".into(),
10398            buffer_marked_text: "<p|field>: bool".into(),
10399            completion_text: "pub ",
10400            expected_with_insert_mode: "pub ˇfield: bool".into(),
10401            expected_with_replace_mode: "pub ˇ: bool".into(),
10402            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10403            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10404        },
10405        Run {
10406            run_description: "Add element to start of list",
10407            initial_state: "[element_ˇelement_2]".into(),
10408            buffer_marked_text: "[<element_|element_2>]".into(),
10409            completion_text: "element_1",
10410            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10411            expected_with_replace_mode: "[element_1ˇ]".into(),
10412            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10413            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10414        },
10415        Run {
10416            run_description: "Add element to start of list -- first and second elements are equal",
10417            initial_state: "[elˇelement]".into(),
10418            buffer_marked_text: "[<el|element>]".into(),
10419            completion_text: "element",
10420            expected_with_insert_mode: "[elementˇelement]".into(),
10421            expected_with_replace_mode: "[elementˇ]".into(),
10422            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10423            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10424        },
10425        Run {
10426            run_description: "Ends with matching suffix",
10427            initial_state: "SubˇError".into(),
10428            buffer_marked_text: "<Sub|Error>".into(),
10429            completion_text: "SubscriptionError",
10430            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10431            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10432            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10433            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10434        },
10435        Run {
10436            run_description: "Suffix is a subsequence -- contiguous",
10437            initial_state: "SubˇErr".into(),
10438            buffer_marked_text: "<Sub|Err>".into(),
10439            completion_text: "SubscriptionError",
10440            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10441            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10442            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10443            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10444        },
10445        Run {
10446            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10447            initial_state: "Suˇscrirr".into(),
10448            buffer_marked_text: "<Su|scrirr>".into(),
10449            completion_text: "SubscriptionError",
10450            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10451            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10452            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10453            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10454        },
10455        Run {
10456            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10457            initial_state: "foo(indˇix)".into(),
10458            buffer_marked_text: "foo(<ind|ix>)".into(),
10459            completion_text: "node_index",
10460            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10461            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10462            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10463            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10464        },
10465    ];
10466
10467    for run in runs {
10468        let run_variations = [
10469            (LspInsertMode::Insert, run.expected_with_insert_mode),
10470            (LspInsertMode::Replace, run.expected_with_replace_mode),
10471            (
10472                LspInsertMode::ReplaceSubsequence,
10473                run.expected_with_replace_subsequence_mode,
10474            ),
10475            (
10476                LspInsertMode::ReplaceSuffix,
10477                run.expected_with_replace_suffix_mode,
10478            ),
10479        ];
10480
10481        for (lsp_insert_mode, expected_text) in run_variations {
10482            eprintln!(
10483                "run = {:?}, mode = {lsp_insert_mode:.?}",
10484                run.run_description,
10485            );
10486
10487            update_test_language_settings(&mut cx, |settings| {
10488                settings.defaults.completions = Some(CompletionSettings {
10489                    lsp_insert_mode,
10490                    words: WordsCompletionMode::Disabled,
10491                    lsp: true,
10492                    lsp_fetch_timeout_ms: 0,
10493                });
10494            });
10495
10496            cx.set_state(&run.initial_state);
10497            cx.update_editor(|editor, window, cx| {
10498                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10499            });
10500
10501            let counter = Arc::new(AtomicUsize::new(0));
10502            handle_completion_request_with_insert_and_replace(
10503                &mut cx,
10504                &run.buffer_marked_text,
10505                vec![run.completion_text],
10506                counter.clone(),
10507            )
10508            .await;
10509            cx.condition(|editor, _| editor.context_menu_visible())
10510                .await;
10511            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10512
10513            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10514                editor
10515                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10516                    .unwrap()
10517            });
10518            cx.assert_editor_state(&expected_text);
10519            handle_resolve_completion_request(&mut cx, None).await;
10520            apply_additional_edits.await.unwrap();
10521        }
10522    }
10523}
10524
10525#[gpui::test]
10526async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10527    init_test(cx, |_| {});
10528    let mut cx = EditorLspTestContext::new_rust(
10529        lsp::ServerCapabilities {
10530            completion_provider: Some(lsp::CompletionOptions {
10531                resolve_provider: Some(true),
10532                ..Default::default()
10533            }),
10534            ..Default::default()
10535        },
10536        cx,
10537    )
10538    .await;
10539
10540    let initial_state = "SubˇError";
10541    let buffer_marked_text = "<Sub|Error>";
10542    let completion_text = "SubscriptionError";
10543    let expected_with_insert_mode = "SubscriptionErrorˇError";
10544    let expected_with_replace_mode = "SubscriptionErrorˇ";
10545
10546    update_test_language_settings(&mut cx, |settings| {
10547        settings.defaults.completions = Some(CompletionSettings {
10548            words: WordsCompletionMode::Disabled,
10549            // set the opposite here to ensure that the action is overriding the default behavior
10550            lsp_insert_mode: LspInsertMode::Insert,
10551            lsp: true,
10552            lsp_fetch_timeout_ms: 0,
10553        });
10554    });
10555
10556    cx.set_state(initial_state);
10557    cx.update_editor(|editor, window, cx| {
10558        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10559    });
10560
10561    let counter = Arc::new(AtomicUsize::new(0));
10562    handle_completion_request_with_insert_and_replace(
10563        &mut cx,
10564        &buffer_marked_text,
10565        vec![completion_text],
10566        counter.clone(),
10567    )
10568    .await;
10569    cx.condition(|editor, _| editor.context_menu_visible())
10570        .await;
10571    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10572
10573    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10574        editor
10575            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10576            .unwrap()
10577    });
10578    cx.assert_editor_state(&expected_with_replace_mode);
10579    handle_resolve_completion_request(&mut cx, None).await;
10580    apply_additional_edits.await.unwrap();
10581
10582    update_test_language_settings(&mut cx, |settings| {
10583        settings.defaults.completions = Some(CompletionSettings {
10584            words: WordsCompletionMode::Disabled,
10585            // set the opposite here to ensure that the action is overriding the default behavior
10586            lsp_insert_mode: LspInsertMode::Replace,
10587            lsp: true,
10588            lsp_fetch_timeout_ms: 0,
10589        });
10590    });
10591
10592    cx.set_state(initial_state);
10593    cx.update_editor(|editor, window, cx| {
10594        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10595    });
10596    handle_completion_request_with_insert_and_replace(
10597        &mut cx,
10598        &buffer_marked_text,
10599        vec![completion_text],
10600        counter.clone(),
10601    )
10602    .await;
10603    cx.condition(|editor, _| editor.context_menu_visible())
10604        .await;
10605    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10606
10607    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10608        editor
10609            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10610            .unwrap()
10611    });
10612    cx.assert_editor_state(&expected_with_insert_mode);
10613    handle_resolve_completion_request(&mut cx, None).await;
10614    apply_additional_edits.await.unwrap();
10615}
10616
10617#[gpui::test]
10618async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10619    init_test(cx, |_| {});
10620    let mut cx = EditorLspTestContext::new_rust(
10621        lsp::ServerCapabilities {
10622            completion_provider: Some(lsp::CompletionOptions {
10623                resolve_provider: Some(true),
10624                ..Default::default()
10625            }),
10626            ..Default::default()
10627        },
10628        cx,
10629    )
10630    .await;
10631
10632    // scenario: surrounding text matches completion text
10633    let completion_text = "to_offset";
10634    let initial_state = indoc! {"
10635        1. buf.to_offˇsuffix
10636        2. buf.to_offˇsuf
10637        3. buf.to_offˇfix
10638        4. buf.to_offˇ
10639        5. into_offˇensive
10640        6. ˇsuffix
10641        7. let ˇ //
10642        8. aaˇzz
10643        9. buf.to_off«zzzzzˇ»suffix
10644        10. buf.«ˇzzzzz»suffix
10645        11. to_off«ˇzzzzz»
10646
10647        buf.to_offˇsuffix  // newest cursor
10648    "};
10649    let completion_marked_buffer = indoc! {"
10650        1. buf.to_offsuffix
10651        2. buf.to_offsuf
10652        3. buf.to_offfix
10653        4. buf.to_off
10654        5. into_offensive
10655        6. suffix
10656        7. let  //
10657        8. aazz
10658        9. buf.to_offzzzzzsuffix
10659        10. buf.zzzzzsuffix
10660        11. to_offzzzzz
10661
10662        buf.<to_off|suffix>  // newest cursor
10663    "};
10664    let expected = indoc! {"
10665        1. buf.to_offsetˇ
10666        2. buf.to_offsetˇsuf
10667        3. buf.to_offsetˇfix
10668        4. buf.to_offsetˇ
10669        5. into_offsetˇensive
10670        6. to_offsetˇsuffix
10671        7. let to_offsetˇ //
10672        8. aato_offsetˇzz
10673        9. buf.to_offsetˇ
10674        10. buf.to_offsetˇsuffix
10675        11. to_offsetˇ
10676
10677        buf.to_offsetˇ  // newest cursor
10678    "};
10679    cx.set_state(initial_state);
10680    cx.update_editor(|editor, window, cx| {
10681        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10682    });
10683    handle_completion_request_with_insert_and_replace(
10684        &mut cx,
10685        completion_marked_buffer,
10686        vec![completion_text],
10687        Arc::new(AtomicUsize::new(0)),
10688    )
10689    .await;
10690    cx.condition(|editor, _| editor.context_menu_visible())
10691        .await;
10692    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10693        editor
10694            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10695            .unwrap()
10696    });
10697    cx.assert_editor_state(expected);
10698    handle_resolve_completion_request(&mut cx, None).await;
10699    apply_additional_edits.await.unwrap();
10700
10701    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10702    let completion_text = "foo_and_bar";
10703    let initial_state = indoc! {"
10704        1. ooanbˇ
10705        2. zooanbˇ
10706        3. ooanbˇz
10707        4. zooanbˇz
10708        5. ooanˇ
10709        6. oanbˇ
10710
10711        ooanbˇ
10712    "};
10713    let completion_marked_buffer = indoc! {"
10714        1. ooanb
10715        2. zooanb
10716        3. ooanbz
10717        4. zooanbz
10718        5. ooan
10719        6. oanb
10720
10721        <ooanb|>
10722    "};
10723    let expected = indoc! {"
10724        1. foo_and_barˇ
10725        2. zfoo_and_barˇ
10726        3. foo_and_barˇz
10727        4. zfoo_and_barˇz
10728        5. ooanfoo_and_barˇ
10729        6. oanbfoo_and_barˇ
10730
10731        foo_and_barˇ
10732    "};
10733    cx.set_state(initial_state);
10734    cx.update_editor(|editor, window, cx| {
10735        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10736    });
10737    handle_completion_request_with_insert_and_replace(
10738        &mut cx,
10739        completion_marked_buffer,
10740        vec![completion_text],
10741        Arc::new(AtomicUsize::new(0)),
10742    )
10743    .await;
10744    cx.condition(|editor, _| editor.context_menu_visible())
10745        .await;
10746    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10747        editor
10748            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10749            .unwrap()
10750    });
10751    cx.assert_editor_state(expected);
10752    handle_resolve_completion_request(&mut cx, None).await;
10753    apply_additional_edits.await.unwrap();
10754
10755    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10756    // (expects the same as if it was inserted at the end)
10757    let completion_text = "foo_and_bar";
10758    let initial_state = indoc! {"
10759        1. ooˇanb
10760        2. zooˇanb
10761        3. ooˇanbz
10762        4. zooˇanbz
10763
10764        ooˇanb
10765    "};
10766    let completion_marked_buffer = indoc! {"
10767        1. ooanb
10768        2. zooanb
10769        3. ooanbz
10770        4. zooanbz
10771
10772        <oo|anb>
10773    "};
10774    let expected = indoc! {"
10775        1. foo_and_barˇ
10776        2. zfoo_and_barˇ
10777        3. foo_and_barˇz
10778        4. zfoo_and_barˇz
10779
10780        foo_and_barˇ
10781    "};
10782    cx.set_state(initial_state);
10783    cx.update_editor(|editor, window, cx| {
10784        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10785    });
10786    handle_completion_request_with_insert_and_replace(
10787        &mut cx,
10788        completion_marked_buffer,
10789        vec![completion_text],
10790        Arc::new(AtomicUsize::new(0)),
10791    )
10792    .await;
10793    cx.condition(|editor, _| editor.context_menu_visible())
10794        .await;
10795    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10796        editor
10797            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10798            .unwrap()
10799    });
10800    cx.assert_editor_state(expected);
10801    handle_resolve_completion_request(&mut cx, None).await;
10802    apply_additional_edits.await.unwrap();
10803}
10804
10805// This used to crash
10806#[gpui::test]
10807async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10808    init_test(cx, |_| {});
10809
10810    let buffer_text = indoc! {"
10811        fn main() {
10812            10.satu;
10813
10814            //
10815            // separate cursors so they open in different excerpts (manually reproducible)
10816            //
10817
10818            10.satu20;
10819        }
10820    "};
10821    let multibuffer_text_with_selections = indoc! {"
10822        fn main() {
10823            10.satuˇ;
10824
10825            //
10826
10827            //
10828
10829            10.satuˇ20;
10830        }
10831    "};
10832    let expected_multibuffer = indoc! {"
10833        fn main() {
10834            10.saturating_sub()ˇ;
10835
10836            //
10837
10838            //
10839
10840            10.saturating_sub()ˇ;
10841        }
10842    "};
10843
10844    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10845    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10846
10847    let fs = FakeFs::new(cx.executor());
10848    fs.insert_tree(
10849        path!("/a"),
10850        json!({
10851            "main.rs": buffer_text,
10852        }),
10853    )
10854    .await;
10855
10856    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10857    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10858    language_registry.add(rust_lang());
10859    let mut fake_servers = language_registry.register_fake_lsp(
10860        "Rust",
10861        FakeLspAdapter {
10862            capabilities: lsp::ServerCapabilities {
10863                completion_provider: Some(lsp::CompletionOptions {
10864                    resolve_provider: None,
10865                    ..lsp::CompletionOptions::default()
10866                }),
10867                ..lsp::ServerCapabilities::default()
10868            },
10869            ..FakeLspAdapter::default()
10870        },
10871    );
10872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10873    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10874    let buffer = project
10875        .update(cx, |project, cx| {
10876            project.open_local_buffer(path!("/a/main.rs"), cx)
10877        })
10878        .await
10879        .unwrap();
10880
10881    let multi_buffer = cx.new(|cx| {
10882        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10883        multi_buffer.push_excerpts(
10884            buffer.clone(),
10885            [ExcerptRange::new(0..first_excerpt_end)],
10886            cx,
10887        );
10888        multi_buffer.push_excerpts(
10889            buffer.clone(),
10890            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10891            cx,
10892        );
10893        multi_buffer
10894    });
10895
10896    let editor = workspace
10897        .update(cx, |_, window, cx| {
10898            cx.new(|cx| {
10899                Editor::new(
10900                    EditorMode::Full {
10901                        scale_ui_elements_with_buffer_font_size: false,
10902                        show_active_line_background: false,
10903                        sized_by_content: false,
10904                    },
10905                    multi_buffer.clone(),
10906                    Some(project.clone()),
10907                    window,
10908                    cx,
10909                )
10910            })
10911        })
10912        .unwrap();
10913
10914    let pane = workspace
10915        .update(cx, |workspace, _, _| workspace.active_pane().clone())
10916        .unwrap();
10917    pane.update_in(cx, |pane, window, cx| {
10918        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10919    });
10920
10921    let fake_server = fake_servers.next().await.unwrap();
10922
10923    editor.update_in(cx, |editor, window, cx| {
10924        editor.change_selections(None, window, cx, |s| {
10925            s.select_ranges([
10926                Point::new(1, 11)..Point::new(1, 11),
10927                Point::new(7, 11)..Point::new(7, 11),
10928            ])
10929        });
10930
10931        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10932    });
10933
10934    editor.update_in(cx, |editor, window, cx| {
10935        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10936    });
10937
10938    fake_server
10939        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10940            let completion_item = lsp::CompletionItem {
10941                label: "saturating_sub()".into(),
10942                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10943                    lsp::InsertReplaceEdit {
10944                        new_text: "saturating_sub()".to_owned(),
10945                        insert: lsp::Range::new(
10946                            lsp::Position::new(7, 7),
10947                            lsp::Position::new(7, 11),
10948                        ),
10949                        replace: lsp::Range::new(
10950                            lsp::Position::new(7, 7),
10951                            lsp::Position::new(7, 13),
10952                        ),
10953                    },
10954                )),
10955                ..lsp::CompletionItem::default()
10956            };
10957
10958            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10959        })
10960        .next()
10961        .await
10962        .unwrap();
10963
10964    cx.condition(&editor, |editor, _| editor.context_menu_visible())
10965        .await;
10966
10967    editor
10968        .update_in(cx, |editor, window, cx| {
10969            editor
10970                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10971                .unwrap()
10972        })
10973        .await
10974        .unwrap();
10975
10976    editor.update(cx, |editor, cx| {
10977        assert_text_with_selections(editor, expected_multibuffer, cx);
10978    })
10979}
10980
10981#[gpui::test]
10982async fn test_completion(cx: &mut TestAppContext) {
10983    init_test(cx, |_| {});
10984
10985    let mut cx = EditorLspTestContext::new_rust(
10986        lsp::ServerCapabilities {
10987            completion_provider: Some(lsp::CompletionOptions {
10988                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10989                resolve_provider: Some(true),
10990                ..Default::default()
10991            }),
10992            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10993            ..Default::default()
10994        },
10995        cx,
10996    )
10997    .await;
10998    let counter = Arc::new(AtomicUsize::new(0));
10999
11000    cx.set_state(indoc! {"
11001        oneˇ
11002        two
11003        three
11004    "});
11005    cx.simulate_keystroke(".");
11006    handle_completion_request(
11007        &mut cx,
11008        indoc! {"
11009            one.|<>
11010            two
11011            three
11012        "},
11013        vec!["first_completion", "second_completion"],
11014        counter.clone(),
11015    )
11016    .await;
11017    cx.condition(|editor, _| editor.context_menu_visible())
11018        .await;
11019    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11020
11021    let _handler = handle_signature_help_request(
11022        &mut cx,
11023        lsp::SignatureHelp {
11024            signatures: vec![lsp::SignatureInformation {
11025                label: "test signature".to_string(),
11026                documentation: None,
11027                parameters: Some(vec![lsp::ParameterInformation {
11028                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11029                    documentation: None,
11030                }]),
11031                active_parameter: None,
11032            }],
11033            active_signature: None,
11034            active_parameter: None,
11035        },
11036    );
11037    cx.update_editor(|editor, window, cx| {
11038        assert!(
11039            !editor.signature_help_state.is_shown(),
11040            "No signature help was called for"
11041        );
11042        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11043    });
11044    cx.run_until_parked();
11045    cx.update_editor(|editor, _, _| {
11046        assert!(
11047            !editor.signature_help_state.is_shown(),
11048            "No signature help should be shown when completions menu is open"
11049        );
11050    });
11051
11052    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11053        editor.context_menu_next(&Default::default(), window, cx);
11054        editor
11055            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11056            .unwrap()
11057    });
11058    cx.assert_editor_state(indoc! {"
11059        one.second_completionˇ
11060        two
11061        three
11062    "});
11063
11064    handle_resolve_completion_request(
11065        &mut cx,
11066        Some(vec![
11067            (
11068                //This overlaps with the primary completion edit which is
11069                //misbehavior from the LSP spec, test that we filter it out
11070                indoc! {"
11071                    one.second_ˇcompletion
11072                    two
11073                    threeˇ
11074                "},
11075                "overlapping additional edit",
11076            ),
11077            (
11078                indoc! {"
11079                    one.second_completion
11080                    two
11081                    threeˇ
11082                "},
11083                "\nadditional edit",
11084            ),
11085        ]),
11086    )
11087    .await;
11088    apply_additional_edits.await.unwrap();
11089    cx.assert_editor_state(indoc! {"
11090        one.second_completionˇ
11091        two
11092        three
11093        additional edit
11094    "});
11095
11096    cx.set_state(indoc! {"
11097        one.second_completion
11098        twoˇ
11099        threeˇ
11100        additional edit
11101    "});
11102    cx.simulate_keystroke(" ");
11103    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11104    cx.simulate_keystroke("s");
11105    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11106
11107    cx.assert_editor_state(indoc! {"
11108        one.second_completion
11109        two sˇ
11110        three sˇ
11111        additional edit
11112    "});
11113    handle_completion_request(
11114        &mut cx,
11115        indoc! {"
11116            one.second_completion
11117            two s
11118            three <s|>
11119            additional edit
11120        "},
11121        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11122        counter.clone(),
11123    )
11124    .await;
11125    cx.condition(|editor, _| editor.context_menu_visible())
11126        .await;
11127    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11128
11129    cx.simulate_keystroke("i");
11130
11131    handle_completion_request(
11132        &mut cx,
11133        indoc! {"
11134            one.second_completion
11135            two si
11136            three <si|>
11137            additional edit
11138        "},
11139        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11140        counter.clone(),
11141    )
11142    .await;
11143    cx.condition(|editor, _| editor.context_menu_visible())
11144        .await;
11145    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11146
11147    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11148        editor
11149            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11150            .unwrap()
11151    });
11152    cx.assert_editor_state(indoc! {"
11153        one.second_completion
11154        two sixth_completionˇ
11155        three sixth_completionˇ
11156        additional edit
11157    "});
11158
11159    apply_additional_edits.await.unwrap();
11160
11161    update_test_language_settings(&mut cx, |settings| {
11162        settings.defaults.show_completions_on_input = Some(false);
11163    });
11164    cx.set_state("editorˇ");
11165    cx.simulate_keystroke(".");
11166    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11167    cx.simulate_keystrokes("c l o");
11168    cx.assert_editor_state("editor.cloˇ");
11169    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11170    cx.update_editor(|editor, window, cx| {
11171        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11172    });
11173    handle_completion_request(
11174        &mut cx,
11175        "editor.<clo|>",
11176        vec!["close", "clobber"],
11177        counter.clone(),
11178    )
11179    .await;
11180    cx.condition(|editor, _| editor.context_menu_visible())
11181        .await;
11182    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11183
11184    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11185        editor
11186            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11187            .unwrap()
11188    });
11189    cx.assert_editor_state("editor.closeˇ");
11190    handle_resolve_completion_request(&mut cx, None).await;
11191    apply_additional_edits.await.unwrap();
11192}
11193
11194#[gpui::test]
11195async fn test_word_completion(cx: &mut TestAppContext) {
11196    let lsp_fetch_timeout_ms = 10;
11197    init_test(cx, |language_settings| {
11198        language_settings.defaults.completions = Some(CompletionSettings {
11199            words: WordsCompletionMode::Fallback,
11200            lsp: true,
11201            lsp_fetch_timeout_ms: 10,
11202            lsp_insert_mode: LspInsertMode::Insert,
11203        });
11204    });
11205
11206    let mut cx = EditorLspTestContext::new_rust(
11207        lsp::ServerCapabilities {
11208            completion_provider: Some(lsp::CompletionOptions {
11209                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11210                ..lsp::CompletionOptions::default()
11211            }),
11212            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11213            ..lsp::ServerCapabilities::default()
11214        },
11215        cx,
11216    )
11217    .await;
11218
11219    let throttle_completions = Arc::new(AtomicBool::new(false));
11220
11221    let lsp_throttle_completions = throttle_completions.clone();
11222    let _completion_requests_handler =
11223        cx.lsp
11224            .server
11225            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11226                let lsp_throttle_completions = lsp_throttle_completions.clone();
11227                let cx = cx.clone();
11228                async move {
11229                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11230                        cx.background_executor()
11231                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11232                            .await;
11233                    }
11234                    Ok(Some(lsp::CompletionResponse::Array(vec![
11235                        lsp::CompletionItem {
11236                            label: "first".into(),
11237                            ..lsp::CompletionItem::default()
11238                        },
11239                        lsp::CompletionItem {
11240                            label: "last".into(),
11241                            ..lsp::CompletionItem::default()
11242                        },
11243                    ])))
11244                }
11245            });
11246
11247    cx.set_state(indoc! {"
11248        oneˇ
11249        two
11250        three
11251    "});
11252    cx.simulate_keystroke(".");
11253    cx.executor().run_until_parked();
11254    cx.condition(|editor, _| editor.context_menu_visible())
11255        .await;
11256    cx.update_editor(|editor, window, cx| {
11257        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11258        {
11259            assert_eq!(
11260                completion_menu_entries(&menu),
11261                &["first", "last"],
11262                "When LSP server is fast to reply, no fallback word completions are used"
11263            );
11264        } else {
11265            panic!("expected completion menu to be open");
11266        }
11267        editor.cancel(&Cancel, window, cx);
11268    });
11269    cx.executor().run_until_parked();
11270    cx.condition(|editor, _| !editor.context_menu_visible())
11271        .await;
11272
11273    throttle_completions.store(true, atomic::Ordering::Release);
11274    cx.simulate_keystroke(".");
11275    cx.executor()
11276        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11277    cx.executor().run_until_parked();
11278    cx.condition(|editor, _| editor.context_menu_visible())
11279        .await;
11280    cx.update_editor(|editor, _, _| {
11281        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11282        {
11283            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11284                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11285        } else {
11286            panic!("expected completion menu to be open");
11287        }
11288    });
11289}
11290
11291#[gpui::test]
11292async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11293    init_test(cx, |language_settings| {
11294        language_settings.defaults.completions = Some(CompletionSettings {
11295            words: WordsCompletionMode::Enabled,
11296            lsp: true,
11297            lsp_fetch_timeout_ms: 0,
11298            lsp_insert_mode: LspInsertMode::Insert,
11299        });
11300    });
11301
11302    let mut cx = EditorLspTestContext::new_rust(
11303        lsp::ServerCapabilities {
11304            completion_provider: Some(lsp::CompletionOptions {
11305                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11306                ..lsp::CompletionOptions::default()
11307            }),
11308            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11309            ..lsp::ServerCapabilities::default()
11310        },
11311        cx,
11312    )
11313    .await;
11314
11315    let _completion_requests_handler =
11316        cx.lsp
11317            .server
11318            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11319                Ok(Some(lsp::CompletionResponse::Array(vec![
11320                    lsp::CompletionItem {
11321                        label: "first".into(),
11322                        ..lsp::CompletionItem::default()
11323                    },
11324                    lsp::CompletionItem {
11325                        label: "last".into(),
11326                        ..lsp::CompletionItem::default()
11327                    },
11328                ])))
11329            });
11330
11331    cx.set_state(indoc! {"ˇ
11332        first
11333        last
11334        second
11335    "});
11336    cx.simulate_keystroke(".");
11337    cx.executor().run_until_parked();
11338    cx.condition(|editor, _| editor.context_menu_visible())
11339        .await;
11340    cx.update_editor(|editor, _, _| {
11341        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11342        {
11343            assert_eq!(
11344                completion_menu_entries(&menu),
11345                &["first", "last", "second"],
11346                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11347            );
11348        } else {
11349            panic!("expected completion menu to be open");
11350        }
11351    });
11352}
11353
11354#[gpui::test]
11355async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11356    init_test(cx, |language_settings| {
11357        language_settings.defaults.completions = Some(CompletionSettings {
11358            words: WordsCompletionMode::Disabled,
11359            lsp: true,
11360            lsp_fetch_timeout_ms: 0,
11361            lsp_insert_mode: LspInsertMode::Insert,
11362        });
11363    });
11364
11365    let mut cx = EditorLspTestContext::new_rust(
11366        lsp::ServerCapabilities {
11367            completion_provider: Some(lsp::CompletionOptions {
11368                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11369                ..lsp::CompletionOptions::default()
11370            }),
11371            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11372            ..lsp::ServerCapabilities::default()
11373        },
11374        cx,
11375    )
11376    .await;
11377
11378    let _completion_requests_handler =
11379        cx.lsp
11380            .server
11381            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11382                panic!("LSP completions should not be queried when dealing with word completions")
11383            });
11384
11385    cx.set_state(indoc! {"ˇ
11386        first
11387        last
11388        second
11389    "});
11390    cx.update_editor(|editor, window, cx| {
11391        editor.show_word_completions(&ShowWordCompletions, window, cx);
11392    });
11393    cx.executor().run_until_parked();
11394    cx.condition(|editor, _| editor.context_menu_visible())
11395        .await;
11396    cx.update_editor(|editor, _, _| {
11397        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11398        {
11399            assert_eq!(
11400                completion_menu_entries(&menu),
11401                &["first", "last", "second"],
11402                "`ShowWordCompletions` action should show word completions"
11403            );
11404        } else {
11405            panic!("expected completion menu to be open");
11406        }
11407    });
11408
11409    cx.simulate_keystroke("l");
11410    cx.executor().run_until_parked();
11411    cx.condition(|editor, _| editor.context_menu_visible())
11412        .await;
11413    cx.update_editor(|editor, _, _| {
11414        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11415        {
11416            assert_eq!(
11417                completion_menu_entries(&menu),
11418                &["last"],
11419                "After showing word completions, further editing should filter them and not query the LSP"
11420            );
11421        } else {
11422            panic!("expected completion menu to be open");
11423        }
11424    });
11425}
11426
11427#[gpui::test]
11428async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11429    init_test(cx, |language_settings| {
11430        language_settings.defaults.completions = Some(CompletionSettings {
11431            words: WordsCompletionMode::Fallback,
11432            lsp: false,
11433            lsp_fetch_timeout_ms: 0,
11434            lsp_insert_mode: LspInsertMode::Insert,
11435        });
11436    });
11437
11438    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11439
11440    cx.set_state(indoc! {"ˇ
11441        0_usize
11442        let
11443        33
11444        4.5f32
11445    "});
11446    cx.update_editor(|editor, window, cx| {
11447        editor.show_completions(&ShowCompletions::default(), window, cx);
11448    });
11449    cx.executor().run_until_parked();
11450    cx.condition(|editor, _| editor.context_menu_visible())
11451        .await;
11452    cx.update_editor(|editor, window, cx| {
11453        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11454        {
11455            assert_eq!(
11456                completion_menu_entries(&menu),
11457                &["let"],
11458                "With no digits in the completion query, no digits should be in the word completions"
11459            );
11460        } else {
11461            panic!("expected completion menu to be open");
11462        }
11463        editor.cancel(&Cancel, window, cx);
11464    });
11465
11466    cx.set_state(indoc! {"11467        0_usize
11468        let
11469        3
11470        33.35f32
11471    "});
11472    cx.update_editor(|editor, window, cx| {
11473        editor.show_completions(&ShowCompletions::default(), window, cx);
11474    });
11475    cx.executor().run_until_parked();
11476    cx.condition(|editor, _| editor.context_menu_visible())
11477        .await;
11478    cx.update_editor(|editor, _, _| {
11479        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11480        {
11481            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11482                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11483        } else {
11484            panic!("expected completion menu to be open");
11485        }
11486    });
11487}
11488
11489fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11490    let position = || lsp::Position {
11491        line: params.text_document_position.position.line,
11492        character: params.text_document_position.position.character,
11493    };
11494    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11495        range: lsp::Range {
11496            start: position(),
11497            end: position(),
11498        },
11499        new_text: text.to_string(),
11500    }))
11501}
11502
11503#[gpui::test]
11504async fn test_multiline_completion(cx: &mut TestAppContext) {
11505    init_test(cx, |_| {});
11506
11507    let fs = FakeFs::new(cx.executor());
11508    fs.insert_tree(
11509        path!("/a"),
11510        json!({
11511            "main.ts": "a",
11512        }),
11513    )
11514    .await;
11515
11516    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11517    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11518    let typescript_language = Arc::new(Language::new(
11519        LanguageConfig {
11520            name: "TypeScript".into(),
11521            matcher: LanguageMatcher {
11522                path_suffixes: vec!["ts".to_string()],
11523                ..LanguageMatcher::default()
11524            },
11525            line_comments: vec!["// ".into()],
11526            ..LanguageConfig::default()
11527        },
11528        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11529    ));
11530    language_registry.add(typescript_language.clone());
11531    let mut fake_servers = language_registry.register_fake_lsp(
11532        "TypeScript",
11533        FakeLspAdapter {
11534            capabilities: lsp::ServerCapabilities {
11535                completion_provider: Some(lsp::CompletionOptions {
11536                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11537                    ..lsp::CompletionOptions::default()
11538                }),
11539                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11540                ..lsp::ServerCapabilities::default()
11541            },
11542            // Emulate vtsls label generation
11543            label_for_completion: Some(Box::new(|item, _| {
11544                let text = if let Some(description) = item
11545                    .label_details
11546                    .as_ref()
11547                    .and_then(|label_details| label_details.description.as_ref())
11548                {
11549                    format!("{} {}", item.label, description)
11550                } else if let Some(detail) = &item.detail {
11551                    format!("{} {}", item.label, detail)
11552                } else {
11553                    item.label.clone()
11554                };
11555                let len = text.len();
11556                Some(language::CodeLabel {
11557                    text,
11558                    runs: Vec::new(),
11559                    filter_range: 0..len,
11560                })
11561            })),
11562            ..FakeLspAdapter::default()
11563        },
11564    );
11565    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11566    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11567    let worktree_id = workspace
11568        .update(cx, |workspace, _window, cx| {
11569            workspace.project().update(cx, |project, cx| {
11570                project.worktrees(cx).next().unwrap().read(cx).id()
11571            })
11572        })
11573        .unwrap();
11574    let _buffer = project
11575        .update(cx, |project, cx| {
11576            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11577        })
11578        .await
11579        .unwrap();
11580    let editor = workspace
11581        .update(cx, |workspace, window, cx| {
11582            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11583        })
11584        .unwrap()
11585        .await
11586        .unwrap()
11587        .downcast::<Editor>()
11588        .unwrap();
11589    let fake_server = fake_servers.next().await.unwrap();
11590
11591    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11592    let multiline_label_2 = "a\nb\nc\n";
11593    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11594    let multiline_description = "d\ne\nf\n";
11595    let multiline_detail_2 = "g\nh\ni\n";
11596
11597    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11598        move |params, _| async move {
11599            Ok(Some(lsp::CompletionResponse::Array(vec![
11600                lsp::CompletionItem {
11601                    label: multiline_label.to_string(),
11602                    text_edit: gen_text_edit(&params, "new_text_1"),
11603                    ..lsp::CompletionItem::default()
11604                },
11605                lsp::CompletionItem {
11606                    label: "single line label 1".to_string(),
11607                    detail: Some(multiline_detail.to_string()),
11608                    text_edit: gen_text_edit(&params, "new_text_2"),
11609                    ..lsp::CompletionItem::default()
11610                },
11611                lsp::CompletionItem {
11612                    label: "single line label 2".to_string(),
11613                    label_details: Some(lsp::CompletionItemLabelDetails {
11614                        description: Some(multiline_description.to_string()),
11615                        detail: None,
11616                    }),
11617                    text_edit: gen_text_edit(&params, "new_text_2"),
11618                    ..lsp::CompletionItem::default()
11619                },
11620                lsp::CompletionItem {
11621                    label: multiline_label_2.to_string(),
11622                    detail: Some(multiline_detail_2.to_string()),
11623                    text_edit: gen_text_edit(&params, "new_text_3"),
11624                    ..lsp::CompletionItem::default()
11625                },
11626                lsp::CompletionItem {
11627                    label: "Label with many     spaces and \t but without newlines".to_string(),
11628                    detail: Some(
11629                        "Details with many     spaces and \t but without newlines".to_string(),
11630                    ),
11631                    text_edit: gen_text_edit(&params, "new_text_4"),
11632                    ..lsp::CompletionItem::default()
11633                },
11634            ])))
11635        },
11636    );
11637
11638    editor.update_in(cx, |editor, window, cx| {
11639        cx.focus_self(window);
11640        editor.move_to_end(&MoveToEnd, window, cx);
11641        editor.handle_input(".", window, cx);
11642    });
11643    cx.run_until_parked();
11644    completion_handle.next().await.unwrap();
11645
11646    editor.update(cx, |editor, _| {
11647        assert!(editor.context_menu_visible());
11648        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11649        {
11650            let completion_labels = menu
11651                .completions
11652                .borrow()
11653                .iter()
11654                .map(|c| c.label.text.clone())
11655                .collect::<Vec<_>>();
11656            assert_eq!(
11657                completion_labels,
11658                &[
11659                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11660                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11661                    "single line label 2 d e f ",
11662                    "a b c g h i ",
11663                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11664                ],
11665                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11666            );
11667
11668            for completion in menu
11669                .completions
11670                .borrow()
11671                .iter() {
11672                    assert_eq!(
11673                        completion.label.filter_range,
11674                        0..completion.label.text.len(),
11675                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11676                    );
11677                }
11678        } else {
11679            panic!("expected completion menu to be open");
11680        }
11681    });
11682}
11683
11684#[gpui::test]
11685async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11686    init_test(cx, |_| {});
11687    let mut cx = EditorLspTestContext::new_rust(
11688        lsp::ServerCapabilities {
11689            completion_provider: Some(lsp::CompletionOptions {
11690                trigger_characters: Some(vec![".".to_string()]),
11691                ..Default::default()
11692            }),
11693            ..Default::default()
11694        },
11695        cx,
11696    )
11697    .await;
11698    cx.lsp
11699        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11700            Ok(Some(lsp::CompletionResponse::Array(vec![
11701                lsp::CompletionItem {
11702                    label: "first".into(),
11703                    ..Default::default()
11704                },
11705                lsp::CompletionItem {
11706                    label: "last".into(),
11707                    ..Default::default()
11708                },
11709            ])))
11710        });
11711    cx.set_state("variableˇ");
11712    cx.simulate_keystroke(".");
11713    cx.executor().run_until_parked();
11714
11715    cx.update_editor(|editor, _, _| {
11716        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11717        {
11718            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11719        } else {
11720            panic!("expected completion menu to be open");
11721        }
11722    });
11723
11724    cx.update_editor(|editor, window, cx| {
11725        editor.move_page_down(&MovePageDown::default(), window, cx);
11726        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11727        {
11728            assert!(
11729                menu.selected_item == 1,
11730                "expected PageDown to select the last item from the context menu"
11731            );
11732        } else {
11733            panic!("expected completion menu to stay open after PageDown");
11734        }
11735    });
11736
11737    cx.update_editor(|editor, window, cx| {
11738        editor.move_page_up(&MovePageUp::default(), window, cx);
11739        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11740        {
11741            assert!(
11742                menu.selected_item == 0,
11743                "expected PageUp to select the first item from the context menu"
11744            );
11745        } else {
11746            panic!("expected completion menu to stay open after PageUp");
11747        }
11748    });
11749}
11750
11751#[gpui::test]
11752async fn test_as_is_completions(cx: &mut TestAppContext) {
11753    init_test(cx, |_| {});
11754    let mut cx = EditorLspTestContext::new_rust(
11755        lsp::ServerCapabilities {
11756            completion_provider: Some(lsp::CompletionOptions {
11757                ..Default::default()
11758            }),
11759            ..Default::default()
11760        },
11761        cx,
11762    )
11763    .await;
11764    cx.lsp
11765        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11766            Ok(Some(lsp::CompletionResponse::Array(vec![
11767                lsp::CompletionItem {
11768                    label: "unsafe".into(),
11769                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11770                        range: lsp::Range {
11771                            start: lsp::Position {
11772                                line: 1,
11773                                character: 2,
11774                            },
11775                            end: lsp::Position {
11776                                line: 1,
11777                                character: 3,
11778                            },
11779                        },
11780                        new_text: "unsafe".to_string(),
11781                    })),
11782                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11783                    ..Default::default()
11784                },
11785            ])))
11786        });
11787    cx.set_state("fn a() {}\n");
11788    cx.executor().run_until_parked();
11789    cx.update_editor(|editor, window, cx| {
11790        editor.show_completions(
11791            &ShowCompletions {
11792                trigger: Some("\n".into()),
11793            },
11794            window,
11795            cx,
11796        );
11797    });
11798    cx.executor().run_until_parked();
11799
11800    cx.update_editor(|editor, window, cx| {
11801        editor.confirm_completion(&Default::default(), window, cx)
11802    });
11803    cx.executor().run_until_parked();
11804    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11805}
11806
11807#[gpui::test]
11808async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11809    init_test(cx, |_| {});
11810
11811    let mut cx = EditorLspTestContext::new_rust(
11812        lsp::ServerCapabilities {
11813            completion_provider: Some(lsp::CompletionOptions {
11814                trigger_characters: Some(vec![".".to_string()]),
11815                resolve_provider: Some(true),
11816                ..Default::default()
11817            }),
11818            ..Default::default()
11819        },
11820        cx,
11821    )
11822    .await;
11823
11824    cx.set_state("fn main() { let a = 2ˇ; }");
11825    cx.simulate_keystroke(".");
11826    let completion_item = lsp::CompletionItem {
11827        label: "Some".into(),
11828        kind: Some(lsp::CompletionItemKind::SNIPPET),
11829        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11830        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11831            kind: lsp::MarkupKind::Markdown,
11832            value: "```rust\nSome(2)\n```".to_string(),
11833        })),
11834        deprecated: Some(false),
11835        sort_text: Some("Some".to_string()),
11836        filter_text: Some("Some".to_string()),
11837        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11838        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11839            range: lsp::Range {
11840                start: lsp::Position {
11841                    line: 0,
11842                    character: 22,
11843                },
11844                end: lsp::Position {
11845                    line: 0,
11846                    character: 22,
11847                },
11848            },
11849            new_text: "Some(2)".to_string(),
11850        })),
11851        additional_text_edits: Some(vec![lsp::TextEdit {
11852            range: lsp::Range {
11853                start: lsp::Position {
11854                    line: 0,
11855                    character: 20,
11856                },
11857                end: lsp::Position {
11858                    line: 0,
11859                    character: 22,
11860                },
11861            },
11862            new_text: "".to_string(),
11863        }]),
11864        ..Default::default()
11865    };
11866
11867    let closure_completion_item = completion_item.clone();
11868    let counter = Arc::new(AtomicUsize::new(0));
11869    let counter_clone = counter.clone();
11870    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11871        let task_completion_item = closure_completion_item.clone();
11872        counter_clone.fetch_add(1, atomic::Ordering::Release);
11873        async move {
11874            Ok(Some(lsp::CompletionResponse::Array(vec![
11875                task_completion_item,
11876            ])))
11877        }
11878    });
11879
11880    cx.condition(|editor, _| editor.context_menu_visible())
11881        .await;
11882    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11883    assert!(request.next().await.is_some());
11884    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11885
11886    cx.simulate_keystrokes("S o m");
11887    cx.condition(|editor, _| editor.context_menu_visible())
11888        .await;
11889    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11890    assert!(request.next().await.is_some());
11891    assert!(request.next().await.is_some());
11892    assert!(request.next().await.is_some());
11893    request.close();
11894    assert!(request.next().await.is_none());
11895    assert_eq!(
11896        counter.load(atomic::Ordering::Acquire),
11897        4,
11898        "With the completions menu open, only one LSP request should happen per input"
11899    );
11900}
11901
11902#[gpui::test]
11903async fn test_toggle_comment(cx: &mut TestAppContext) {
11904    init_test(cx, |_| {});
11905    let mut cx = EditorTestContext::new(cx).await;
11906    let language = Arc::new(Language::new(
11907        LanguageConfig {
11908            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11909            ..Default::default()
11910        },
11911        Some(tree_sitter_rust::LANGUAGE.into()),
11912    ));
11913    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11914
11915    // If multiple selections intersect a line, the line is only toggled once.
11916    cx.set_state(indoc! {"
11917        fn a() {
11918            «//b();
11919            ˇ»// «c();
11920            //ˇ»  d();
11921        }
11922    "});
11923
11924    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11925
11926    cx.assert_editor_state(indoc! {"
11927        fn a() {
11928            «b();
11929            c();
11930            ˇ» d();
11931        }
11932    "});
11933
11934    // The comment prefix is inserted at the same column for every line in a
11935    // selection.
11936    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11937
11938    cx.assert_editor_state(indoc! {"
11939        fn a() {
11940            // «b();
11941            // c();
11942            ˇ»//  d();
11943        }
11944    "});
11945
11946    // If a selection ends at the beginning of a line, that line is not toggled.
11947    cx.set_selections_state(indoc! {"
11948        fn a() {
11949            // b();
11950            «// c();
11951        ˇ»    //  d();
11952        }
11953    "});
11954
11955    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11956
11957    cx.assert_editor_state(indoc! {"
11958        fn a() {
11959            // b();
11960            «c();
11961        ˇ»    //  d();
11962        }
11963    "});
11964
11965    // If a selection span a single line and is empty, the line is toggled.
11966    cx.set_state(indoc! {"
11967        fn a() {
11968            a();
11969            b();
11970        ˇ
11971        }
11972    "});
11973
11974    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11975
11976    cx.assert_editor_state(indoc! {"
11977        fn a() {
11978            a();
11979            b();
11980        //•ˇ
11981        }
11982    "});
11983
11984    // If a selection span multiple lines, empty lines are not toggled.
11985    cx.set_state(indoc! {"
11986        fn a() {
11987            «a();
11988
11989            c();ˇ»
11990        }
11991    "});
11992
11993    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11994
11995    cx.assert_editor_state(indoc! {"
11996        fn a() {
11997            // «a();
11998
11999            // c();ˇ»
12000        }
12001    "});
12002
12003    // If a selection includes multiple comment prefixes, all lines are uncommented.
12004    cx.set_state(indoc! {"
12005        fn a() {
12006            «// a();
12007            /// b();
12008            //! c();ˇ»
12009        }
12010    "});
12011
12012    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12013
12014    cx.assert_editor_state(indoc! {"
12015        fn a() {
12016            «a();
12017            b();
12018            c();ˇ»
12019        }
12020    "});
12021}
12022
12023#[gpui::test]
12024async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12025    init_test(cx, |_| {});
12026    let mut cx = EditorTestContext::new(cx).await;
12027    let language = Arc::new(Language::new(
12028        LanguageConfig {
12029            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12030            ..Default::default()
12031        },
12032        Some(tree_sitter_rust::LANGUAGE.into()),
12033    ));
12034    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12035
12036    let toggle_comments = &ToggleComments {
12037        advance_downwards: false,
12038        ignore_indent: true,
12039    };
12040
12041    // If multiple selections intersect a line, the line is only toggled once.
12042    cx.set_state(indoc! {"
12043        fn a() {
12044        //    «b();
12045        //    c();
12046        //    ˇ» d();
12047        }
12048    "});
12049
12050    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12051
12052    cx.assert_editor_state(indoc! {"
12053        fn a() {
12054            «b();
12055            c();
12056            ˇ» d();
12057        }
12058    "});
12059
12060    // The comment prefix is inserted at the beginning of each line
12061    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12062
12063    cx.assert_editor_state(indoc! {"
12064        fn a() {
12065        //    «b();
12066        //    c();
12067        //    ˇ» d();
12068        }
12069    "});
12070
12071    // If a selection ends at the beginning of a line, that line is not toggled.
12072    cx.set_selections_state(indoc! {"
12073        fn a() {
12074        //    b();
12075        //    «c();
12076        ˇ»//     d();
12077        }
12078    "});
12079
12080    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12081
12082    cx.assert_editor_state(indoc! {"
12083        fn a() {
12084        //    b();
12085            «c();
12086        ˇ»//     d();
12087        }
12088    "});
12089
12090    // If a selection span a single line and is empty, the line is toggled.
12091    cx.set_state(indoc! {"
12092        fn a() {
12093            a();
12094            b();
12095        ˇ
12096        }
12097    "});
12098
12099    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12100
12101    cx.assert_editor_state(indoc! {"
12102        fn a() {
12103            a();
12104            b();
12105        //ˇ
12106        }
12107    "});
12108
12109    // If a selection span multiple lines, empty lines are not toggled.
12110    cx.set_state(indoc! {"
12111        fn a() {
12112            «a();
12113
12114            c();ˇ»
12115        }
12116    "});
12117
12118    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12119
12120    cx.assert_editor_state(indoc! {"
12121        fn a() {
12122        //    «a();
12123
12124        //    c();ˇ»
12125        }
12126    "});
12127
12128    // If a selection includes multiple comment prefixes, all lines are uncommented.
12129    cx.set_state(indoc! {"
12130        fn a() {
12131        //    «a();
12132        ///    b();
12133        //!    c();ˇ»
12134        }
12135    "});
12136
12137    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12138
12139    cx.assert_editor_state(indoc! {"
12140        fn a() {
12141            «a();
12142            b();
12143            c();ˇ»
12144        }
12145    "});
12146}
12147
12148#[gpui::test]
12149async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12150    init_test(cx, |_| {});
12151
12152    let language = Arc::new(Language::new(
12153        LanguageConfig {
12154            line_comments: vec!["// ".into()],
12155            ..Default::default()
12156        },
12157        Some(tree_sitter_rust::LANGUAGE.into()),
12158    ));
12159
12160    let mut cx = EditorTestContext::new(cx).await;
12161
12162    cx.language_registry().add(language.clone());
12163    cx.update_buffer(|buffer, cx| {
12164        buffer.set_language(Some(language), cx);
12165    });
12166
12167    let toggle_comments = &ToggleComments {
12168        advance_downwards: true,
12169        ignore_indent: false,
12170    };
12171
12172    // Single cursor on one line -> advance
12173    // Cursor moves horizontally 3 characters as well on non-blank line
12174    cx.set_state(indoc!(
12175        "fn a() {
12176             ˇdog();
12177             cat();
12178        }"
12179    ));
12180    cx.update_editor(|editor, window, cx| {
12181        editor.toggle_comments(toggle_comments, window, cx);
12182    });
12183    cx.assert_editor_state(indoc!(
12184        "fn a() {
12185             // dog();
12186             catˇ();
12187        }"
12188    ));
12189
12190    // Single selection on one line -> don't advance
12191    cx.set_state(indoc!(
12192        "fn a() {
12193             «dog()ˇ»;
12194             cat();
12195        }"
12196    ));
12197    cx.update_editor(|editor, window, cx| {
12198        editor.toggle_comments(toggle_comments, window, cx);
12199    });
12200    cx.assert_editor_state(indoc!(
12201        "fn a() {
12202             // «dog()ˇ»;
12203             cat();
12204        }"
12205    ));
12206
12207    // Multiple cursors on one line -> advance
12208    cx.set_state(indoc!(
12209        "fn a() {
12210             ˇdˇog();
12211             cat();
12212        }"
12213    ));
12214    cx.update_editor(|editor, window, cx| {
12215        editor.toggle_comments(toggle_comments, window, cx);
12216    });
12217    cx.assert_editor_state(indoc!(
12218        "fn a() {
12219             // dog();
12220             catˇ(ˇ);
12221        }"
12222    ));
12223
12224    // Multiple cursors on one line, with selection -> don't advance
12225    cx.set_state(indoc!(
12226        "fn a() {
12227             ˇdˇog«()ˇ»;
12228             cat();
12229        }"
12230    ));
12231    cx.update_editor(|editor, window, cx| {
12232        editor.toggle_comments(toggle_comments, window, cx);
12233    });
12234    cx.assert_editor_state(indoc!(
12235        "fn a() {
12236             // ˇdˇog«()ˇ»;
12237             cat();
12238        }"
12239    ));
12240
12241    // Single cursor on one line -> advance
12242    // Cursor moves to column 0 on blank line
12243    cx.set_state(indoc!(
12244        "fn a() {
12245             ˇdog();
12246
12247             cat();
12248        }"
12249    ));
12250    cx.update_editor(|editor, window, cx| {
12251        editor.toggle_comments(toggle_comments, window, cx);
12252    });
12253    cx.assert_editor_state(indoc!(
12254        "fn a() {
12255             // dog();
12256        ˇ
12257             cat();
12258        }"
12259    ));
12260
12261    // Single cursor on one line -> advance
12262    // Cursor starts and ends at column 0
12263    cx.set_state(indoc!(
12264        "fn a() {
12265         ˇ    dog();
12266             cat();
12267        }"
12268    ));
12269    cx.update_editor(|editor, window, cx| {
12270        editor.toggle_comments(toggle_comments, window, cx);
12271    });
12272    cx.assert_editor_state(indoc!(
12273        "fn a() {
12274             // dog();
12275         ˇ    cat();
12276        }"
12277    ));
12278}
12279
12280#[gpui::test]
12281async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12282    init_test(cx, |_| {});
12283
12284    let mut cx = EditorTestContext::new(cx).await;
12285
12286    let html_language = Arc::new(
12287        Language::new(
12288            LanguageConfig {
12289                name: "HTML".into(),
12290                block_comment: Some(("<!-- ".into(), " -->".into())),
12291                ..Default::default()
12292            },
12293            Some(tree_sitter_html::LANGUAGE.into()),
12294        )
12295        .with_injection_query(
12296            r#"
12297            (script_element
12298                (raw_text) @injection.content
12299                (#set! injection.language "javascript"))
12300            "#,
12301        )
12302        .unwrap(),
12303    );
12304
12305    let javascript_language = Arc::new(Language::new(
12306        LanguageConfig {
12307            name: "JavaScript".into(),
12308            line_comments: vec!["// ".into()],
12309            ..Default::default()
12310        },
12311        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12312    ));
12313
12314    cx.language_registry().add(html_language.clone());
12315    cx.language_registry().add(javascript_language.clone());
12316    cx.update_buffer(|buffer, cx| {
12317        buffer.set_language(Some(html_language), cx);
12318    });
12319
12320    // Toggle comments for empty selections
12321    cx.set_state(
12322        &r#"
12323            <p>A</p>ˇ
12324            <p>B</p>ˇ
12325            <p>C</p>ˇ
12326        "#
12327        .unindent(),
12328    );
12329    cx.update_editor(|editor, window, cx| {
12330        editor.toggle_comments(&ToggleComments::default(), window, cx)
12331    });
12332    cx.assert_editor_state(
12333        &r#"
12334            <!-- <p>A</p>ˇ -->
12335            <!-- <p>B</p>ˇ -->
12336            <!-- <p>C</p>ˇ -->
12337        "#
12338        .unindent(),
12339    );
12340    cx.update_editor(|editor, window, cx| {
12341        editor.toggle_comments(&ToggleComments::default(), window, cx)
12342    });
12343    cx.assert_editor_state(
12344        &r#"
12345            <p>A</p>ˇ
12346            <p>B</p>ˇ
12347            <p>C</p>ˇ
12348        "#
12349        .unindent(),
12350    );
12351
12352    // Toggle comments for mixture of empty and non-empty selections, where
12353    // multiple selections occupy a given line.
12354    cx.set_state(
12355        &r#"
12356            <p>A«</p>
12357            <p>ˇ»B</p>ˇ
12358            <p>C«</p>
12359            <p>ˇ»D</p>ˇ
12360        "#
12361        .unindent(),
12362    );
12363
12364    cx.update_editor(|editor, window, cx| {
12365        editor.toggle_comments(&ToggleComments::default(), window, cx)
12366    });
12367    cx.assert_editor_state(
12368        &r#"
12369            <!-- <p>A«</p>
12370            <p>ˇ»B</p>ˇ -->
12371            <!-- <p>C«</p>
12372            <p>ˇ»D</p>ˇ -->
12373        "#
12374        .unindent(),
12375    );
12376    cx.update_editor(|editor, window, cx| {
12377        editor.toggle_comments(&ToggleComments::default(), window, cx)
12378    });
12379    cx.assert_editor_state(
12380        &r#"
12381            <p>A«</p>
12382            <p>ˇ»B</p>ˇ
12383            <p>C«</p>
12384            <p>ˇ»D</p>ˇ
12385        "#
12386        .unindent(),
12387    );
12388
12389    // Toggle comments when different languages are active for different
12390    // selections.
12391    cx.set_state(
12392        &r#"
12393            ˇ<script>
12394                ˇvar x = new Y();
12395            ˇ</script>
12396        "#
12397        .unindent(),
12398    );
12399    cx.executor().run_until_parked();
12400    cx.update_editor(|editor, window, cx| {
12401        editor.toggle_comments(&ToggleComments::default(), window, cx)
12402    });
12403    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12404    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12405    cx.assert_editor_state(
12406        &r#"
12407            <!-- ˇ<script> -->
12408                // ˇvar x = new Y();
12409            <!-- ˇ</script> -->
12410        "#
12411        .unindent(),
12412    );
12413}
12414
12415#[gpui::test]
12416fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12417    init_test(cx, |_| {});
12418
12419    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12420    let multibuffer = cx.new(|cx| {
12421        let mut multibuffer = MultiBuffer::new(ReadWrite);
12422        multibuffer.push_excerpts(
12423            buffer.clone(),
12424            [
12425                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12426                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12427            ],
12428            cx,
12429        );
12430        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12431        multibuffer
12432    });
12433
12434    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12435    editor.update_in(cx, |editor, window, cx| {
12436        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12437        editor.change_selections(None, window, cx, |s| {
12438            s.select_ranges([
12439                Point::new(0, 0)..Point::new(0, 0),
12440                Point::new(1, 0)..Point::new(1, 0),
12441            ])
12442        });
12443
12444        editor.handle_input("X", window, cx);
12445        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12446        assert_eq!(
12447            editor.selections.ranges(cx),
12448            [
12449                Point::new(0, 1)..Point::new(0, 1),
12450                Point::new(1, 1)..Point::new(1, 1),
12451            ]
12452        );
12453
12454        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12455        editor.change_selections(None, window, cx, |s| {
12456            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12457        });
12458        editor.backspace(&Default::default(), window, cx);
12459        assert_eq!(editor.text(cx), "Xa\nbbb");
12460        assert_eq!(
12461            editor.selections.ranges(cx),
12462            [Point::new(1, 0)..Point::new(1, 0)]
12463        );
12464
12465        editor.change_selections(None, window, cx, |s| {
12466            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12467        });
12468        editor.backspace(&Default::default(), window, cx);
12469        assert_eq!(editor.text(cx), "X\nbb");
12470        assert_eq!(
12471            editor.selections.ranges(cx),
12472            [Point::new(0, 1)..Point::new(0, 1)]
12473        );
12474    });
12475}
12476
12477#[gpui::test]
12478fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12479    init_test(cx, |_| {});
12480
12481    let markers = vec![('[', ']').into(), ('(', ')').into()];
12482    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12483        indoc! {"
12484            [aaaa
12485            (bbbb]
12486            cccc)",
12487        },
12488        markers.clone(),
12489    );
12490    let excerpt_ranges = markers.into_iter().map(|marker| {
12491        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12492        ExcerptRange::new(context.clone())
12493    });
12494    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12495    let multibuffer = cx.new(|cx| {
12496        let mut multibuffer = MultiBuffer::new(ReadWrite);
12497        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12498        multibuffer
12499    });
12500
12501    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12502    editor.update_in(cx, |editor, window, cx| {
12503        let (expected_text, selection_ranges) = marked_text_ranges(
12504            indoc! {"
12505                aaaa
12506                bˇbbb
12507                bˇbbˇb
12508                cccc"
12509            },
12510            true,
12511        );
12512        assert_eq!(editor.text(cx), expected_text);
12513        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12514
12515        editor.handle_input("X", window, cx);
12516
12517        let (expected_text, expected_selections) = marked_text_ranges(
12518            indoc! {"
12519                aaaa
12520                bXˇbbXb
12521                bXˇbbXˇb
12522                cccc"
12523            },
12524            false,
12525        );
12526        assert_eq!(editor.text(cx), expected_text);
12527        assert_eq!(editor.selections.ranges(cx), expected_selections);
12528
12529        editor.newline(&Newline, window, cx);
12530        let (expected_text, expected_selections) = marked_text_ranges(
12531            indoc! {"
12532                aaaa
12533                bX
12534                ˇbbX
12535                b
12536                bX
12537                ˇbbX
12538                ˇb
12539                cccc"
12540            },
12541            false,
12542        );
12543        assert_eq!(editor.text(cx), expected_text);
12544        assert_eq!(editor.selections.ranges(cx), expected_selections);
12545    });
12546}
12547
12548#[gpui::test]
12549fn test_refresh_selections(cx: &mut TestAppContext) {
12550    init_test(cx, |_| {});
12551
12552    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12553    let mut excerpt1_id = None;
12554    let multibuffer = cx.new(|cx| {
12555        let mut multibuffer = MultiBuffer::new(ReadWrite);
12556        excerpt1_id = multibuffer
12557            .push_excerpts(
12558                buffer.clone(),
12559                [
12560                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12561                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12562                ],
12563                cx,
12564            )
12565            .into_iter()
12566            .next();
12567        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12568        multibuffer
12569    });
12570
12571    let editor = cx.add_window(|window, cx| {
12572        let mut editor = build_editor(multibuffer.clone(), window, cx);
12573        let snapshot = editor.snapshot(window, cx);
12574        editor.change_selections(None, window, cx, |s| {
12575            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12576        });
12577        editor.begin_selection(
12578            Point::new(2, 1).to_display_point(&snapshot),
12579            true,
12580            1,
12581            window,
12582            cx,
12583        );
12584        assert_eq!(
12585            editor.selections.ranges(cx),
12586            [
12587                Point::new(1, 3)..Point::new(1, 3),
12588                Point::new(2, 1)..Point::new(2, 1),
12589            ]
12590        );
12591        editor
12592    });
12593
12594    // Refreshing selections is a no-op when excerpts haven't changed.
12595    _ = editor.update(cx, |editor, window, cx| {
12596        editor.change_selections(None, window, cx, |s| s.refresh());
12597        assert_eq!(
12598            editor.selections.ranges(cx),
12599            [
12600                Point::new(1, 3)..Point::new(1, 3),
12601                Point::new(2, 1)..Point::new(2, 1),
12602            ]
12603        );
12604    });
12605
12606    multibuffer.update(cx, |multibuffer, cx| {
12607        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12608    });
12609    _ = editor.update(cx, |editor, window, cx| {
12610        // Removing an excerpt causes the first selection to become degenerate.
12611        assert_eq!(
12612            editor.selections.ranges(cx),
12613            [
12614                Point::new(0, 0)..Point::new(0, 0),
12615                Point::new(0, 1)..Point::new(0, 1)
12616            ]
12617        );
12618
12619        // Refreshing selections will relocate the first selection to the original buffer
12620        // location.
12621        editor.change_selections(None, window, cx, |s| s.refresh());
12622        assert_eq!(
12623            editor.selections.ranges(cx),
12624            [
12625                Point::new(0, 1)..Point::new(0, 1),
12626                Point::new(0, 3)..Point::new(0, 3)
12627            ]
12628        );
12629        assert!(editor.selections.pending_anchor().is_some());
12630    });
12631}
12632
12633#[gpui::test]
12634fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12635    init_test(cx, |_| {});
12636
12637    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12638    let mut excerpt1_id = None;
12639    let multibuffer = cx.new(|cx| {
12640        let mut multibuffer = MultiBuffer::new(ReadWrite);
12641        excerpt1_id = multibuffer
12642            .push_excerpts(
12643                buffer.clone(),
12644                [
12645                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12646                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12647                ],
12648                cx,
12649            )
12650            .into_iter()
12651            .next();
12652        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12653        multibuffer
12654    });
12655
12656    let editor = cx.add_window(|window, cx| {
12657        let mut editor = build_editor(multibuffer.clone(), window, cx);
12658        let snapshot = editor.snapshot(window, cx);
12659        editor.begin_selection(
12660            Point::new(1, 3).to_display_point(&snapshot),
12661            false,
12662            1,
12663            window,
12664            cx,
12665        );
12666        assert_eq!(
12667            editor.selections.ranges(cx),
12668            [Point::new(1, 3)..Point::new(1, 3)]
12669        );
12670        editor
12671    });
12672
12673    multibuffer.update(cx, |multibuffer, cx| {
12674        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12675    });
12676    _ = editor.update(cx, |editor, window, cx| {
12677        assert_eq!(
12678            editor.selections.ranges(cx),
12679            [Point::new(0, 0)..Point::new(0, 0)]
12680        );
12681
12682        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12683        editor.change_selections(None, window, cx, |s| s.refresh());
12684        assert_eq!(
12685            editor.selections.ranges(cx),
12686            [Point::new(0, 3)..Point::new(0, 3)]
12687        );
12688        assert!(editor.selections.pending_anchor().is_some());
12689    });
12690}
12691
12692#[gpui::test]
12693async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12694    init_test(cx, |_| {});
12695
12696    let language = Arc::new(
12697        Language::new(
12698            LanguageConfig {
12699                brackets: BracketPairConfig {
12700                    pairs: vec![
12701                        BracketPair {
12702                            start: "{".to_string(),
12703                            end: "}".to_string(),
12704                            close: true,
12705                            surround: true,
12706                            newline: true,
12707                        },
12708                        BracketPair {
12709                            start: "/* ".to_string(),
12710                            end: " */".to_string(),
12711                            close: true,
12712                            surround: true,
12713                            newline: true,
12714                        },
12715                    ],
12716                    ..Default::default()
12717                },
12718                ..Default::default()
12719            },
12720            Some(tree_sitter_rust::LANGUAGE.into()),
12721        )
12722        .with_indents_query("")
12723        .unwrap(),
12724    );
12725
12726    let text = concat!(
12727        "{   }\n",     //
12728        "  x\n",       //
12729        "  /*   */\n", //
12730        "x\n",         //
12731        "{{} }\n",     //
12732    );
12733
12734    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12737    editor
12738        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12739        .await;
12740
12741    editor.update_in(cx, |editor, window, cx| {
12742        editor.change_selections(None, window, cx, |s| {
12743            s.select_display_ranges([
12744                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12745                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12746                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12747            ])
12748        });
12749        editor.newline(&Newline, window, cx);
12750
12751        assert_eq!(
12752            editor.buffer().read(cx).read(cx).text(),
12753            concat!(
12754                "{ \n",    // Suppress rustfmt
12755                "\n",      //
12756                "}\n",     //
12757                "  x\n",   //
12758                "  /* \n", //
12759                "  \n",    //
12760                "  */\n",  //
12761                "x\n",     //
12762                "{{} \n",  //
12763                "}\n",     //
12764            )
12765        );
12766    });
12767}
12768
12769#[gpui::test]
12770fn test_highlighted_ranges(cx: &mut TestAppContext) {
12771    init_test(cx, |_| {});
12772
12773    let editor = cx.add_window(|window, cx| {
12774        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12775        build_editor(buffer.clone(), window, cx)
12776    });
12777
12778    _ = editor.update(cx, |editor, window, cx| {
12779        struct Type1;
12780        struct Type2;
12781
12782        let buffer = editor.buffer.read(cx).snapshot(cx);
12783
12784        let anchor_range =
12785            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12786
12787        editor.highlight_background::<Type1>(
12788            &[
12789                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12790                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12791                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12792                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12793            ],
12794            |_| Hsla::red(),
12795            cx,
12796        );
12797        editor.highlight_background::<Type2>(
12798            &[
12799                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12800                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12801                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12802                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12803            ],
12804            |_| Hsla::green(),
12805            cx,
12806        );
12807
12808        let snapshot = editor.snapshot(window, cx);
12809        let mut highlighted_ranges = editor.background_highlights_in_range(
12810            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12811            &snapshot,
12812            cx.theme().colors(),
12813        );
12814        // Enforce a consistent ordering based on color without relying on the ordering of the
12815        // highlight's `TypeId` which is non-executor.
12816        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12817        assert_eq!(
12818            highlighted_ranges,
12819            &[
12820                (
12821                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12822                    Hsla::red(),
12823                ),
12824                (
12825                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12826                    Hsla::red(),
12827                ),
12828                (
12829                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12830                    Hsla::green(),
12831                ),
12832                (
12833                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12834                    Hsla::green(),
12835                ),
12836            ]
12837        );
12838        assert_eq!(
12839            editor.background_highlights_in_range(
12840                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12841                &snapshot,
12842                cx.theme().colors(),
12843            ),
12844            &[(
12845                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12846                Hsla::red(),
12847            )]
12848        );
12849    });
12850}
12851
12852#[gpui::test]
12853async fn test_following(cx: &mut TestAppContext) {
12854    init_test(cx, |_| {});
12855
12856    let fs = FakeFs::new(cx.executor());
12857    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12858
12859    let buffer = project.update(cx, |project, cx| {
12860        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12861        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12862    });
12863    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12864    let follower = cx.update(|cx| {
12865        cx.open_window(
12866            WindowOptions {
12867                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12868                    gpui::Point::new(px(0.), px(0.)),
12869                    gpui::Point::new(px(10.), px(80.)),
12870                ))),
12871                ..Default::default()
12872            },
12873            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12874        )
12875        .unwrap()
12876    });
12877
12878    let is_still_following = Rc::new(RefCell::new(true));
12879    let follower_edit_event_count = Rc::new(RefCell::new(0));
12880    let pending_update = Rc::new(RefCell::new(None));
12881    let leader_entity = leader.root(cx).unwrap();
12882    let follower_entity = follower.root(cx).unwrap();
12883    _ = follower.update(cx, {
12884        let update = pending_update.clone();
12885        let is_still_following = is_still_following.clone();
12886        let follower_edit_event_count = follower_edit_event_count.clone();
12887        |_, window, cx| {
12888            cx.subscribe_in(
12889                &leader_entity,
12890                window,
12891                move |_, leader, event, window, cx| {
12892                    leader.read(cx).add_event_to_update_proto(
12893                        event,
12894                        &mut update.borrow_mut(),
12895                        window,
12896                        cx,
12897                    );
12898                },
12899            )
12900            .detach();
12901
12902            cx.subscribe_in(
12903                &follower_entity,
12904                window,
12905                move |_, _, event: &EditorEvent, _window, _cx| {
12906                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12907                        *is_still_following.borrow_mut() = false;
12908                    }
12909
12910                    if let EditorEvent::BufferEdited = event {
12911                        *follower_edit_event_count.borrow_mut() += 1;
12912                    }
12913                },
12914            )
12915            .detach();
12916        }
12917    });
12918
12919    // Update the selections only
12920    _ = leader.update(cx, |leader, window, cx| {
12921        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12922    });
12923    follower
12924        .update(cx, |follower, window, cx| {
12925            follower.apply_update_proto(
12926                &project,
12927                pending_update.borrow_mut().take().unwrap(),
12928                window,
12929                cx,
12930            )
12931        })
12932        .unwrap()
12933        .await
12934        .unwrap();
12935    _ = follower.update(cx, |follower, _, cx| {
12936        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12937    });
12938    assert!(*is_still_following.borrow());
12939    assert_eq!(*follower_edit_event_count.borrow(), 0);
12940
12941    // Update the scroll position only
12942    _ = leader.update(cx, |leader, window, cx| {
12943        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12944    });
12945    follower
12946        .update(cx, |follower, window, cx| {
12947            follower.apply_update_proto(
12948                &project,
12949                pending_update.borrow_mut().take().unwrap(),
12950                window,
12951                cx,
12952            )
12953        })
12954        .unwrap()
12955        .await
12956        .unwrap();
12957    assert_eq!(
12958        follower
12959            .update(cx, |follower, _, cx| follower.scroll_position(cx))
12960            .unwrap(),
12961        gpui::Point::new(1.5, 3.5)
12962    );
12963    assert!(*is_still_following.borrow());
12964    assert_eq!(*follower_edit_event_count.borrow(), 0);
12965
12966    // Update the selections and scroll position. The follower's scroll position is updated
12967    // via autoscroll, not via the leader's exact scroll position.
12968    _ = leader.update(cx, |leader, window, cx| {
12969        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12970        leader.request_autoscroll(Autoscroll::newest(), cx);
12971        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12972    });
12973    follower
12974        .update(cx, |follower, window, cx| {
12975            follower.apply_update_proto(
12976                &project,
12977                pending_update.borrow_mut().take().unwrap(),
12978                window,
12979                cx,
12980            )
12981        })
12982        .unwrap()
12983        .await
12984        .unwrap();
12985    _ = follower.update(cx, |follower, _, cx| {
12986        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12987        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12988    });
12989    assert!(*is_still_following.borrow());
12990
12991    // Creating a pending selection that precedes another selection
12992    _ = leader.update(cx, |leader, window, cx| {
12993        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12994        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12995    });
12996    follower
12997        .update(cx, |follower, window, cx| {
12998            follower.apply_update_proto(
12999                &project,
13000                pending_update.borrow_mut().take().unwrap(),
13001                window,
13002                cx,
13003            )
13004        })
13005        .unwrap()
13006        .await
13007        .unwrap();
13008    _ = follower.update(cx, |follower, _, cx| {
13009        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13010    });
13011    assert!(*is_still_following.borrow());
13012
13013    // Extend the pending selection so that it surrounds another selection
13014    _ = leader.update(cx, |leader, window, cx| {
13015        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13016    });
13017    follower
13018        .update(cx, |follower, window, cx| {
13019            follower.apply_update_proto(
13020                &project,
13021                pending_update.borrow_mut().take().unwrap(),
13022                window,
13023                cx,
13024            )
13025        })
13026        .unwrap()
13027        .await
13028        .unwrap();
13029    _ = follower.update(cx, |follower, _, cx| {
13030        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13031    });
13032
13033    // Scrolling locally breaks the follow
13034    _ = follower.update(cx, |follower, window, cx| {
13035        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13036        follower.set_scroll_anchor(
13037            ScrollAnchor {
13038                anchor: top_anchor,
13039                offset: gpui::Point::new(0.0, 0.5),
13040            },
13041            window,
13042            cx,
13043        );
13044    });
13045    assert!(!(*is_still_following.borrow()));
13046}
13047
13048#[gpui::test]
13049async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13050    init_test(cx, |_| {});
13051
13052    let fs = FakeFs::new(cx.executor());
13053    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13054    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13055    let pane = workspace
13056        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13057        .unwrap();
13058
13059    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13060
13061    let leader = pane.update_in(cx, |_, window, cx| {
13062        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13063        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13064    });
13065
13066    // Start following the editor when it has no excerpts.
13067    let mut state_message =
13068        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13069    let workspace_entity = workspace.root(cx).unwrap();
13070    let follower_1 = cx
13071        .update_window(*workspace.deref(), |_, window, cx| {
13072            Editor::from_state_proto(
13073                workspace_entity,
13074                ViewId {
13075                    creator: CollaboratorId::PeerId(PeerId::default()),
13076                    id: 0,
13077                },
13078                &mut state_message,
13079                window,
13080                cx,
13081            )
13082        })
13083        .unwrap()
13084        .unwrap()
13085        .await
13086        .unwrap();
13087
13088    let update_message = Rc::new(RefCell::new(None));
13089    follower_1.update_in(cx, {
13090        let update = update_message.clone();
13091        |_, window, cx| {
13092            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13093                leader.read(cx).add_event_to_update_proto(
13094                    event,
13095                    &mut update.borrow_mut(),
13096                    window,
13097                    cx,
13098                );
13099            })
13100            .detach();
13101        }
13102    });
13103
13104    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13105        (
13106            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13107            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13108        )
13109    });
13110
13111    // Insert some excerpts.
13112    leader.update(cx, |leader, cx| {
13113        leader.buffer.update(cx, |multibuffer, cx| {
13114            multibuffer.set_excerpts_for_path(
13115                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13116                buffer_1.clone(),
13117                vec![
13118                    Point::row_range(0..3),
13119                    Point::row_range(1..6),
13120                    Point::row_range(12..15),
13121                ],
13122                0,
13123                cx,
13124            );
13125            multibuffer.set_excerpts_for_path(
13126                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13127                buffer_2.clone(),
13128                vec![Point::row_range(0..6), Point::row_range(8..12)],
13129                0,
13130                cx,
13131            );
13132        });
13133    });
13134
13135    // Apply the update of adding the excerpts.
13136    follower_1
13137        .update_in(cx, |follower, window, cx| {
13138            follower.apply_update_proto(
13139                &project,
13140                update_message.borrow().clone().unwrap(),
13141                window,
13142                cx,
13143            )
13144        })
13145        .await
13146        .unwrap();
13147    assert_eq!(
13148        follower_1.update(cx, |editor, cx| editor.text(cx)),
13149        leader.update(cx, |editor, cx| editor.text(cx))
13150    );
13151    update_message.borrow_mut().take();
13152
13153    // Start following separately after it already has excerpts.
13154    let mut state_message =
13155        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13156    let workspace_entity = workspace.root(cx).unwrap();
13157    let follower_2 = cx
13158        .update_window(*workspace.deref(), |_, window, cx| {
13159            Editor::from_state_proto(
13160                workspace_entity,
13161                ViewId {
13162                    creator: CollaboratorId::PeerId(PeerId::default()),
13163                    id: 0,
13164                },
13165                &mut state_message,
13166                window,
13167                cx,
13168            )
13169        })
13170        .unwrap()
13171        .unwrap()
13172        .await
13173        .unwrap();
13174    assert_eq!(
13175        follower_2.update(cx, |editor, cx| editor.text(cx)),
13176        leader.update(cx, |editor, cx| editor.text(cx))
13177    );
13178
13179    // Remove some excerpts.
13180    leader.update(cx, |leader, cx| {
13181        leader.buffer.update(cx, |multibuffer, cx| {
13182            let excerpt_ids = multibuffer.excerpt_ids();
13183            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13184            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13185        });
13186    });
13187
13188    // Apply the update of removing the excerpts.
13189    follower_1
13190        .update_in(cx, |follower, window, cx| {
13191            follower.apply_update_proto(
13192                &project,
13193                update_message.borrow().clone().unwrap(),
13194                window,
13195                cx,
13196            )
13197        })
13198        .await
13199        .unwrap();
13200    follower_2
13201        .update_in(cx, |follower, window, cx| {
13202            follower.apply_update_proto(
13203                &project,
13204                update_message.borrow().clone().unwrap(),
13205                window,
13206                cx,
13207            )
13208        })
13209        .await
13210        .unwrap();
13211    update_message.borrow_mut().take();
13212    assert_eq!(
13213        follower_1.update(cx, |editor, cx| editor.text(cx)),
13214        leader.update(cx, |editor, cx| editor.text(cx))
13215    );
13216}
13217
13218#[gpui::test]
13219async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13220    init_test(cx, |_| {});
13221
13222    let mut cx = EditorTestContext::new(cx).await;
13223    let lsp_store =
13224        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13225
13226    cx.set_state(indoc! {"
13227        ˇfn func(abc def: i32) -> u32 {
13228        }
13229    "});
13230
13231    cx.update(|_, cx| {
13232        lsp_store.update(cx, |lsp_store, cx| {
13233            lsp_store
13234                .update_diagnostics(
13235                    LanguageServerId(0),
13236                    lsp::PublishDiagnosticsParams {
13237                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13238                        version: None,
13239                        diagnostics: vec![
13240                            lsp::Diagnostic {
13241                                range: lsp::Range::new(
13242                                    lsp::Position::new(0, 11),
13243                                    lsp::Position::new(0, 12),
13244                                ),
13245                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13246                                ..Default::default()
13247                            },
13248                            lsp::Diagnostic {
13249                                range: lsp::Range::new(
13250                                    lsp::Position::new(0, 12),
13251                                    lsp::Position::new(0, 15),
13252                                ),
13253                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13254                                ..Default::default()
13255                            },
13256                            lsp::Diagnostic {
13257                                range: lsp::Range::new(
13258                                    lsp::Position::new(0, 25),
13259                                    lsp::Position::new(0, 28),
13260                                ),
13261                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13262                                ..Default::default()
13263                            },
13264                        ],
13265                    },
13266                    &[],
13267                    cx,
13268                )
13269                .unwrap()
13270        });
13271    });
13272
13273    executor.run_until_parked();
13274
13275    cx.update_editor(|editor, window, cx| {
13276        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13277    });
13278
13279    cx.assert_editor_state(indoc! {"
13280        fn func(abc def: i32) -> ˇu32 {
13281        }
13282    "});
13283
13284    cx.update_editor(|editor, window, cx| {
13285        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13286    });
13287
13288    cx.assert_editor_state(indoc! {"
13289        fn func(abc ˇdef: i32) -> u32 {
13290        }
13291    "});
13292
13293    cx.update_editor(|editor, window, cx| {
13294        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13295    });
13296
13297    cx.assert_editor_state(indoc! {"
13298        fn func(abcˇ def: i32) -> u32 {
13299        }
13300    "});
13301
13302    cx.update_editor(|editor, window, cx| {
13303        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13304    });
13305
13306    cx.assert_editor_state(indoc! {"
13307        fn func(abc def: i32) -> ˇu32 {
13308        }
13309    "});
13310}
13311
13312#[gpui::test]
13313async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13314    init_test(cx, |_| {});
13315
13316    let mut cx = EditorTestContext::new(cx).await;
13317
13318    let diff_base = r#"
13319        use some::mod;
13320
13321        const A: u32 = 42;
13322
13323        fn main() {
13324            println!("hello");
13325
13326            println!("world");
13327        }
13328        "#
13329    .unindent();
13330
13331    // Edits are modified, removed, modified, added
13332    cx.set_state(
13333        &r#"
13334        use some::modified;
13335
13336        ˇ
13337        fn main() {
13338            println!("hello there");
13339
13340            println!("around the");
13341            println!("world");
13342        }
13343        "#
13344        .unindent(),
13345    );
13346
13347    cx.set_head_text(&diff_base);
13348    executor.run_until_parked();
13349
13350    cx.update_editor(|editor, window, cx| {
13351        //Wrap around the bottom of the buffer
13352        for _ in 0..3 {
13353            editor.go_to_next_hunk(&GoToHunk, window, cx);
13354        }
13355    });
13356
13357    cx.assert_editor_state(
13358        &r#"
13359        ˇuse some::modified;
13360
13361
13362        fn main() {
13363            println!("hello there");
13364
13365            println!("around the");
13366            println!("world");
13367        }
13368        "#
13369        .unindent(),
13370    );
13371
13372    cx.update_editor(|editor, window, cx| {
13373        //Wrap around the top of the buffer
13374        for _ in 0..2 {
13375            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13376        }
13377    });
13378
13379    cx.assert_editor_state(
13380        &r#"
13381        use some::modified;
13382
13383
13384        fn main() {
13385        ˇ    println!("hello there");
13386
13387            println!("around the");
13388            println!("world");
13389        }
13390        "#
13391        .unindent(),
13392    );
13393
13394    cx.update_editor(|editor, window, cx| {
13395        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13396    });
13397
13398    cx.assert_editor_state(
13399        &r#"
13400        use some::modified;
13401
13402        ˇ
13403        fn main() {
13404            println!("hello there");
13405
13406            println!("around the");
13407            println!("world");
13408        }
13409        "#
13410        .unindent(),
13411    );
13412
13413    cx.update_editor(|editor, window, cx| {
13414        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13415    });
13416
13417    cx.assert_editor_state(
13418        &r#"
13419        ˇuse some::modified;
13420
13421
13422        fn main() {
13423            println!("hello there");
13424
13425            println!("around the");
13426            println!("world");
13427        }
13428        "#
13429        .unindent(),
13430    );
13431
13432    cx.update_editor(|editor, window, cx| {
13433        for _ in 0..2 {
13434            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13435        }
13436    });
13437
13438    cx.assert_editor_state(
13439        &r#"
13440        use some::modified;
13441
13442
13443        fn main() {
13444        ˇ    println!("hello there");
13445
13446            println!("around the");
13447            println!("world");
13448        }
13449        "#
13450        .unindent(),
13451    );
13452
13453    cx.update_editor(|editor, window, cx| {
13454        editor.fold(&Fold, window, cx);
13455    });
13456
13457    cx.update_editor(|editor, window, cx| {
13458        editor.go_to_next_hunk(&GoToHunk, window, cx);
13459    });
13460
13461    cx.assert_editor_state(
13462        &r#"
13463        ˇuse some::modified;
13464
13465
13466        fn main() {
13467            println!("hello there");
13468
13469            println!("around the");
13470            println!("world");
13471        }
13472        "#
13473        .unindent(),
13474    );
13475}
13476
13477#[test]
13478fn test_split_words() {
13479    fn split(text: &str) -> Vec<&str> {
13480        split_words(text).collect()
13481    }
13482
13483    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13484    assert_eq!(split("hello_world"), &["hello_", "world"]);
13485    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13486    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13487    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13488    assert_eq!(split("helloworld"), &["helloworld"]);
13489
13490    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13491}
13492
13493#[gpui::test]
13494async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13495    init_test(cx, |_| {});
13496
13497    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13498    let mut assert = |before, after| {
13499        let _state_context = cx.set_state(before);
13500        cx.run_until_parked();
13501        cx.update_editor(|editor, window, cx| {
13502            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13503        });
13504        cx.run_until_parked();
13505        cx.assert_editor_state(after);
13506    };
13507
13508    // Outside bracket jumps to outside of matching bracket
13509    assert("console.logˇ(var);", "console.log(var)ˇ;");
13510    assert("console.log(var)ˇ;", "console.logˇ(var);");
13511
13512    // Inside bracket jumps to inside of matching bracket
13513    assert("console.log(ˇvar);", "console.log(varˇ);");
13514    assert("console.log(varˇ);", "console.log(ˇvar);");
13515
13516    // When outside a bracket and inside, favor jumping to the inside bracket
13517    assert(
13518        "console.log('foo', [1, 2, 3]ˇ);",
13519        "console.log(ˇ'foo', [1, 2, 3]);",
13520    );
13521    assert(
13522        "console.log(ˇ'foo', [1, 2, 3]);",
13523        "console.log('foo', [1, 2, 3]ˇ);",
13524    );
13525
13526    // Bias forward if two options are equally likely
13527    assert(
13528        "let result = curried_fun()ˇ();",
13529        "let result = curried_fun()()ˇ;",
13530    );
13531
13532    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13533    assert(
13534        indoc! {"
13535            function test() {
13536                console.log('test')ˇ
13537            }"},
13538        indoc! {"
13539            function test() {
13540                console.logˇ('test')
13541            }"},
13542    );
13543}
13544
13545#[gpui::test]
13546async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13547    init_test(cx, |_| {});
13548
13549    let fs = FakeFs::new(cx.executor());
13550    fs.insert_tree(
13551        path!("/a"),
13552        json!({
13553            "main.rs": "fn main() { let a = 5; }",
13554            "other.rs": "// Test file",
13555        }),
13556    )
13557    .await;
13558    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13559
13560    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13561    language_registry.add(Arc::new(Language::new(
13562        LanguageConfig {
13563            name: "Rust".into(),
13564            matcher: LanguageMatcher {
13565                path_suffixes: vec!["rs".to_string()],
13566                ..Default::default()
13567            },
13568            brackets: BracketPairConfig {
13569                pairs: vec![BracketPair {
13570                    start: "{".to_string(),
13571                    end: "}".to_string(),
13572                    close: true,
13573                    surround: true,
13574                    newline: true,
13575                }],
13576                disabled_scopes_by_bracket_ix: Vec::new(),
13577            },
13578            ..Default::default()
13579        },
13580        Some(tree_sitter_rust::LANGUAGE.into()),
13581    )));
13582    let mut fake_servers = language_registry.register_fake_lsp(
13583        "Rust",
13584        FakeLspAdapter {
13585            capabilities: lsp::ServerCapabilities {
13586                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13587                    first_trigger_character: "{".to_string(),
13588                    more_trigger_character: None,
13589                }),
13590                ..Default::default()
13591            },
13592            ..Default::default()
13593        },
13594    );
13595
13596    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13597
13598    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13599
13600    let worktree_id = workspace
13601        .update(cx, |workspace, _, cx| {
13602            workspace.project().update(cx, |project, cx| {
13603                project.worktrees(cx).next().unwrap().read(cx).id()
13604            })
13605        })
13606        .unwrap();
13607
13608    let buffer = project
13609        .update(cx, |project, cx| {
13610            project.open_local_buffer(path!("/a/main.rs"), cx)
13611        })
13612        .await
13613        .unwrap();
13614    let editor_handle = workspace
13615        .update(cx, |workspace, window, cx| {
13616            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13617        })
13618        .unwrap()
13619        .await
13620        .unwrap()
13621        .downcast::<Editor>()
13622        .unwrap();
13623
13624    cx.executor().start_waiting();
13625    let fake_server = fake_servers.next().await.unwrap();
13626
13627    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13628        |params, _| async move {
13629            assert_eq!(
13630                params.text_document_position.text_document.uri,
13631                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13632            );
13633            assert_eq!(
13634                params.text_document_position.position,
13635                lsp::Position::new(0, 21),
13636            );
13637
13638            Ok(Some(vec![lsp::TextEdit {
13639                new_text: "]".to_string(),
13640                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13641            }]))
13642        },
13643    );
13644
13645    editor_handle.update_in(cx, |editor, window, cx| {
13646        window.focus(&editor.focus_handle(cx));
13647        editor.change_selections(None, window, cx, |s| {
13648            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13649        });
13650        editor.handle_input("{", window, cx);
13651    });
13652
13653    cx.executor().run_until_parked();
13654
13655    buffer.update(cx, |buffer, _| {
13656        assert_eq!(
13657            buffer.text(),
13658            "fn main() { let a = {5}; }",
13659            "No extra braces from on type formatting should appear in the buffer"
13660        )
13661    });
13662}
13663
13664#[gpui::test]
13665async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13666    init_test(cx, |_| {});
13667
13668    let fs = FakeFs::new(cx.executor());
13669    fs.insert_tree(
13670        path!("/a"),
13671        json!({
13672            "main.rs": "fn main() { let a = 5; }",
13673            "other.rs": "// Test file",
13674        }),
13675    )
13676    .await;
13677
13678    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13679
13680    let server_restarts = Arc::new(AtomicUsize::new(0));
13681    let closure_restarts = Arc::clone(&server_restarts);
13682    let language_server_name = "test language server";
13683    let language_name: LanguageName = "Rust".into();
13684
13685    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13686    language_registry.add(Arc::new(Language::new(
13687        LanguageConfig {
13688            name: language_name.clone(),
13689            matcher: LanguageMatcher {
13690                path_suffixes: vec!["rs".to_string()],
13691                ..Default::default()
13692            },
13693            ..Default::default()
13694        },
13695        Some(tree_sitter_rust::LANGUAGE.into()),
13696    )));
13697    let mut fake_servers = language_registry.register_fake_lsp(
13698        "Rust",
13699        FakeLspAdapter {
13700            name: language_server_name,
13701            initialization_options: Some(json!({
13702                "testOptionValue": true
13703            })),
13704            initializer: Some(Box::new(move |fake_server| {
13705                let task_restarts = Arc::clone(&closure_restarts);
13706                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13707                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13708                    futures::future::ready(Ok(()))
13709                });
13710            })),
13711            ..Default::default()
13712        },
13713    );
13714
13715    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13716    let _buffer = project
13717        .update(cx, |project, cx| {
13718            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13719        })
13720        .await
13721        .unwrap();
13722    let _fake_server = fake_servers.next().await.unwrap();
13723    update_test_language_settings(cx, |language_settings| {
13724        language_settings.languages.insert(
13725            language_name.clone(),
13726            LanguageSettingsContent {
13727                tab_size: NonZeroU32::new(8),
13728                ..Default::default()
13729            },
13730        );
13731    });
13732    cx.executor().run_until_parked();
13733    assert_eq!(
13734        server_restarts.load(atomic::Ordering::Acquire),
13735        0,
13736        "Should not restart LSP server on an unrelated change"
13737    );
13738
13739    update_test_project_settings(cx, |project_settings| {
13740        project_settings.lsp.insert(
13741            "Some other server name".into(),
13742            LspSettings {
13743                binary: None,
13744                settings: None,
13745                initialization_options: Some(json!({
13746                    "some other init value": false
13747                })),
13748                enable_lsp_tasks: false,
13749            },
13750        );
13751    });
13752    cx.executor().run_until_parked();
13753    assert_eq!(
13754        server_restarts.load(atomic::Ordering::Acquire),
13755        0,
13756        "Should not restart LSP server on an unrelated LSP settings change"
13757    );
13758
13759    update_test_project_settings(cx, |project_settings| {
13760        project_settings.lsp.insert(
13761            language_server_name.into(),
13762            LspSettings {
13763                binary: None,
13764                settings: None,
13765                initialization_options: Some(json!({
13766                    "anotherInitValue": false
13767                })),
13768                enable_lsp_tasks: false,
13769            },
13770        );
13771    });
13772    cx.executor().run_until_parked();
13773    assert_eq!(
13774        server_restarts.load(atomic::Ordering::Acquire),
13775        1,
13776        "Should restart LSP server on a related LSP settings change"
13777    );
13778
13779    update_test_project_settings(cx, |project_settings| {
13780        project_settings.lsp.insert(
13781            language_server_name.into(),
13782            LspSettings {
13783                binary: None,
13784                settings: None,
13785                initialization_options: Some(json!({
13786                    "anotherInitValue": false
13787                })),
13788                enable_lsp_tasks: false,
13789            },
13790        );
13791    });
13792    cx.executor().run_until_parked();
13793    assert_eq!(
13794        server_restarts.load(atomic::Ordering::Acquire),
13795        1,
13796        "Should not restart LSP server on a related LSP settings change that is the same"
13797    );
13798
13799    update_test_project_settings(cx, |project_settings| {
13800        project_settings.lsp.insert(
13801            language_server_name.into(),
13802            LspSettings {
13803                binary: None,
13804                settings: None,
13805                initialization_options: None,
13806                enable_lsp_tasks: false,
13807            },
13808        );
13809    });
13810    cx.executor().run_until_parked();
13811    assert_eq!(
13812        server_restarts.load(atomic::Ordering::Acquire),
13813        2,
13814        "Should restart LSP server on another related LSP settings change"
13815    );
13816}
13817
13818#[gpui::test]
13819async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13820    init_test(cx, |_| {});
13821
13822    let mut cx = EditorLspTestContext::new_rust(
13823        lsp::ServerCapabilities {
13824            completion_provider: Some(lsp::CompletionOptions {
13825                trigger_characters: Some(vec![".".to_string()]),
13826                resolve_provider: Some(true),
13827                ..Default::default()
13828            }),
13829            ..Default::default()
13830        },
13831        cx,
13832    )
13833    .await;
13834
13835    cx.set_state("fn main() { let a = 2ˇ; }");
13836    cx.simulate_keystroke(".");
13837    let completion_item = lsp::CompletionItem {
13838        label: "some".into(),
13839        kind: Some(lsp::CompletionItemKind::SNIPPET),
13840        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13841        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13842            kind: lsp::MarkupKind::Markdown,
13843            value: "```rust\nSome(2)\n```".to_string(),
13844        })),
13845        deprecated: Some(false),
13846        sort_text: Some("fffffff2".to_string()),
13847        filter_text: Some("some".to_string()),
13848        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13849        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13850            range: lsp::Range {
13851                start: lsp::Position {
13852                    line: 0,
13853                    character: 22,
13854                },
13855                end: lsp::Position {
13856                    line: 0,
13857                    character: 22,
13858                },
13859            },
13860            new_text: "Some(2)".to_string(),
13861        })),
13862        additional_text_edits: Some(vec![lsp::TextEdit {
13863            range: lsp::Range {
13864                start: lsp::Position {
13865                    line: 0,
13866                    character: 20,
13867                },
13868                end: lsp::Position {
13869                    line: 0,
13870                    character: 22,
13871                },
13872            },
13873            new_text: "".to_string(),
13874        }]),
13875        ..Default::default()
13876    };
13877
13878    let closure_completion_item = completion_item.clone();
13879    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13880        let task_completion_item = closure_completion_item.clone();
13881        async move {
13882            Ok(Some(lsp::CompletionResponse::Array(vec![
13883                task_completion_item,
13884            ])))
13885        }
13886    });
13887
13888    request.next().await;
13889
13890    cx.condition(|editor, _| editor.context_menu_visible())
13891        .await;
13892    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13893        editor
13894            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13895            .unwrap()
13896    });
13897    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13898
13899    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13900        let task_completion_item = completion_item.clone();
13901        async move { Ok(task_completion_item) }
13902    })
13903    .next()
13904    .await
13905    .unwrap();
13906    apply_additional_edits.await.unwrap();
13907    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13908}
13909
13910#[gpui::test]
13911async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13912    init_test(cx, |_| {});
13913
13914    let mut cx = EditorLspTestContext::new_rust(
13915        lsp::ServerCapabilities {
13916            completion_provider: Some(lsp::CompletionOptions {
13917                trigger_characters: Some(vec![".".to_string()]),
13918                resolve_provider: Some(true),
13919                ..Default::default()
13920            }),
13921            ..Default::default()
13922        },
13923        cx,
13924    )
13925    .await;
13926
13927    cx.set_state("fn main() { let a = 2ˇ; }");
13928    cx.simulate_keystroke(".");
13929
13930    let item1 = lsp::CompletionItem {
13931        label: "method id()".to_string(),
13932        filter_text: Some("id".to_string()),
13933        detail: None,
13934        documentation: None,
13935        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13936            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13937            new_text: ".id".to_string(),
13938        })),
13939        ..lsp::CompletionItem::default()
13940    };
13941
13942    let item2 = lsp::CompletionItem {
13943        label: "other".to_string(),
13944        filter_text: Some("other".to_string()),
13945        detail: None,
13946        documentation: None,
13947        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13948            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13949            new_text: ".other".to_string(),
13950        })),
13951        ..lsp::CompletionItem::default()
13952    };
13953
13954    let item1 = item1.clone();
13955    cx.set_request_handler::<lsp::request::Completion, _, _>({
13956        let item1 = item1.clone();
13957        move |_, _, _| {
13958            let item1 = item1.clone();
13959            let item2 = item2.clone();
13960            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13961        }
13962    })
13963    .next()
13964    .await;
13965
13966    cx.condition(|editor, _| editor.context_menu_visible())
13967        .await;
13968    cx.update_editor(|editor, _, _| {
13969        let context_menu = editor.context_menu.borrow_mut();
13970        let context_menu = context_menu
13971            .as_ref()
13972            .expect("Should have the context menu deployed");
13973        match context_menu {
13974            CodeContextMenu::Completions(completions_menu) => {
13975                let completions = completions_menu.completions.borrow_mut();
13976                assert_eq!(
13977                    completions
13978                        .iter()
13979                        .map(|completion| &completion.label.text)
13980                        .collect::<Vec<_>>(),
13981                    vec!["method id()", "other"]
13982                )
13983            }
13984            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13985        }
13986    });
13987
13988    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13989        let item1 = item1.clone();
13990        move |_, item_to_resolve, _| {
13991            let item1 = item1.clone();
13992            async move {
13993                if item1 == item_to_resolve {
13994                    Ok(lsp::CompletionItem {
13995                        label: "method id()".to_string(),
13996                        filter_text: Some("id".to_string()),
13997                        detail: Some("Now resolved!".to_string()),
13998                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13999                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14000                            range: lsp::Range::new(
14001                                lsp::Position::new(0, 22),
14002                                lsp::Position::new(0, 22),
14003                            ),
14004                            new_text: ".id".to_string(),
14005                        })),
14006                        ..lsp::CompletionItem::default()
14007                    })
14008                } else {
14009                    Ok(item_to_resolve)
14010                }
14011            }
14012        }
14013    })
14014    .next()
14015    .await
14016    .unwrap();
14017    cx.run_until_parked();
14018
14019    cx.update_editor(|editor, window, cx| {
14020        editor.context_menu_next(&Default::default(), window, cx);
14021    });
14022
14023    cx.update_editor(|editor, _, _| {
14024        let context_menu = editor.context_menu.borrow_mut();
14025        let context_menu = context_menu
14026            .as_ref()
14027            .expect("Should have the context menu deployed");
14028        match context_menu {
14029            CodeContextMenu::Completions(completions_menu) => {
14030                let completions = completions_menu.completions.borrow_mut();
14031                assert_eq!(
14032                    completions
14033                        .iter()
14034                        .map(|completion| &completion.label.text)
14035                        .collect::<Vec<_>>(),
14036                    vec!["method id() Now resolved!", "other"],
14037                    "Should update first completion label, but not second as the filter text did not match."
14038                );
14039            }
14040            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14041        }
14042    });
14043}
14044
14045#[gpui::test]
14046async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14047    init_test(cx, |_| {});
14048    let mut cx = EditorLspTestContext::new_rust(
14049        lsp::ServerCapabilities {
14050            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14051            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14052            completion_provider: Some(lsp::CompletionOptions {
14053                resolve_provider: Some(true),
14054                ..Default::default()
14055            }),
14056            ..Default::default()
14057        },
14058        cx,
14059    )
14060    .await;
14061    cx.set_state(indoc! {"
14062        struct TestStruct {
14063            field: i32
14064        }
14065
14066        fn mainˇ() {
14067            let unused_var = 42;
14068            let test_struct = TestStruct { field: 42 };
14069        }
14070    "});
14071    let symbol_range = cx.lsp_range(indoc! {"
14072        struct TestStruct {
14073            field: i32
14074        }
14075
14076        «fn main»() {
14077            let unused_var = 42;
14078            let test_struct = TestStruct { field: 42 };
14079        }
14080    "});
14081    let mut hover_requests =
14082        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14083            Ok(Some(lsp::Hover {
14084                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14085                    kind: lsp::MarkupKind::Markdown,
14086                    value: "Function documentation".to_string(),
14087                }),
14088                range: Some(symbol_range),
14089            }))
14090        });
14091
14092    // Case 1: Test that code action menu hide hover popover
14093    cx.dispatch_action(Hover);
14094    hover_requests.next().await;
14095    cx.condition(|editor, _| editor.hover_state.visible()).await;
14096    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14097        move |_, _, _| async move {
14098            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14099                lsp::CodeAction {
14100                    title: "Remove unused variable".to_string(),
14101                    kind: Some(CodeActionKind::QUICKFIX),
14102                    edit: Some(lsp::WorkspaceEdit {
14103                        changes: Some(
14104                            [(
14105                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14106                                vec![lsp::TextEdit {
14107                                    range: lsp::Range::new(
14108                                        lsp::Position::new(5, 4),
14109                                        lsp::Position::new(5, 27),
14110                                    ),
14111                                    new_text: "".to_string(),
14112                                }],
14113                            )]
14114                            .into_iter()
14115                            .collect(),
14116                        ),
14117                        ..Default::default()
14118                    }),
14119                    ..Default::default()
14120                },
14121            )]))
14122        },
14123    );
14124    cx.update_editor(|editor, window, cx| {
14125        editor.toggle_code_actions(
14126            &ToggleCodeActions {
14127                deployed_from_indicator: None,
14128                quick_launch: false,
14129            },
14130            window,
14131            cx,
14132        );
14133    });
14134    code_action_requests.next().await;
14135    cx.run_until_parked();
14136    cx.condition(|editor, _| editor.context_menu_visible())
14137        .await;
14138    cx.update_editor(|editor, _, _| {
14139        assert!(
14140            !editor.hover_state.visible(),
14141            "Hover popover should be hidden when code action menu is shown"
14142        );
14143        // Hide code actions
14144        editor.context_menu.take();
14145    });
14146
14147    // Case 2: Test that code completions hide hover popover
14148    cx.dispatch_action(Hover);
14149    hover_requests.next().await;
14150    cx.condition(|editor, _| editor.hover_state.visible()).await;
14151    let counter = Arc::new(AtomicUsize::new(0));
14152    let mut completion_requests =
14153        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14154            let counter = counter.clone();
14155            async move {
14156                counter.fetch_add(1, atomic::Ordering::Release);
14157                Ok(Some(lsp::CompletionResponse::Array(vec![
14158                    lsp::CompletionItem {
14159                        label: "main".into(),
14160                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14161                        detail: Some("() -> ()".to_string()),
14162                        ..Default::default()
14163                    },
14164                    lsp::CompletionItem {
14165                        label: "TestStruct".into(),
14166                        kind: Some(lsp::CompletionItemKind::STRUCT),
14167                        detail: Some("struct TestStruct".to_string()),
14168                        ..Default::default()
14169                    },
14170                ])))
14171            }
14172        });
14173    cx.update_editor(|editor, window, cx| {
14174        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14175    });
14176    completion_requests.next().await;
14177    cx.condition(|editor, _| editor.context_menu_visible())
14178        .await;
14179    cx.update_editor(|editor, _, _| {
14180        assert!(
14181            !editor.hover_state.visible(),
14182            "Hover popover should be hidden when completion menu is shown"
14183        );
14184    });
14185}
14186
14187#[gpui::test]
14188async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14189    init_test(cx, |_| {});
14190
14191    let mut cx = EditorLspTestContext::new_rust(
14192        lsp::ServerCapabilities {
14193            completion_provider: Some(lsp::CompletionOptions {
14194                trigger_characters: Some(vec![".".to_string()]),
14195                resolve_provider: Some(true),
14196                ..Default::default()
14197            }),
14198            ..Default::default()
14199        },
14200        cx,
14201    )
14202    .await;
14203
14204    cx.set_state("fn main() { let a = 2ˇ; }");
14205    cx.simulate_keystroke(".");
14206
14207    let unresolved_item_1 = lsp::CompletionItem {
14208        label: "id".to_string(),
14209        filter_text: Some("id".to_string()),
14210        detail: None,
14211        documentation: None,
14212        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14213            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14214            new_text: ".id".to_string(),
14215        })),
14216        ..lsp::CompletionItem::default()
14217    };
14218    let resolved_item_1 = lsp::CompletionItem {
14219        additional_text_edits: Some(vec![lsp::TextEdit {
14220            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14221            new_text: "!!".to_string(),
14222        }]),
14223        ..unresolved_item_1.clone()
14224    };
14225    let unresolved_item_2 = lsp::CompletionItem {
14226        label: "other".to_string(),
14227        filter_text: Some("other".to_string()),
14228        detail: None,
14229        documentation: None,
14230        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14231            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14232            new_text: ".other".to_string(),
14233        })),
14234        ..lsp::CompletionItem::default()
14235    };
14236    let resolved_item_2 = lsp::CompletionItem {
14237        additional_text_edits: Some(vec![lsp::TextEdit {
14238            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14239            new_text: "??".to_string(),
14240        }]),
14241        ..unresolved_item_2.clone()
14242    };
14243
14244    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14245    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14246    cx.lsp
14247        .server
14248        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14249            let unresolved_item_1 = unresolved_item_1.clone();
14250            let resolved_item_1 = resolved_item_1.clone();
14251            let unresolved_item_2 = unresolved_item_2.clone();
14252            let resolved_item_2 = resolved_item_2.clone();
14253            let resolve_requests_1 = resolve_requests_1.clone();
14254            let resolve_requests_2 = resolve_requests_2.clone();
14255            move |unresolved_request, _| {
14256                let unresolved_item_1 = unresolved_item_1.clone();
14257                let resolved_item_1 = resolved_item_1.clone();
14258                let unresolved_item_2 = unresolved_item_2.clone();
14259                let resolved_item_2 = resolved_item_2.clone();
14260                let resolve_requests_1 = resolve_requests_1.clone();
14261                let resolve_requests_2 = resolve_requests_2.clone();
14262                async move {
14263                    if unresolved_request == unresolved_item_1 {
14264                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14265                        Ok(resolved_item_1.clone())
14266                    } else if unresolved_request == unresolved_item_2 {
14267                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14268                        Ok(resolved_item_2.clone())
14269                    } else {
14270                        panic!("Unexpected completion item {unresolved_request:?}")
14271                    }
14272                }
14273            }
14274        })
14275        .detach();
14276
14277    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14278        let unresolved_item_1 = unresolved_item_1.clone();
14279        let unresolved_item_2 = unresolved_item_2.clone();
14280        async move {
14281            Ok(Some(lsp::CompletionResponse::Array(vec![
14282                unresolved_item_1,
14283                unresolved_item_2,
14284            ])))
14285        }
14286    })
14287    .next()
14288    .await;
14289
14290    cx.condition(|editor, _| editor.context_menu_visible())
14291        .await;
14292    cx.update_editor(|editor, _, _| {
14293        let context_menu = editor.context_menu.borrow_mut();
14294        let context_menu = context_menu
14295            .as_ref()
14296            .expect("Should have the context menu deployed");
14297        match context_menu {
14298            CodeContextMenu::Completions(completions_menu) => {
14299                let completions = completions_menu.completions.borrow_mut();
14300                assert_eq!(
14301                    completions
14302                        .iter()
14303                        .map(|completion| &completion.label.text)
14304                        .collect::<Vec<_>>(),
14305                    vec!["id", "other"]
14306                )
14307            }
14308            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14309        }
14310    });
14311    cx.run_until_parked();
14312
14313    cx.update_editor(|editor, window, cx| {
14314        editor.context_menu_next(&ContextMenuNext, window, cx);
14315    });
14316    cx.run_until_parked();
14317    cx.update_editor(|editor, window, cx| {
14318        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14319    });
14320    cx.run_until_parked();
14321    cx.update_editor(|editor, window, cx| {
14322        editor.context_menu_next(&ContextMenuNext, window, cx);
14323    });
14324    cx.run_until_parked();
14325    cx.update_editor(|editor, window, cx| {
14326        editor
14327            .compose_completion(&ComposeCompletion::default(), window, cx)
14328            .expect("No task returned")
14329    })
14330    .await
14331    .expect("Completion failed");
14332    cx.run_until_parked();
14333
14334    cx.update_editor(|editor, _, cx| {
14335        assert_eq!(
14336            resolve_requests_1.load(atomic::Ordering::Acquire),
14337            1,
14338            "Should always resolve once despite multiple selections"
14339        );
14340        assert_eq!(
14341            resolve_requests_2.load(atomic::Ordering::Acquire),
14342            1,
14343            "Should always resolve once after multiple selections and applying the completion"
14344        );
14345        assert_eq!(
14346            editor.text(cx),
14347            "fn main() { let a = ??.other; }",
14348            "Should use resolved data when applying the completion"
14349        );
14350    });
14351}
14352
14353#[gpui::test]
14354async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14355    init_test(cx, |_| {});
14356
14357    let item_0 = lsp::CompletionItem {
14358        label: "abs".into(),
14359        insert_text: Some("abs".into()),
14360        data: Some(json!({ "very": "special"})),
14361        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14362        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14363            lsp::InsertReplaceEdit {
14364                new_text: "abs".to_string(),
14365                insert: lsp::Range::default(),
14366                replace: lsp::Range::default(),
14367            },
14368        )),
14369        ..lsp::CompletionItem::default()
14370    };
14371    let items = iter::once(item_0.clone())
14372        .chain((11..51).map(|i| lsp::CompletionItem {
14373            label: format!("item_{}", i),
14374            insert_text: Some(format!("item_{}", i)),
14375            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14376            ..lsp::CompletionItem::default()
14377        }))
14378        .collect::<Vec<_>>();
14379
14380    let default_commit_characters = vec!["?".to_string()];
14381    let default_data = json!({ "default": "data"});
14382    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14383    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14384    let default_edit_range = lsp::Range {
14385        start: lsp::Position {
14386            line: 0,
14387            character: 5,
14388        },
14389        end: lsp::Position {
14390            line: 0,
14391            character: 5,
14392        },
14393    };
14394
14395    let mut cx = EditorLspTestContext::new_rust(
14396        lsp::ServerCapabilities {
14397            completion_provider: Some(lsp::CompletionOptions {
14398                trigger_characters: Some(vec![".".to_string()]),
14399                resolve_provider: Some(true),
14400                ..Default::default()
14401            }),
14402            ..Default::default()
14403        },
14404        cx,
14405    )
14406    .await;
14407
14408    cx.set_state("fn main() { let a = 2ˇ; }");
14409    cx.simulate_keystroke(".");
14410
14411    let completion_data = default_data.clone();
14412    let completion_characters = default_commit_characters.clone();
14413    let completion_items = items.clone();
14414    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14415        let default_data = completion_data.clone();
14416        let default_commit_characters = completion_characters.clone();
14417        let items = completion_items.clone();
14418        async move {
14419            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14420                items,
14421                item_defaults: Some(lsp::CompletionListItemDefaults {
14422                    data: Some(default_data.clone()),
14423                    commit_characters: Some(default_commit_characters.clone()),
14424                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14425                        default_edit_range,
14426                    )),
14427                    insert_text_format: Some(default_insert_text_format),
14428                    insert_text_mode: Some(default_insert_text_mode),
14429                }),
14430                ..lsp::CompletionList::default()
14431            })))
14432        }
14433    })
14434    .next()
14435    .await;
14436
14437    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14438    cx.lsp
14439        .server
14440        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14441            let closure_resolved_items = resolved_items.clone();
14442            move |item_to_resolve, _| {
14443                let closure_resolved_items = closure_resolved_items.clone();
14444                async move {
14445                    closure_resolved_items.lock().push(item_to_resolve.clone());
14446                    Ok(item_to_resolve)
14447                }
14448            }
14449        })
14450        .detach();
14451
14452    cx.condition(|editor, _| editor.context_menu_visible())
14453        .await;
14454    cx.run_until_parked();
14455    cx.update_editor(|editor, _, _| {
14456        let menu = editor.context_menu.borrow_mut();
14457        match menu.as_ref().expect("should have the completions menu") {
14458            CodeContextMenu::Completions(completions_menu) => {
14459                assert_eq!(
14460                    completions_menu
14461                        .entries
14462                        .borrow()
14463                        .iter()
14464                        .map(|mat| mat.string.clone())
14465                        .collect::<Vec<String>>(),
14466                    items
14467                        .iter()
14468                        .map(|completion| completion.label.clone())
14469                        .collect::<Vec<String>>()
14470                );
14471            }
14472            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14473        }
14474    });
14475    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14476    // with 4 from the end.
14477    assert_eq!(
14478        *resolved_items.lock(),
14479        [&items[0..16], &items[items.len() - 4..items.len()]]
14480            .concat()
14481            .iter()
14482            .cloned()
14483            .map(|mut item| {
14484                if item.data.is_none() {
14485                    item.data = Some(default_data.clone());
14486                }
14487                item
14488            })
14489            .collect::<Vec<lsp::CompletionItem>>(),
14490        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14491    );
14492    resolved_items.lock().clear();
14493
14494    cx.update_editor(|editor, window, cx| {
14495        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14496    });
14497    cx.run_until_parked();
14498    // Completions that have already been resolved are skipped.
14499    assert_eq!(
14500        *resolved_items.lock(),
14501        items[items.len() - 16..items.len() - 4]
14502            .iter()
14503            .cloned()
14504            .map(|mut item| {
14505                if item.data.is_none() {
14506                    item.data = Some(default_data.clone());
14507                }
14508                item
14509            })
14510            .collect::<Vec<lsp::CompletionItem>>()
14511    );
14512    resolved_items.lock().clear();
14513}
14514
14515#[gpui::test]
14516async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14517    init_test(cx, |_| {});
14518
14519    let mut cx = EditorLspTestContext::new(
14520        Language::new(
14521            LanguageConfig {
14522                matcher: LanguageMatcher {
14523                    path_suffixes: vec!["jsx".into()],
14524                    ..Default::default()
14525                },
14526                overrides: [(
14527                    "element".into(),
14528                    LanguageConfigOverride {
14529                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14530                        ..Default::default()
14531                    },
14532                )]
14533                .into_iter()
14534                .collect(),
14535                ..Default::default()
14536            },
14537            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14538        )
14539        .with_override_query("(jsx_self_closing_element) @element")
14540        .unwrap(),
14541        lsp::ServerCapabilities {
14542            completion_provider: Some(lsp::CompletionOptions {
14543                trigger_characters: Some(vec![":".to_string()]),
14544                ..Default::default()
14545            }),
14546            ..Default::default()
14547        },
14548        cx,
14549    )
14550    .await;
14551
14552    cx.lsp
14553        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14554            Ok(Some(lsp::CompletionResponse::Array(vec![
14555                lsp::CompletionItem {
14556                    label: "bg-blue".into(),
14557                    ..Default::default()
14558                },
14559                lsp::CompletionItem {
14560                    label: "bg-red".into(),
14561                    ..Default::default()
14562                },
14563                lsp::CompletionItem {
14564                    label: "bg-yellow".into(),
14565                    ..Default::default()
14566                },
14567            ])))
14568        });
14569
14570    cx.set_state(r#"<p class="bgˇ" />"#);
14571
14572    // Trigger completion when typing a dash, because the dash is an extra
14573    // word character in the 'element' scope, which contains the cursor.
14574    cx.simulate_keystroke("-");
14575    cx.executor().run_until_parked();
14576    cx.update_editor(|editor, _, _| {
14577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14578        {
14579            assert_eq!(
14580                completion_menu_entries(&menu),
14581                &["bg-red", "bg-blue", "bg-yellow"]
14582            );
14583        } else {
14584            panic!("expected completion menu to be open");
14585        }
14586    });
14587
14588    cx.simulate_keystroke("l");
14589    cx.executor().run_until_parked();
14590    cx.update_editor(|editor, _, _| {
14591        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14592        {
14593            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14594        } else {
14595            panic!("expected completion menu to be open");
14596        }
14597    });
14598
14599    // When filtering completions, consider the character after the '-' to
14600    // be the start of a subword.
14601    cx.set_state(r#"<p class="yelˇ" />"#);
14602    cx.simulate_keystroke("l");
14603    cx.executor().run_until_parked();
14604    cx.update_editor(|editor, _, _| {
14605        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14606        {
14607            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14608        } else {
14609            panic!("expected completion menu to be open");
14610        }
14611    });
14612}
14613
14614fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14615    let entries = menu.entries.borrow();
14616    entries.iter().map(|mat| mat.string.clone()).collect()
14617}
14618
14619#[gpui::test]
14620async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14621    init_test(cx, |settings| {
14622        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14623            FormatterList(vec![Formatter::Prettier].into()),
14624        ))
14625    });
14626
14627    let fs = FakeFs::new(cx.executor());
14628    fs.insert_file(path!("/file.ts"), Default::default()).await;
14629
14630    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14631    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14632
14633    language_registry.add(Arc::new(Language::new(
14634        LanguageConfig {
14635            name: "TypeScript".into(),
14636            matcher: LanguageMatcher {
14637                path_suffixes: vec!["ts".to_string()],
14638                ..Default::default()
14639            },
14640            ..Default::default()
14641        },
14642        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14643    )));
14644    update_test_language_settings(cx, |settings| {
14645        settings.defaults.prettier = Some(PrettierSettings {
14646            allowed: true,
14647            ..PrettierSettings::default()
14648        });
14649    });
14650
14651    let test_plugin = "test_plugin";
14652    let _ = language_registry.register_fake_lsp(
14653        "TypeScript",
14654        FakeLspAdapter {
14655            prettier_plugins: vec![test_plugin],
14656            ..Default::default()
14657        },
14658    );
14659
14660    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14661    let buffer = project
14662        .update(cx, |project, cx| {
14663            project.open_local_buffer(path!("/file.ts"), cx)
14664        })
14665        .await
14666        .unwrap();
14667
14668    let buffer_text = "one\ntwo\nthree\n";
14669    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14671    editor.update_in(cx, |editor, window, cx| {
14672        editor.set_text(buffer_text, window, cx)
14673    });
14674
14675    editor
14676        .update_in(cx, |editor, window, cx| {
14677            editor.perform_format(
14678                project.clone(),
14679                FormatTrigger::Manual,
14680                FormatTarget::Buffers,
14681                window,
14682                cx,
14683            )
14684        })
14685        .unwrap()
14686        .await;
14687    assert_eq!(
14688        editor.update(cx, |editor, cx| editor.text(cx)),
14689        buffer_text.to_string() + prettier_format_suffix,
14690        "Test prettier formatting was not applied to the original buffer text",
14691    );
14692
14693    update_test_language_settings(cx, |settings| {
14694        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14695    });
14696    let format = editor.update_in(cx, |editor, window, cx| {
14697        editor.perform_format(
14698            project.clone(),
14699            FormatTrigger::Manual,
14700            FormatTarget::Buffers,
14701            window,
14702            cx,
14703        )
14704    });
14705    format.await.unwrap();
14706    assert_eq!(
14707        editor.update(cx, |editor, cx| editor.text(cx)),
14708        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14709        "Autoformatting (via test prettier) was not applied to the original buffer text",
14710    );
14711}
14712
14713#[gpui::test]
14714async fn test_addition_reverts(cx: &mut TestAppContext) {
14715    init_test(cx, |_| {});
14716    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14717    let base_text = indoc! {r#"
14718        struct Row;
14719        struct Row1;
14720        struct Row2;
14721
14722        struct Row4;
14723        struct Row5;
14724        struct Row6;
14725
14726        struct Row8;
14727        struct Row9;
14728        struct Row10;"#};
14729
14730    // When addition hunks are not adjacent to carets, no hunk revert is performed
14731    assert_hunk_revert(
14732        indoc! {r#"struct Row;
14733                   struct Row1;
14734                   struct Row1.1;
14735                   struct Row1.2;
14736                   struct Row2;ˇ
14737
14738                   struct Row4;
14739                   struct Row5;
14740                   struct Row6;
14741
14742                   struct Row8;
14743                   ˇstruct Row9;
14744                   struct Row9.1;
14745                   struct Row9.2;
14746                   struct Row9.3;
14747                   struct Row10;"#},
14748        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14749        indoc! {r#"struct Row;
14750                   struct Row1;
14751                   struct Row1.1;
14752                   struct Row1.2;
14753                   struct Row2;ˇ
14754
14755                   struct Row4;
14756                   struct Row5;
14757                   struct Row6;
14758
14759                   struct Row8;
14760                   ˇstruct Row9;
14761                   struct Row9.1;
14762                   struct Row9.2;
14763                   struct Row9.3;
14764                   struct Row10;"#},
14765        base_text,
14766        &mut cx,
14767    );
14768    // Same for selections
14769    assert_hunk_revert(
14770        indoc! {r#"struct Row;
14771                   struct Row1;
14772                   struct Row2;
14773                   struct Row2.1;
14774                   struct Row2.2;
14775                   «ˇ
14776                   struct Row4;
14777                   struct» Row5;
14778                   «struct Row6;
14779                   ˇ»
14780                   struct Row9.1;
14781                   struct Row9.2;
14782                   struct Row9.3;
14783                   struct Row8;
14784                   struct Row9;
14785                   struct Row10;"#},
14786        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14787        indoc! {r#"struct Row;
14788                   struct Row1;
14789                   struct Row2;
14790                   struct Row2.1;
14791                   struct Row2.2;
14792                   «ˇ
14793                   struct Row4;
14794                   struct» Row5;
14795                   «struct Row6;
14796                   ˇ»
14797                   struct Row9.1;
14798                   struct Row9.2;
14799                   struct Row9.3;
14800                   struct Row8;
14801                   struct Row9;
14802                   struct Row10;"#},
14803        base_text,
14804        &mut cx,
14805    );
14806
14807    // When carets and selections intersect the addition hunks, those are reverted.
14808    // Adjacent carets got merged.
14809    assert_hunk_revert(
14810        indoc! {r#"struct Row;
14811                   ˇ// something on the top
14812                   struct Row1;
14813                   struct Row2;
14814                   struct Roˇw3.1;
14815                   struct Row2.2;
14816                   struct Row2.3;ˇ
14817
14818                   struct Row4;
14819                   struct ˇRow5.1;
14820                   struct Row5.2;
14821                   struct «Rowˇ»5.3;
14822                   struct Row5;
14823                   struct Row6;
14824                   ˇ
14825                   struct Row9.1;
14826                   struct «Rowˇ»9.2;
14827                   struct «ˇRow»9.3;
14828                   struct Row8;
14829                   struct Row9;
14830                   «ˇ// something on bottom»
14831                   struct Row10;"#},
14832        vec![
14833            DiffHunkStatusKind::Added,
14834            DiffHunkStatusKind::Added,
14835            DiffHunkStatusKind::Added,
14836            DiffHunkStatusKind::Added,
14837            DiffHunkStatusKind::Added,
14838        ],
14839        indoc! {r#"struct Row;
14840                   ˇstruct Row1;
14841                   struct Row2;
14842                   ˇ
14843                   struct Row4;
14844                   ˇstruct Row5;
14845                   struct Row6;
14846                   ˇ
14847                   ˇstruct Row8;
14848                   struct Row9;
14849                   ˇstruct Row10;"#},
14850        base_text,
14851        &mut cx,
14852    );
14853}
14854
14855#[gpui::test]
14856async fn test_modification_reverts(cx: &mut TestAppContext) {
14857    init_test(cx, |_| {});
14858    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14859    let base_text = indoc! {r#"
14860        struct Row;
14861        struct Row1;
14862        struct Row2;
14863
14864        struct Row4;
14865        struct Row5;
14866        struct Row6;
14867
14868        struct Row8;
14869        struct Row9;
14870        struct Row10;"#};
14871
14872    // Modification hunks behave the same as the addition ones.
14873    assert_hunk_revert(
14874        indoc! {r#"struct Row;
14875                   struct Row1;
14876                   struct Row33;
14877                   ˇ
14878                   struct Row4;
14879                   struct Row5;
14880                   struct Row6;
14881                   ˇ
14882                   struct Row99;
14883                   struct Row9;
14884                   struct Row10;"#},
14885        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14886        indoc! {r#"struct Row;
14887                   struct Row1;
14888                   struct Row33;
14889                   ˇ
14890                   struct Row4;
14891                   struct Row5;
14892                   struct Row6;
14893                   ˇ
14894                   struct Row99;
14895                   struct Row9;
14896                   struct Row10;"#},
14897        base_text,
14898        &mut cx,
14899    );
14900    assert_hunk_revert(
14901        indoc! {r#"struct Row;
14902                   struct Row1;
14903                   struct Row33;
14904                   «ˇ
14905                   struct Row4;
14906                   struct» Row5;
14907                   «struct Row6;
14908                   ˇ»
14909                   struct Row99;
14910                   struct Row9;
14911                   struct Row10;"#},
14912        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14913        indoc! {r#"struct Row;
14914                   struct Row1;
14915                   struct Row33;
14916                   «ˇ
14917                   struct Row4;
14918                   struct» Row5;
14919                   «struct Row6;
14920                   ˇ»
14921                   struct Row99;
14922                   struct Row9;
14923                   struct Row10;"#},
14924        base_text,
14925        &mut cx,
14926    );
14927
14928    assert_hunk_revert(
14929        indoc! {r#"ˇstruct Row1.1;
14930                   struct Row1;
14931                   «ˇstr»uct Row22;
14932
14933                   struct ˇRow44;
14934                   struct Row5;
14935                   struct «Rˇ»ow66;ˇ
14936
14937                   «struˇ»ct Row88;
14938                   struct Row9;
14939                   struct Row1011;ˇ"#},
14940        vec![
14941            DiffHunkStatusKind::Modified,
14942            DiffHunkStatusKind::Modified,
14943            DiffHunkStatusKind::Modified,
14944            DiffHunkStatusKind::Modified,
14945            DiffHunkStatusKind::Modified,
14946            DiffHunkStatusKind::Modified,
14947        ],
14948        indoc! {r#"struct Row;
14949                   ˇstruct Row1;
14950                   struct Row2;
14951                   ˇ
14952                   struct Row4;
14953                   ˇstruct Row5;
14954                   struct Row6;
14955                   ˇ
14956                   struct Row8;
14957                   ˇstruct Row9;
14958                   struct Row10;ˇ"#},
14959        base_text,
14960        &mut cx,
14961    );
14962}
14963
14964#[gpui::test]
14965async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14966    init_test(cx, |_| {});
14967    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14968    let base_text = indoc! {r#"
14969        one
14970
14971        two
14972        three
14973        "#};
14974
14975    cx.set_head_text(base_text);
14976    cx.set_state("\nˇ\n");
14977    cx.executor().run_until_parked();
14978    cx.update_editor(|editor, _window, cx| {
14979        editor.expand_selected_diff_hunks(cx);
14980    });
14981    cx.executor().run_until_parked();
14982    cx.update_editor(|editor, window, cx| {
14983        editor.backspace(&Default::default(), window, cx);
14984    });
14985    cx.run_until_parked();
14986    cx.assert_state_with_diff(
14987        indoc! {r#"
14988
14989        - two
14990        - threeˇ
14991        +
14992        "#}
14993        .to_string(),
14994    );
14995}
14996
14997#[gpui::test]
14998async fn test_deletion_reverts(cx: &mut TestAppContext) {
14999    init_test(cx, |_| {});
15000    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15001    let base_text = indoc! {r#"struct Row;
15002struct Row1;
15003struct Row2;
15004
15005struct Row4;
15006struct Row5;
15007struct Row6;
15008
15009struct Row8;
15010struct Row9;
15011struct Row10;"#};
15012
15013    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15014    assert_hunk_revert(
15015        indoc! {r#"struct Row;
15016                   struct Row2;
15017
15018                   ˇstruct Row4;
15019                   struct Row5;
15020                   struct Row6;
15021                   ˇ
15022                   struct Row8;
15023                   struct Row10;"#},
15024        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15025        indoc! {r#"struct Row;
15026                   struct Row2;
15027
15028                   ˇstruct Row4;
15029                   struct Row5;
15030                   struct Row6;
15031                   ˇ
15032                   struct Row8;
15033                   struct Row10;"#},
15034        base_text,
15035        &mut cx,
15036    );
15037    assert_hunk_revert(
15038        indoc! {r#"struct Row;
15039                   struct Row2;
15040
15041                   «ˇstruct Row4;
15042                   struct» Row5;
15043                   «struct Row6;
15044                   ˇ»
15045                   struct Row8;
15046                   struct Row10;"#},
15047        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15048        indoc! {r#"struct Row;
15049                   struct Row2;
15050
15051                   «ˇstruct Row4;
15052                   struct» Row5;
15053                   «struct Row6;
15054                   ˇ»
15055                   struct Row8;
15056                   struct Row10;"#},
15057        base_text,
15058        &mut cx,
15059    );
15060
15061    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15062    assert_hunk_revert(
15063        indoc! {r#"struct Row;
15064                   ˇstruct Row2;
15065
15066                   struct Row4;
15067                   struct Row5;
15068                   struct Row6;
15069
15070                   struct Row8;ˇ
15071                   struct Row10;"#},
15072        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15073        indoc! {r#"struct Row;
15074                   struct Row1;
15075                   ˇstruct Row2;
15076
15077                   struct Row4;
15078                   struct Row5;
15079                   struct Row6;
15080
15081                   struct Row8;ˇ
15082                   struct Row9;
15083                   struct Row10;"#},
15084        base_text,
15085        &mut cx,
15086    );
15087    assert_hunk_revert(
15088        indoc! {r#"struct Row;
15089                   struct Row2«ˇ;
15090                   struct Row4;
15091                   struct» Row5;
15092                   «struct Row6;
15093
15094                   struct Row8;ˇ»
15095                   struct Row10;"#},
15096        vec![
15097            DiffHunkStatusKind::Deleted,
15098            DiffHunkStatusKind::Deleted,
15099            DiffHunkStatusKind::Deleted,
15100        ],
15101        indoc! {r#"struct Row;
15102                   struct Row1;
15103                   struct Row2«ˇ;
15104
15105                   struct Row4;
15106                   struct» Row5;
15107                   «struct Row6;
15108
15109                   struct Row8;ˇ»
15110                   struct Row9;
15111                   struct Row10;"#},
15112        base_text,
15113        &mut cx,
15114    );
15115}
15116
15117#[gpui::test]
15118async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15119    init_test(cx, |_| {});
15120
15121    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15122    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15123    let base_text_3 =
15124        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15125
15126    let text_1 = edit_first_char_of_every_line(base_text_1);
15127    let text_2 = edit_first_char_of_every_line(base_text_2);
15128    let text_3 = edit_first_char_of_every_line(base_text_3);
15129
15130    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15131    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15132    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15133
15134    let multibuffer = cx.new(|cx| {
15135        let mut multibuffer = MultiBuffer::new(ReadWrite);
15136        multibuffer.push_excerpts(
15137            buffer_1.clone(),
15138            [
15139                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15140                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15141                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15142            ],
15143            cx,
15144        );
15145        multibuffer.push_excerpts(
15146            buffer_2.clone(),
15147            [
15148                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15149                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15150                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15151            ],
15152            cx,
15153        );
15154        multibuffer.push_excerpts(
15155            buffer_3.clone(),
15156            [
15157                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15158                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15159                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15160            ],
15161            cx,
15162        );
15163        multibuffer
15164    });
15165
15166    let fs = FakeFs::new(cx.executor());
15167    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15168    let (editor, cx) = cx
15169        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15170    editor.update_in(cx, |editor, _window, cx| {
15171        for (buffer, diff_base) in [
15172            (buffer_1.clone(), base_text_1),
15173            (buffer_2.clone(), base_text_2),
15174            (buffer_3.clone(), base_text_3),
15175        ] {
15176            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15177            editor
15178                .buffer
15179                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15180        }
15181    });
15182    cx.executor().run_until_parked();
15183
15184    editor.update_in(cx, |editor, window, cx| {
15185        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}");
15186        editor.select_all(&SelectAll, window, cx);
15187        editor.git_restore(&Default::default(), window, cx);
15188    });
15189    cx.executor().run_until_parked();
15190
15191    // When all ranges are selected, all buffer hunks are reverted.
15192    editor.update(cx, |editor, cx| {
15193        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");
15194    });
15195    buffer_1.update(cx, |buffer, _| {
15196        assert_eq!(buffer.text(), base_text_1);
15197    });
15198    buffer_2.update(cx, |buffer, _| {
15199        assert_eq!(buffer.text(), base_text_2);
15200    });
15201    buffer_3.update(cx, |buffer, _| {
15202        assert_eq!(buffer.text(), base_text_3);
15203    });
15204
15205    editor.update_in(cx, |editor, window, cx| {
15206        editor.undo(&Default::default(), window, cx);
15207    });
15208
15209    editor.update_in(cx, |editor, window, cx| {
15210        editor.change_selections(None, window, cx, |s| {
15211            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15212        });
15213        editor.git_restore(&Default::default(), window, cx);
15214    });
15215
15216    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15217    // but not affect buffer_2 and its related excerpts.
15218    editor.update(cx, |editor, cx| {
15219        assert_eq!(
15220            editor.text(cx),
15221            "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}"
15222        );
15223    });
15224    buffer_1.update(cx, |buffer, _| {
15225        assert_eq!(buffer.text(), base_text_1);
15226    });
15227    buffer_2.update(cx, |buffer, _| {
15228        assert_eq!(
15229            buffer.text(),
15230            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15231        );
15232    });
15233    buffer_3.update(cx, |buffer, _| {
15234        assert_eq!(
15235            buffer.text(),
15236            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15237        );
15238    });
15239
15240    fn edit_first_char_of_every_line(text: &str) -> String {
15241        text.split('\n')
15242            .map(|line| format!("X{}", &line[1..]))
15243            .collect::<Vec<_>>()
15244            .join("\n")
15245    }
15246}
15247
15248#[gpui::test]
15249async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15250    init_test(cx, |_| {});
15251
15252    let cols = 4;
15253    let rows = 10;
15254    let sample_text_1 = sample_text(rows, cols, 'a');
15255    assert_eq!(
15256        sample_text_1,
15257        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15258    );
15259    let sample_text_2 = sample_text(rows, cols, 'l');
15260    assert_eq!(
15261        sample_text_2,
15262        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15263    );
15264    let sample_text_3 = sample_text(rows, cols, 'v');
15265    assert_eq!(
15266        sample_text_3,
15267        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15268    );
15269
15270    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15271    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15272    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15273
15274    let multi_buffer = cx.new(|cx| {
15275        let mut multibuffer = MultiBuffer::new(ReadWrite);
15276        multibuffer.push_excerpts(
15277            buffer_1.clone(),
15278            [
15279                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15280                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15282            ],
15283            cx,
15284        );
15285        multibuffer.push_excerpts(
15286            buffer_2.clone(),
15287            [
15288                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15289                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15290                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15291            ],
15292            cx,
15293        );
15294        multibuffer.push_excerpts(
15295            buffer_3.clone(),
15296            [
15297                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15298                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15299                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15300            ],
15301            cx,
15302        );
15303        multibuffer
15304    });
15305
15306    let fs = FakeFs::new(cx.executor());
15307    fs.insert_tree(
15308        "/a",
15309        json!({
15310            "main.rs": sample_text_1,
15311            "other.rs": sample_text_2,
15312            "lib.rs": sample_text_3,
15313        }),
15314    )
15315    .await;
15316    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15317    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15318    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15319    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15320        Editor::new(
15321            EditorMode::full(),
15322            multi_buffer,
15323            Some(project.clone()),
15324            window,
15325            cx,
15326        )
15327    });
15328    let multibuffer_item_id = workspace
15329        .update(cx, |workspace, window, cx| {
15330            assert!(
15331                workspace.active_item(cx).is_none(),
15332                "active item should be None before the first item is added"
15333            );
15334            workspace.add_item_to_active_pane(
15335                Box::new(multi_buffer_editor.clone()),
15336                None,
15337                true,
15338                window,
15339                cx,
15340            );
15341            let active_item = workspace
15342                .active_item(cx)
15343                .expect("should have an active item after adding the multi buffer");
15344            assert!(
15345                !active_item.is_singleton(cx),
15346                "A multi buffer was expected to active after adding"
15347            );
15348            active_item.item_id()
15349        })
15350        .unwrap();
15351    cx.executor().run_until_parked();
15352
15353    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15354        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15355            s.select_ranges(Some(1..2))
15356        });
15357        editor.open_excerpts(&OpenExcerpts, window, cx);
15358    });
15359    cx.executor().run_until_parked();
15360    let first_item_id = workspace
15361        .update(cx, |workspace, window, cx| {
15362            let active_item = workspace
15363                .active_item(cx)
15364                .expect("should have an active item after navigating into the 1st buffer");
15365            let first_item_id = active_item.item_id();
15366            assert_ne!(
15367                first_item_id, multibuffer_item_id,
15368                "Should navigate into the 1st buffer and activate it"
15369            );
15370            assert!(
15371                active_item.is_singleton(cx),
15372                "New active item should be a singleton buffer"
15373            );
15374            assert_eq!(
15375                active_item
15376                    .act_as::<Editor>(cx)
15377                    .expect("should have navigated into an editor for the 1st buffer")
15378                    .read(cx)
15379                    .text(cx),
15380                sample_text_1
15381            );
15382
15383            workspace
15384                .go_back(workspace.active_pane().downgrade(), window, cx)
15385                .detach_and_log_err(cx);
15386
15387            first_item_id
15388        })
15389        .unwrap();
15390    cx.executor().run_until_parked();
15391    workspace
15392        .update(cx, |workspace, _, cx| {
15393            let active_item = workspace
15394                .active_item(cx)
15395                .expect("should have an active item after navigating back");
15396            assert_eq!(
15397                active_item.item_id(),
15398                multibuffer_item_id,
15399                "Should navigate back to the multi buffer"
15400            );
15401            assert!(!active_item.is_singleton(cx));
15402        })
15403        .unwrap();
15404
15405    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15406        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15407            s.select_ranges(Some(39..40))
15408        });
15409        editor.open_excerpts(&OpenExcerpts, window, cx);
15410    });
15411    cx.executor().run_until_parked();
15412    let second_item_id = workspace
15413        .update(cx, |workspace, window, cx| {
15414            let active_item = workspace
15415                .active_item(cx)
15416                .expect("should have an active item after navigating into the 2nd buffer");
15417            let second_item_id = active_item.item_id();
15418            assert_ne!(
15419                second_item_id, multibuffer_item_id,
15420                "Should navigate away from the multibuffer"
15421            );
15422            assert_ne!(
15423                second_item_id, first_item_id,
15424                "Should navigate into the 2nd buffer and activate it"
15425            );
15426            assert!(
15427                active_item.is_singleton(cx),
15428                "New active item should be a singleton buffer"
15429            );
15430            assert_eq!(
15431                active_item
15432                    .act_as::<Editor>(cx)
15433                    .expect("should have navigated into an editor")
15434                    .read(cx)
15435                    .text(cx),
15436                sample_text_2
15437            );
15438
15439            workspace
15440                .go_back(workspace.active_pane().downgrade(), window, cx)
15441                .detach_and_log_err(cx);
15442
15443            second_item_id
15444        })
15445        .unwrap();
15446    cx.executor().run_until_parked();
15447    workspace
15448        .update(cx, |workspace, _, cx| {
15449            let active_item = workspace
15450                .active_item(cx)
15451                .expect("should have an active item after navigating back from the 2nd buffer");
15452            assert_eq!(
15453                active_item.item_id(),
15454                multibuffer_item_id,
15455                "Should navigate back from the 2nd buffer to the multi buffer"
15456            );
15457            assert!(!active_item.is_singleton(cx));
15458        })
15459        .unwrap();
15460
15461    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15462        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15463            s.select_ranges(Some(70..70))
15464        });
15465        editor.open_excerpts(&OpenExcerpts, window, cx);
15466    });
15467    cx.executor().run_until_parked();
15468    workspace
15469        .update(cx, |workspace, window, cx| {
15470            let active_item = workspace
15471                .active_item(cx)
15472                .expect("should have an active item after navigating into the 3rd buffer");
15473            let third_item_id = active_item.item_id();
15474            assert_ne!(
15475                third_item_id, multibuffer_item_id,
15476                "Should navigate into the 3rd buffer and activate it"
15477            );
15478            assert_ne!(third_item_id, first_item_id);
15479            assert_ne!(third_item_id, second_item_id);
15480            assert!(
15481                active_item.is_singleton(cx),
15482                "New active item should be a singleton buffer"
15483            );
15484            assert_eq!(
15485                active_item
15486                    .act_as::<Editor>(cx)
15487                    .expect("should have navigated into an editor")
15488                    .read(cx)
15489                    .text(cx),
15490                sample_text_3
15491            );
15492
15493            workspace
15494                .go_back(workspace.active_pane().downgrade(), window, cx)
15495                .detach_and_log_err(cx);
15496        })
15497        .unwrap();
15498    cx.executor().run_until_parked();
15499    workspace
15500        .update(cx, |workspace, _, cx| {
15501            let active_item = workspace
15502                .active_item(cx)
15503                .expect("should have an active item after navigating back from the 3rd buffer");
15504            assert_eq!(
15505                active_item.item_id(),
15506                multibuffer_item_id,
15507                "Should navigate back from the 3rd buffer to the multi buffer"
15508            );
15509            assert!(!active_item.is_singleton(cx));
15510        })
15511        .unwrap();
15512}
15513
15514#[gpui::test]
15515async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15516    init_test(cx, |_| {});
15517
15518    let mut cx = EditorTestContext::new(cx).await;
15519
15520    let diff_base = r#"
15521        use some::mod;
15522
15523        const A: u32 = 42;
15524
15525        fn main() {
15526            println!("hello");
15527
15528            println!("world");
15529        }
15530        "#
15531    .unindent();
15532
15533    cx.set_state(
15534        &r#"
15535        use some::modified;
15536
15537        ˇ
15538        fn main() {
15539            println!("hello there");
15540
15541            println!("around the");
15542            println!("world");
15543        }
15544        "#
15545        .unindent(),
15546    );
15547
15548    cx.set_head_text(&diff_base);
15549    executor.run_until_parked();
15550
15551    cx.update_editor(|editor, window, cx| {
15552        editor.go_to_next_hunk(&GoToHunk, window, cx);
15553        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15554    });
15555    executor.run_until_parked();
15556    cx.assert_state_with_diff(
15557        r#"
15558          use some::modified;
15559
15560
15561          fn main() {
15562        -     println!("hello");
15563        + ˇ    println!("hello there");
15564
15565              println!("around the");
15566              println!("world");
15567          }
15568        "#
15569        .unindent(),
15570    );
15571
15572    cx.update_editor(|editor, window, cx| {
15573        for _ in 0..2 {
15574            editor.go_to_next_hunk(&GoToHunk, window, cx);
15575            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15576        }
15577    });
15578    executor.run_until_parked();
15579    cx.assert_state_with_diff(
15580        r#"
15581        - use some::mod;
15582        + ˇuse some::modified;
15583
15584
15585          fn main() {
15586        -     println!("hello");
15587        +     println!("hello there");
15588
15589        +     println!("around the");
15590              println!("world");
15591          }
15592        "#
15593        .unindent(),
15594    );
15595
15596    cx.update_editor(|editor, window, cx| {
15597        editor.go_to_next_hunk(&GoToHunk, window, cx);
15598        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15599    });
15600    executor.run_until_parked();
15601    cx.assert_state_with_diff(
15602        r#"
15603        - use some::mod;
15604        + use some::modified;
15605
15606        - const A: u32 = 42;
15607          ˇ
15608          fn main() {
15609        -     println!("hello");
15610        +     println!("hello there");
15611
15612        +     println!("around the");
15613              println!("world");
15614          }
15615        "#
15616        .unindent(),
15617    );
15618
15619    cx.update_editor(|editor, window, cx| {
15620        editor.cancel(&Cancel, window, cx);
15621    });
15622
15623    cx.assert_state_with_diff(
15624        r#"
15625          use some::modified;
15626
15627          ˇ
15628          fn main() {
15629              println!("hello there");
15630
15631              println!("around the");
15632              println!("world");
15633          }
15634        "#
15635        .unindent(),
15636    );
15637}
15638
15639#[gpui::test]
15640async fn test_diff_base_change_with_expanded_diff_hunks(
15641    executor: BackgroundExecutor,
15642    cx: &mut TestAppContext,
15643) {
15644    init_test(cx, |_| {});
15645
15646    let mut cx = EditorTestContext::new(cx).await;
15647
15648    let diff_base = r#"
15649        use some::mod1;
15650        use some::mod2;
15651
15652        const A: u32 = 42;
15653        const B: u32 = 42;
15654        const C: u32 = 42;
15655
15656        fn main() {
15657            println!("hello");
15658
15659            println!("world");
15660        }
15661        "#
15662    .unindent();
15663
15664    cx.set_state(
15665        &r#"
15666        use some::mod2;
15667
15668        const A: u32 = 42;
15669        const C: u32 = 42;
15670
15671        fn main(ˇ) {
15672            //println!("hello");
15673
15674            println!("world");
15675            //
15676            //
15677        }
15678        "#
15679        .unindent(),
15680    );
15681
15682    cx.set_head_text(&diff_base);
15683    executor.run_until_parked();
15684
15685    cx.update_editor(|editor, window, cx| {
15686        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15687    });
15688    executor.run_until_parked();
15689    cx.assert_state_with_diff(
15690        r#"
15691        - use some::mod1;
15692          use some::mod2;
15693
15694          const A: u32 = 42;
15695        - const B: u32 = 42;
15696          const C: u32 = 42;
15697
15698          fn main(ˇ) {
15699        -     println!("hello");
15700        +     //println!("hello");
15701
15702              println!("world");
15703        +     //
15704        +     //
15705          }
15706        "#
15707        .unindent(),
15708    );
15709
15710    cx.set_head_text("new diff base!");
15711    executor.run_until_parked();
15712    cx.assert_state_with_diff(
15713        r#"
15714        - new diff base!
15715        + use some::mod2;
15716        +
15717        + const A: u32 = 42;
15718        + const C: u32 = 42;
15719        +
15720        + fn main(ˇ) {
15721        +     //println!("hello");
15722        +
15723        +     println!("world");
15724        +     //
15725        +     //
15726        + }
15727        "#
15728        .unindent(),
15729    );
15730}
15731
15732#[gpui::test]
15733async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15734    init_test(cx, |_| {});
15735
15736    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15737    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15738    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15739    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15740    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15741    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15742
15743    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15744    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15745    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15746
15747    let multi_buffer = cx.new(|cx| {
15748        let mut multibuffer = MultiBuffer::new(ReadWrite);
15749        multibuffer.push_excerpts(
15750            buffer_1.clone(),
15751            [
15752                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15753                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15754                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15755            ],
15756            cx,
15757        );
15758        multibuffer.push_excerpts(
15759            buffer_2.clone(),
15760            [
15761                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15762                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15763                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15764            ],
15765            cx,
15766        );
15767        multibuffer.push_excerpts(
15768            buffer_3.clone(),
15769            [
15770                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15771                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15772                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15773            ],
15774            cx,
15775        );
15776        multibuffer
15777    });
15778
15779    let editor =
15780        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15781    editor
15782        .update(cx, |editor, _window, cx| {
15783            for (buffer, diff_base) in [
15784                (buffer_1.clone(), file_1_old),
15785                (buffer_2.clone(), file_2_old),
15786                (buffer_3.clone(), file_3_old),
15787            ] {
15788                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15789                editor
15790                    .buffer
15791                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15792            }
15793        })
15794        .unwrap();
15795
15796    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15797    cx.run_until_parked();
15798
15799    cx.assert_editor_state(
15800        &"
15801            ˇaaa
15802            ccc
15803            ddd
15804
15805            ggg
15806            hhh
15807
15808
15809            lll
15810            mmm
15811            NNN
15812
15813            qqq
15814            rrr
15815
15816            uuu
15817            111
15818            222
15819            333
15820
15821            666
15822            777
15823
15824            000
15825            !!!"
15826        .unindent(),
15827    );
15828
15829    cx.update_editor(|editor, window, cx| {
15830        editor.select_all(&SelectAll, window, cx);
15831        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15832    });
15833    cx.executor().run_until_parked();
15834
15835    cx.assert_state_with_diff(
15836        "
15837            «aaa
15838          - bbb
15839            ccc
15840            ddd
15841
15842            ggg
15843            hhh
15844
15845
15846            lll
15847            mmm
15848          - nnn
15849          + NNN
15850
15851            qqq
15852            rrr
15853
15854            uuu
15855            111
15856            222
15857            333
15858
15859          + 666
15860            777
15861
15862            000
15863            !!!ˇ»"
15864            .unindent(),
15865    );
15866}
15867
15868#[gpui::test]
15869async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15870    init_test(cx, |_| {});
15871
15872    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15873    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15874
15875    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15876    let multi_buffer = cx.new(|cx| {
15877        let mut multibuffer = MultiBuffer::new(ReadWrite);
15878        multibuffer.push_excerpts(
15879            buffer.clone(),
15880            [
15881                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15882                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15883                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15884            ],
15885            cx,
15886        );
15887        multibuffer
15888    });
15889
15890    let editor =
15891        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15892    editor
15893        .update(cx, |editor, _window, cx| {
15894            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15895            editor
15896                .buffer
15897                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15898        })
15899        .unwrap();
15900
15901    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15902    cx.run_until_parked();
15903
15904    cx.update_editor(|editor, window, cx| {
15905        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15906    });
15907    cx.executor().run_until_parked();
15908
15909    // When the start of a hunk coincides with the start of its excerpt,
15910    // the hunk is expanded. When the start of a a hunk is earlier than
15911    // the start of its excerpt, the hunk is not expanded.
15912    cx.assert_state_with_diff(
15913        "
15914            ˇaaa
15915          - bbb
15916          + BBB
15917
15918          - ddd
15919          - eee
15920          + DDD
15921          + EEE
15922            fff
15923
15924            iii
15925        "
15926        .unindent(),
15927    );
15928}
15929
15930#[gpui::test]
15931async fn test_edits_around_expanded_insertion_hunks(
15932    executor: BackgroundExecutor,
15933    cx: &mut TestAppContext,
15934) {
15935    init_test(cx, |_| {});
15936
15937    let mut cx = EditorTestContext::new(cx).await;
15938
15939    let diff_base = r#"
15940        use some::mod1;
15941        use some::mod2;
15942
15943        const A: u32 = 42;
15944
15945        fn main() {
15946            println!("hello");
15947
15948            println!("world");
15949        }
15950        "#
15951    .unindent();
15952    executor.run_until_parked();
15953    cx.set_state(
15954        &r#"
15955        use some::mod1;
15956        use some::mod2;
15957
15958        const A: u32 = 42;
15959        const B: u32 = 42;
15960        const C: u32 = 42;
15961        ˇ
15962
15963        fn main() {
15964            println!("hello");
15965
15966            println!("world");
15967        }
15968        "#
15969        .unindent(),
15970    );
15971
15972    cx.set_head_text(&diff_base);
15973    executor.run_until_parked();
15974
15975    cx.update_editor(|editor, window, cx| {
15976        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15977    });
15978    executor.run_until_parked();
15979
15980    cx.assert_state_with_diff(
15981        r#"
15982        use some::mod1;
15983        use some::mod2;
15984
15985        const A: u32 = 42;
15986      + const B: u32 = 42;
15987      + const C: u32 = 42;
15988      + ˇ
15989
15990        fn main() {
15991            println!("hello");
15992
15993            println!("world");
15994        }
15995      "#
15996        .unindent(),
15997    );
15998
15999    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16000    executor.run_until_parked();
16001
16002    cx.assert_state_with_diff(
16003        r#"
16004        use some::mod1;
16005        use some::mod2;
16006
16007        const A: u32 = 42;
16008      + const B: u32 = 42;
16009      + const C: u32 = 42;
16010      + const D: u32 = 42;
16011      + ˇ
16012
16013        fn main() {
16014            println!("hello");
16015
16016            println!("world");
16017        }
16018      "#
16019        .unindent(),
16020    );
16021
16022    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16023    executor.run_until_parked();
16024
16025    cx.assert_state_with_diff(
16026        r#"
16027        use some::mod1;
16028        use some::mod2;
16029
16030        const A: u32 = 42;
16031      + const B: u32 = 42;
16032      + const C: u32 = 42;
16033      + const D: u32 = 42;
16034      + const E: u32 = 42;
16035      + ˇ
16036
16037        fn main() {
16038            println!("hello");
16039
16040            println!("world");
16041        }
16042      "#
16043        .unindent(),
16044    );
16045
16046    cx.update_editor(|editor, window, cx| {
16047        editor.delete_line(&DeleteLine, window, cx);
16048    });
16049    executor.run_until_parked();
16050
16051    cx.assert_state_with_diff(
16052        r#"
16053        use some::mod1;
16054        use some::mod2;
16055
16056        const A: u32 = 42;
16057      + const B: u32 = 42;
16058      + const C: u32 = 42;
16059      + const D: u32 = 42;
16060      + const E: u32 = 42;
16061        ˇ
16062        fn main() {
16063            println!("hello");
16064
16065            println!("world");
16066        }
16067      "#
16068        .unindent(),
16069    );
16070
16071    cx.update_editor(|editor, window, cx| {
16072        editor.move_up(&MoveUp, window, cx);
16073        editor.delete_line(&DeleteLine, window, cx);
16074        editor.move_up(&MoveUp, window, cx);
16075        editor.delete_line(&DeleteLine, window, cx);
16076        editor.move_up(&MoveUp, window, cx);
16077        editor.delete_line(&DeleteLine, window, cx);
16078    });
16079    executor.run_until_parked();
16080    cx.assert_state_with_diff(
16081        r#"
16082        use some::mod1;
16083        use some::mod2;
16084
16085        const A: u32 = 42;
16086      + const B: u32 = 42;
16087        ˇ
16088        fn main() {
16089            println!("hello");
16090
16091            println!("world");
16092        }
16093      "#
16094        .unindent(),
16095    );
16096
16097    cx.update_editor(|editor, window, cx| {
16098        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16099        editor.delete_line(&DeleteLine, window, cx);
16100    });
16101    executor.run_until_parked();
16102    cx.assert_state_with_diff(
16103        r#"
16104        ˇ
16105        fn main() {
16106            println!("hello");
16107
16108            println!("world");
16109        }
16110      "#
16111        .unindent(),
16112    );
16113}
16114
16115#[gpui::test]
16116async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16117    init_test(cx, |_| {});
16118
16119    let mut cx = EditorTestContext::new(cx).await;
16120    cx.set_head_text(indoc! { "
16121        one
16122        two
16123        three
16124        four
16125        five
16126        "
16127    });
16128    cx.set_state(indoc! { "
16129        one
16130        ˇthree
16131        five
16132    "});
16133    cx.run_until_parked();
16134    cx.update_editor(|editor, window, cx| {
16135        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16136    });
16137    cx.assert_state_with_diff(
16138        indoc! { "
16139        one
16140      - two
16141        ˇthree
16142      - four
16143        five
16144    "}
16145        .to_string(),
16146    );
16147    cx.update_editor(|editor, window, cx| {
16148        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16149    });
16150
16151    cx.assert_state_with_diff(
16152        indoc! { "
16153        one
16154        ˇthree
16155        five
16156    "}
16157        .to_string(),
16158    );
16159
16160    cx.set_state(indoc! { "
16161        one
16162        ˇTWO
16163        three
16164        four
16165        five
16166    "});
16167    cx.run_until_parked();
16168    cx.update_editor(|editor, window, cx| {
16169        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16170    });
16171
16172    cx.assert_state_with_diff(
16173        indoc! { "
16174            one
16175          - two
16176          + ˇTWO
16177            three
16178            four
16179            five
16180        "}
16181        .to_string(),
16182    );
16183    cx.update_editor(|editor, window, cx| {
16184        editor.move_up(&Default::default(), window, cx);
16185        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16186    });
16187    cx.assert_state_with_diff(
16188        indoc! { "
16189            one
16190            ˇTWO
16191            three
16192            four
16193            five
16194        "}
16195        .to_string(),
16196    );
16197}
16198
16199#[gpui::test]
16200async fn test_edits_around_expanded_deletion_hunks(
16201    executor: BackgroundExecutor,
16202    cx: &mut TestAppContext,
16203) {
16204    init_test(cx, |_| {});
16205
16206    let mut cx = EditorTestContext::new(cx).await;
16207
16208    let diff_base = r#"
16209        use some::mod1;
16210        use some::mod2;
16211
16212        const A: u32 = 42;
16213        const B: u32 = 42;
16214        const C: u32 = 42;
16215
16216
16217        fn main() {
16218            println!("hello");
16219
16220            println!("world");
16221        }
16222    "#
16223    .unindent();
16224    executor.run_until_parked();
16225    cx.set_state(
16226        &r#"
16227        use some::mod1;
16228        use some::mod2;
16229
16230        ˇconst B: u32 = 42;
16231        const C: u32 = 42;
16232
16233
16234        fn main() {
16235            println!("hello");
16236
16237            println!("world");
16238        }
16239        "#
16240        .unindent(),
16241    );
16242
16243    cx.set_head_text(&diff_base);
16244    executor.run_until_parked();
16245
16246    cx.update_editor(|editor, window, cx| {
16247        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16248    });
16249    executor.run_until_parked();
16250
16251    cx.assert_state_with_diff(
16252        r#"
16253        use some::mod1;
16254        use some::mod2;
16255
16256      - const A: u32 = 42;
16257        ˇconst B: u32 = 42;
16258        const C: u32 = 42;
16259
16260
16261        fn main() {
16262            println!("hello");
16263
16264            println!("world");
16265        }
16266      "#
16267        .unindent(),
16268    );
16269
16270    cx.update_editor(|editor, window, cx| {
16271        editor.delete_line(&DeleteLine, window, cx);
16272    });
16273    executor.run_until_parked();
16274    cx.assert_state_with_diff(
16275        r#"
16276        use some::mod1;
16277        use some::mod2;
16278
16279      - const A: u32 = 42;
16280      - const B: u32 = 42;
16281        ˇconst C: u32 = 42;
16282
16283
16284        fn main() {
16285            println!("hello");
16286
16287            println!("world");
16288        }
16289      "#
16290        .unindent(),
16291    );
16292
16293    cx.update_editor(|editor, window, cx| {
16294        editor.delete_line(&DeleteLine, window, cx);
16295    });
16296    executor.run_until_parked();
16297    cx.assert_state_with_diff(
16298        r#"
16299        use some::mod1;
16300        use some::mod2;
16301
16302      - const A: u32 = 42;
16303      - const B: u32 = 42;
16304      - const C: u32 = 42;
16305        ˇ
16306
16307        fn main() {
16308            println!("hello");
16309
16310            println!("world");
16311        }
16312      "#
16313        .unindent(),
16314    );
16315
16316    cx.update_editor(|editor, window, cx| {
16317        editor.handle_input("replacement", window, cx);
16318    });
16319    executor.run_until_parked();
16320    cx.assert_state_with_diff(
16321        r#"
16322        use some::mod1;
16323        use some::mod2;
16324
16325      - const A: u32 = 42;
16326      - const B: u32 = 42;
16327      - const C: u32 = 42;
16328      -
16329      + replacementˇ
16330
16331        fn main() {
16332            println!("hello");
16333
16334            println!("world");
16335        }
16336      "#
16337        .unindent(),
16338    );
16339}
16340
16341#[gpui::test]
16342async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16343    init_test(cx, |_| {});
16344
16345    let mut cx = EditorTestContext::new(cx).await;
16346
16347    let base_text = r#"
16348        one
16349        two
16350        three
16351        four
16352        five
16353    "#
16354    .unindent();
16355    executor.run_until_parked();
16356    cx.set_state(
16357        &r#"
16358        one
16359        two
16360        fˇour
16361        five
16362        "#
16363        .unindent(),
16364    );
16365
16366    cx.set_head_text(&base_text);
16367    executor.run_until_parked();
16368
16369    cx.update_editor(|editor, window, cx| {
16370        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16371    });
16372    executor.run_until_parked();
16373
16374    cx.assert_state_with_diff(
16375        r#"
16376          one
16377          two
16378        - three
16379          fˇour
16380          five
16381        "#
16382        .unindent(),
16383    );
16384
16385    cx.update_editor(|editor, window, cx| {
16386        editor.backspace(&Backspace, window, cx);
16387        editor.backspace(&Backspace, window, cx);
16388    });
16389    executor.run_until_parked();
16390    cx.assert_state_with_diff(
16391        r#"
16392          one
16393          two
16394        - threeˇ
16395        - four
16396        + our
16397          five
16398        "#
16399        .unindent(),
16400    );
16401}
16402
16403#[gpui::test]
16404async fn test_edit_after_expanded_modification_hunk(
16405    executor: BackgroundExecutor,
16406    cx: &mut TestAppContext,
16407) {
16408    init_test(cx, |_| {});
16409
16410    let mut cx = EditorTestContext::new(cx).await;
16411
16412    let diff_base = r#"
16413        use some::mod1;
16414        use some::mod2;
16415
16416        const A: u32 = 42;
16417        const B: u32 = 42;
16418        const C: u32 = 42;
16419        const D: u32 = 42;
16420
16421
16422        fn main() {
16423            println!("hello");
16424
16425            println!("world");
16426        }"#
16427    .unindent();
16428
16429    cx.set_state(
16430        &r#"
16431        use some::mod1;
16432        use some::mod2;
16433
16434        const A: u32 = 42;
16435        const B: u32 = 42;
16436        const C: u32 = 43ˇ
16437        const D: u32 = 42;
16438
16439
16440        fn main() {
16441            println!("hello");
16442
16443            println!("world");
16444        }"#
16445        .unindent(),
16446    );
16447
16448    cx.set_head_text(&diff_base);
16449    executor.run_until_parked();
16450    cx.update_editor(|editor, window, cx| {
16451        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16452    });
16453    executor.run_until_parked();
16454
16455    cx.assert_state_with_diff(
16456        r#"
16457        use some::mod1;
16458        use some::mod2;
16459
16460        const A: u32 = 42;
16461        const B: u32 = 42;
16462      - const C: u32 = 42;
16463      + const C: u32 = 43ˇ
16464        const D: u32 = 42;
16465
16466
16467        fn main() {
16468            println!("hello");
16469
16470            println!("world");
16471        }"#
16472        .unindent(),
16473    );
16474
16475    cx.update_editor(|editor, window, cx| {
16476        editor.handle_input("\nnew_line\n", window, cx);
16477    });
16478    executor.run_until_parked();
16479
16480    cx.assert_state_with_diff(
16481        r#"
16482        use some::mod1;
16483        use some::mod2;
16484
16485        const A: u32 = 42;
16486        const B: u32 = 42;
16487      - const C: u32 = 42;
16488      + const C: u32 = 43
16489      + new_line
16490      + ˇ
16491        const D: u32 = 42;
16492
16493
16494        fn main() {
16495            println!("hello");
16496
16497            println!("world");
16498        }"#
16499        .unindent(),
16500    );
16501}
16502
16503#[gpui::test]
16504async fn test_stage_and_unstage_added_file_hunk(
16505    executor: BackgroundExecutor,
16506    cx: &mut TestAppContext,
16507) {
16508    init_test(cx, |_| {});
16509
16510    let mut cx = EditorTestContext::new(cx).await;
16511    cx.update_editor(|editor, _, cx| {
16512        editor.set_expand_all_diff_hunks(cx);
16513    });
16514
16515    let working_copy = r#"
16516            ˇfn main() {
16517                println!("hello, world!");
16518            }
16519        "#
16520    .unindent();
16521
16522    cx.set_state(&working_copy);
16523    executor.run_until_parked();
16524
16525    cx.assert_state_with_diff(
16526        r#"
16527            + ˇfn main() {
16528            +     println!("hello, world!");
16529            + }
16530        "#
16531        .unindent(),
16532    );
16533    cx.assert_index_text(None);
16534
16535    cx.update_editor(|editor, window, cx| {
16536        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16537    });
16538    executor.run_until_parked();
16539    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16540    cx.assert_state_with_diff(
16541        r#"
16542            + ˇfn main() {
16543            +     println!("hello, world!");
16544            + }
16545        "#
16546        .unindent(),
16547    );
16548
16549    cx.update_editor(|editor, window, cx| {
16550        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16551    });
16552    executor.run_until_parked();
16553    cx.assert_index_text(None);
16554}
16555
16556async fn setup_indent_guides_editor(
16557    text: &str,
16558    cx: &mut TestAppContext,
16559) -> (BufferId, EditorTestContext) {
16560    init_test(cx, |_| {});
16561
16562    let mut cx = EditorTestContext::new(cx).await;
16563
16564    let buffer_id = cx.update_editor(|editor, window, cx| {
16565        editor.set_text(text, window, cx);
16566        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16567
16568        buffer_ids[0]
16569    });
16570
16571    (buffer_id, cx)
16572}
16573
16574fn assert_indent_guides(
16575    range: Range<u32>,
16576    expected: Vec<IndentGuide>,
16577    active_indices: Option<Vec<usize>>,
16578    cx: &mut EditorTestContext,
16579) {
16580    let indent_guides = cx.update_editor(|editor, window, cx| {
16581        let snapshot = editor.snapshot(window, cx).display_snapshot;
16582        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16583            editor,
16584            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16585            true,
16586            &snapshot,
16587            cx,
16588        );
16589
16590        indent_guides.sort_by(|a, b| {
16591            a.depth.cmp(&b.depth).then(
16592                a.start_row
16593                    .cmp(&b.start_row)
16594                    .then(a.end_row.cmp(&b.end_row)),
16595            )
16596        });
16597        indent_guides
16598    });
16599
16600    if let Some(expected) = active_indices {
16601        let active_indices = cx.update_editor(|editor, window, cx| {
16602            let snapshot = editor.snapshot(window, cx).display_snapshot;
16603            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16604        });
16605
16606        assert_eq!(
16607            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16608            expected,
16609            "Active indent guide indices do not match"
16610        );
16611    }
16612
16613    assert_eq!(indent_guides, expected, "Indent guides do not match");
16614}
16615
16616fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16617    IndentGuide {
16618        buffer_id,
16619        start_row: MultiBufferRow(start_row),
16620        end_row: MultiBufferRow(end_row),
16621        depth,
16622        tab_size: 4,
16623        settings: IndentGuideSettings {
16624            enabled: true,
16625            line_width: 1,
16626            active_line_width: 1,
16627            ..Default::default()
16628        },
16629    }
16630}
16631
16632#[gpui::test]
16633async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16634    let (buffer_id, mut cx) = setup_indent_guides_editor(
16635        &"
16636    fn main() {
16637        let a = 1;
16638    }"
16639        .unindent(),
16640        cx,
16641    )
16642    .await;
16643
16644    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16645}
16646
16647#[gpui::test]
16648async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16649    let (buffer_id, mut cx) = setup_indent_guides_editor(
16650        &"
16651    fn main() {
16652        let a = 1;
16653        let b = 2;
16654    }"
16655        .unindent(),
16656        cx,
16657    )
16658    .await;
16659
16660    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16661}
16662
16663#[gpui::test]
16664async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16665    let (buffer_id, mut cx) = setup_indent_guides_editor(
16666        &"
16667    fn main() {
16668        let a = 1;
16669        if a == 3 {
16670            let b = 2;
16671        } else {
16672            let c = 3;
16673        }
16674    }"
16675        .unindent(),
16676        cx,
16677    )
16678    .await;
16679
16680    assert_indent_guides(
16681        0..8,
16682        vec![
16683            indent_guide(buffer_id, 1, 6, 0),
16684            indent_guide(buffer_id, 3, 3, 1),
16685            indent_guide(buffer_id, 5, 5, 1),
16686        ],
16687        None,
16688        &mut cx,
16689    );
16690}
16691
16692#[gpui::test]
16693async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16694    let (buffer_id, mut cx) = setup_indent_guides_editor(
16695        &"
16696    fn main() {
16697        let a = 1;
16698            let b = 2;
16699        let c = 3;
16700    }"
16701        .unindent(),
16702        cx,
16703    )
16704    .await;
16705
16706    assert_indent_guides(
16707        0..5,
16708        vec![
16709            indent_guide(buffer_id, 1, 3, 0),
16710            indent_guide(buffer_id, 2, 2, 1),
16711        ],
16712        None,
16713        &mut cx,
16714    );
16715}
16716
16717#[gpui::test]
16718async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16719    let (buffer_id, mut cx) = setup_indent_guides_editor(
16720        &"
16721        fn main() {
16722            let a = 1;
16723
16724            let c = 3;
16725        }"
16726        .unindent(),
16727        cx,
16728    )
16729    .await;
16730
16731    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16732}
16733
16734#[gpui::test]
16735async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16736    let (buffer_id, mut cx) = setup_indent_guides_editor(
16737        &"
16738        fn main() {
16739            let a = 1;
16740
16741            let c = 3;
16742
16743            if a == 3 {
16744                let b = 2;
16745            } else {
16746                let c = 3;
16747            }
16748        }"
16749        .unindent(),
16750        cx,
16751    )
16752    .await;
16753
16754    assert_indent_guides(
16755        0..11,
16756        vec![
16757            indent_guide(buffer_id, 1, 9, 0),
16758            indent_guide(buffer_id, 6, 6, 1),
16759            indent_guide(buffer_id, 8, 8, 1),
16760        ],
16761        None,
16762        &mut cx,
16763    );
16764}
16765
16766#[gpui::test]
16767async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16768    let (buffer_id, mut cx) = setup_indent_guides_editor(
16769        &"
16770        fn main() {
16771            let a = 1;
16772
16773            let c = 3;
16774
16775            if a == 3 {
16776                let b = 2;
16777            } else {
16778                let c = 3;
16779            }
16780        }"
16781        .unindent(),
16782        cx,
16783    )
16784    .await;
16785
16786    assert_indent_guides(
16787        1..11,
16788        vec![
16789            indent_guide(buffer_id, 1, 9, 0),
16790            indent_guide(buffer_id, 6, 6, 1),
16791            indent_guide(buffer_id, 8, 8, 1),
16792        ],
16793        None,
16794        &mut cx,
16795    );
16796}
16797
16798#[gpui::test]
16799async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16800    let (buffer_id, mut cx) = setup_indent_guides_editor(
16801        &"
16802        fn main() {
16803            let a = 1;
16804
16805            let c = 3;
16806
16807            if a == 3 {
16808                let b = 2;
16809            } else {
16810                let c = 3;
16811            }
16812        }"
16813        .unindent(),
16814        cx,
16815    )
16816    .await;
16817
16818    assert_indent_guides(
16819        1..10,
16820        vec![
16821            indent_guide(buffer_id, 1, 9, 0),
16822            indent_guide(buffer_id, 6, 6, 1),
16823            indent_guide(buffer_id, 8, 8, 1),
16824        ],
16825        None,
16826        &mut cx,
16827    );
16828}
16829
16830#[gpui::test]
16831async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16832    let (buffer_id, mut cx) = setup_indent_guides_editor(
16833        &"
16834        block1
16835            block2
16836                block3
16837                    block4
16838            block2
16839        block1
16840        block1"
16841            .unindent(),
16842        cx,
16843    )
16844    .await;
16845
16846    assert_indent_guides(
16847        1..10,
16848        vec![
16849            indent_guide(buffer_id, 1, 4, 0),
16850            indent_guide(buffer_id, 2, 3, 1),
16851            indent_guide(buffer_id, 3, 3, 2),
16852        ],
16853        None,
16854        &mut cx,
16855    );
16856}
16857
16858#[gpui::test]
16859async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16860    let (buffer_id, mut cx) = setup_indent_guides_editor(
16861        &"
16862        block1
16863            block2
16864                block3
16865
16866        block1
16867        block1"
16868            .unindent(),
16869        cx,
16870    )
16871    .await;
16872
16873    assert_indent_guides(
16874        0..6,
16875        vec![
16876            indent_guide(buffer_id, 1, 2, 0),
16877            indent_guide(buffer_id, 2, 2, 1),
16878        ],
16879        None,
16880        &mut cx,
16881    );
16882}
16883
16884#[gpui::test]
16885async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16886    let (buffer_id, mut cx) = setup_indent_guides_editor(
16887        &"
16888        block1
16889
16890
16891
16892            block2
16893        "
16894        .unindent(),
16895        cx,
16896    )
16897    .await;
16898
16899    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16900}
16901
16902#[gpui::test]
16903async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16904    let (buffer_id, mut cx) = setup_indent_guides_editor(
16905        &"
16906        def a:
16907        \tb = 3
16908        \tif True:
16909        \t\tc = 4
16910        \t\td = 5
16911        \tprint(b)
16912        "
16913        .unindent(),
16914        cx,
16915    )
16916    .await;
16917
16918    assert_indent_guides(
16919        0..6,
16920        vec![
16921            indent_guide(buffer_id, 1, 5, 0),
16922            indent_guide(buffer_id, 3, 4, 1),
16923        ],
16924        None,
16925        &mut cx,
16926    );
16927}
16928
16929#[gpui::test]
16930async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16931    let (buffer_id, mut cx) = setup_indent_guides_editor(
16932        &"
16933    fn main() {
16934        let a = 1;
16935    }"
16936        .unindent(),
16937        cx,
16938    )
16939    .await;
16940
16941    cx.update_editor(|editor, window, cx| {
16942        editor.change_selections(None, window, cx, |s| {
16943            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16944        });
16945    });
16946
16947    assert_indent_guides(
16948        0..3,
16949        vec![indent_guide(buffer_id, 1, 1, 0)],
16950        Some(vec![0]),
16951        &mut cx,
16952    );
16953}
16954
16955#[gpui::test]
16956async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16957    let (buffer_id, mut cx) = setup_indent_guides_editor(
16958        &"
16959    fn main() {
16960        if 1 == 2 {
16961            let a = 1;
16962        }
16963    }"
16964        .unindent(),
16965        cx,
16966    )
16967    .await;
16968
16969    cx.update_editor(|editor, window, cx| {
16970        editor.change_selections(None, window, cx, |s| {
16971            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16972        });
16973    });
16974
16975    assert_indent_guides(
16976        0..4,
16977        vec![
16978            indent_guide(buffer_id, 1, 3, 0),
16979            indent_guide(buffer_id, 2, 2, 1),
16980        ],
16981        Some(vec![1]),
16982        &mut cx,
16983    );
16984
16985    cx.update_editor(|editor, window, cx| {
16986        editor.change_selections(None, window, cx, |s| {
16987            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16988        });
16989    });
16990
16991    assert_indent_guides(
16992        0..4,
16993        vec![
16994            indent_guide(buffer_id, 1, 3, 0),
16995            indent_guide(buffer_id, 2, 2, 1),
16996        ],
16997        Some(vec![1]),
16998        &mut cx,
16999    );
17000
17001    cx.update_editor(|editor, window, cx| {
17002        editor.change_selections(None, window, cx, |s| {
17003            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17004        });
17005    });
17006
17007    assert_indent_guides(
17008        0..4,
17009        vec![
17010            indent_guide(buffer_id, 1, 3, 0),
17011            indent_guide(buffer_id, 2, 2, 1),
17012        ],
17013        Some(vec![0]),
17014        &mut cx,
17015    );
17016}
17017
17018#[gpui::test]
17019async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17020    let (buffer_id, mut cx) = setup_indent_guides_editor(
17021        &"
17022    fn main() {
17023        let a = 1;
17024
17025        let b = 2;
17026    }"
17027        .unindent(),
17028        cx,
17029    )
17030    .await;
17031
17032    cx.update_editor(|editor, window, cx| {
17033        editor.change_selections(None, window, cx, |s| {
17034            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17035        });
17036    });
17037
17038    assert_indent_guides(
17039        0..5,
17040        vec![indent_guide(buffer_id, 1, 3, 0)],
17041        Some(vec![0]),
17042        &mut cx,
17043    );
17044}
17045
17046#[gpui::test]
17047async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17048    let (buffer_id, mut cx) = setup_indent_guides_editor(
17049        &"
17050    def m:
17051        a = 1
17052        pass"
17053            .unindent(),
17054        cx,
17055    )
17056    .await;
17057
17058    cx.update_editor(|editor, window, cx| {
17059        editor.change_selections(None, window, cx, |s| {
17060            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17061        });
17062    });
17063
17064    assert_indent_guides(
17065        0..3,
17066        vec![indent_guide(buffer_id, 1, 2, 0)],
17067        Some(vec![0]),
17068        &mut cx,
17069    );
17070}
17071
17072#[gpui::test]
17073async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17074    init_test(cx, |_| {});
17075    let mut cx = EditorTestContext::new(cx).await;
17076    let text = indoc! {
17077        "
17078        impl A {
17079            fn b() {
17080                0;
17081                3;
17082                5;
17083                6;
17084                7;
17085            }
17086        }
17087        "
17088    };
17089    let base_text = indoc! {
17090        "
17091        impl A {
17092            fn b() {
17093                0;
17094                1;
17095                2;
17096                3;
17097                4;
17098            }
17099            fn c() {
17100                5;
17101                6;
17102                7;
17103            }
17104        }
17105        "
17106    };
17107
17108    cx.update_editor(|editor, window, cx| {
17109        editor.set_text(text, window, cx);
17110
17111        editor.buffer().update(cx, |multibuffer, cx| {
17112            let buffer = multibuffer.as_singleton().unwrap();
17113            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17114
17115            multibuffer.set_all_diff_hunks_expanded(cx);
17116            multibuffer.add_diff(diff, cx);
17117
17118            buffer.read(cx).remote_id()
17119        })
17120    });
17121    cx.run_until_parked();
17122
17123    cx.assert_state_with_diff(
17124        indoc! { "
17125          impl A {
17126              fn b() {
17127                  0;
17128        -         1;
17129        -         2;
17130                  3;
17131        -         4;
17132        -     }
17133        -     fn c() {
17134                  5;
17135                  6;
17136                  7;
17137              }
17138          }
17139          ˇ"
17140        }
17141        .to_string(),
17142    );
17143
17144    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17145        editor
17146            .snapshot(window, cx)
17147            .buffer_snapshot
17148            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17149            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17150            .collect::<Vec<_>>()
17151    });
17152    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17153    assert_eq!(
17154        actual_guides,
17155        vec![
17156            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17157            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17158            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17159        ]
17160    );
17161}
17162
17163#[gpui::test]
17164async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17165    init_test(cx, |_| {});
17166    let mut cx = EditorTestContext::new(cx).await;
17167
17168    let diff_base = r#"
17169        a
17170        b
17171        c
17172        "#
17173    .unindent();
17174
17175    cx.set_state(
17176        &r#"
17177        ˇA
17178        b
17179        C
17180        "#
17181        .unindent(),
17182    );
17183    cx.set_head_text(&diff_base);
17184    cx.update_editor(|editor, window, cx| {
17185        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17186    });
17187    executor.run_until_parked();
17188
17189    let both_hunks_expanded = r#"
17190        - a
17191        + ˇA
17192          b
17193        - c
17194        + C
17195        "#
17196    .unindent();
17197
17198    cx.assert_state_with_diff(both_hunks_expanded.clone());
17199
17200    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17201        let snapshot = editor.snapshot(window, cx);
17202        let hunks = editor
17203            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17204            .collect::<Vec<_>>();
17205        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17206        let buffer_id = hunks[0].buffer_id;
17207        hunks
17208            .into_iter()
17209            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17210            .collect::<Vec<_>>()
17211    });
17212    assert_eq!(hunk_ranges.len(), 2);
17213
17214    cx.update_editor(|editor, _, cx| {
17215        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17216    });
17217    executor.run_until_parked();
17218
17219    let second_hunk_expanded = r#"
17220          ˇA
17221          b
17222        - c
17223        + C
17224        "#
17225    .unindent();
17226
17227    cx.assert_state_with_diff(second_hunk_expanded);
17228
17229    cx.update_editor(|editor, _, cx| {
17230        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17231    });
17232    executor.run_until_parked();
17233
17234    cx.assert_state_with_diff(both_hunks_expanded.clone());
17235
17236    cx.update_editor(|editor, _, cx| {
17237        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17238    });
17239    executor.run_until_parked();
17240
17241    let first_hunk_expanded = r#"
17242        - a
17243        + ˇA
17244          b
17245          C
17246        "#
17247    .unindent();
17248
17249    cx.assert_state_with_diff(first_hunk_expanded);
17250
17251    cx.update_editor(|editor, _, cx| {
17252        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17253    });
17254    executor.run_until_parked();
17255
17256    cx.assert_state_with_diff(both_hunks_expanded);
17257
17258    cx.set_state(
17259        &r#"
17260        ˇA
17261        b
17262        "#
17263        .unindent(),
17264    );
17265    cx.run_until_parked();
17266
17267    // TODO this cursor position seems bad
17268    cx.assert_state_with_diff(
17269        r#"
17270        - ˇa
17271        + A
17272          b
17273        "#
17274        .unindent(),
17275    );
17276
17277    cx.update_editor(|editor, window, cx| {
17278        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17279    });
17280
17281    cx.assert_state_with_diff(
17282        r#"
17283            - ˇa
17284            + A
17285              b
17286            - c
17287            "#
17288        .unindent(),
17289    );
17290
17291    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17292        let snapshot = editor.snapshot(window, cx);
17293        let hunks = editor
17294            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17295            .collect::<Vec<_>>();
17296        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17297        let buffer_id = hunks[0].buffer_id;
17298        hunks
17299            .into_iter()
17300            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17301            .collect::<Vec<_>>()
17302    });
17303    assert_eq!(hunk_ranges.len(), 2);
17304
17305    cx.update_editor(|editor, _, cx| {
17306        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17307    });
17308    executor.run_until_parked();
17309
17310    cx.assert_state_with_diff(
17311        r#"
17312        - ˇa
17313        + A
17314          b
17315        "#
17316        .unindent(),
17317    );
17318}
17319
17320#[gpui::test]
17321async fn test_toggle_deletion_hunk_at_start_of_file(
17322    executor: BackgroundExecutor,
17323    cx: &mut TestAppContext,
17324) {
17325    init_test(cx, |_| {});
17326    let mut cx = EditorTestContext::new(cx).await;
17327
17328    let diff_base = r#"
17329        a
17330        b
17331        c
17332        "#
17333    .unindent();
17334
17335    cx.set_state(
17336        &r#"
17337        ˇb
17338        c
17339        "#
17340        .unindent(),
17341    );
17342    cx.set_head_text(&diff_base);
17343    cx.update_editor(|editor, window, cx| {
17344        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17345    });
17346    executor.run_until_parked();
17347
17348    let hunk_expanded = r#"
17349        - a
17350          ˇb
17351          c
17352        "#
17353    .unindent();
17354
17355    cx.assert_state_with_diff(hunk_expanded.clone());
17356
17357    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17358        let snapshot = editor.snapshot(window, cx);
17359        let hunks = editor
17360            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17361            .collect::<Vec<_>>();
17362        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17363        let buffer_id = hunks[0].buffer_id;
17364        hunks
17365            .into_iter()
17366            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17367            .collect::<Vec<_>>()
17368    });
17369    assert_eq!(hunk_ranges.len(), 1);
17370
17371    cx.update_editor(|editor, _, cx| {
17372        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17373    });
17374    executor.run_until_parked();
17375
17376    let hunk_collapsed = r#"
17377          ˇb
17378          c
17379        "#
17380    .unindent();
17381
17382    cx.assert_state_with_diff(hunk_collapsed);
17383
17384    cx.update_editor(|editor, _, cx| {
17385        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17386    });
17387    executor.run_until_parked();
17388
17389    cx.assert_state_with_diff(hunk_expanded.clone());
17390}
17391
17392#[gpui::test]
17393async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17394    init_test(cx, |_| {});
17395
17396    let fs = FakeFs::new(cx.executor());
17397    fs.insert_tree(
17398        path!("/test"),
17399        json!({
17400            ".git": {},
17401            "file-1": "ONE\n",
17402            "file-2": "TWO\n",
17403            "file-3": "THREE\n",
17404        }),
17405    )
17406    .await;
17407
17408    fs.set_head_for_repo(
17409        path!("/test/.git").as_ref(),
17410        &[
17411            ("file-1".into(), "one\n".into()),
17412            ("file-2".into(), "two\n".into()),
17413            ("file-3".into(), "three\n".into()),
17414        ],
17415    );
17416
17417    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17418    let mut buffers = vec![];
17419    for i in 1..=3 {
17420        let buffer = project
17421            .update(cx, |project, cx| {
17422                let path = format!(path!("/test/file-{}"), i);
17423                project.open_local_buffer(path, cx)
17424            })
17425            .await
17426            .unwrap();
17427        buffers.push(buffer);
17428    }
17429
17430    let multibuffer = cx.new(|cx| {
17431        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17432        multibuffer.set_all_diff_hunks_expanded(cx);
17433        for buffer in &buffers {
17434            let snapshot = buffer.read(cx).snapshot();
17435            multibuffer.set_excerpts_for_path(
17436                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17437                buffer.clone(),
17438                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17439                DEFAULT_MULTIBUFFER_CONTEXT,
17440                cx,
17441            );
17442        }
17443        multibuffer
17444    });
17445
17446    let editor = cx.add_window(|window, cx| {
17447        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17448    });
17449    cx.run_until_parked();
17450
17451    let snapshot = editor
17452        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17453        .unwrap();
17454    let hunks = snapshot
17455        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17456        .map(|hunk| match hunk {
17457            DisplayDiffHunk::Unfolded {
17458                display_row_range, ..
17459            } => display_row_range,
17460            DisplayDiffHunk::Folded { .. } => unreachable!(),
17461        })
17462        .collect::<Vec<_>>();
17463    assert_eq!(
17464        hunks,
17465        [
17466            DisplayRow(2)..DisplayRow(4),
17467            DisplayRow(7)..DisplayRow(9),
17468            DisplayRow(12)..DisplayRow(14),
17469        ]
17470    );
17471}
17472
17473#[gpui::test]
17474async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17475    init_test(cx, |_| {});
17476
17477    let mut cx = EditorTestContext::new(cx).await;
17478    cx.set_head_text(indoc! { "
17479        one
17480        two
17481        three
17482        four
17483        five
17484        "
17485    });
17486    cx.set_index_text(indoc! { "
17487        one
17488        two
17489        three
17490        four
17491        five
17492        "
17493    });
17494    cx.set_state(indoc! {"
17495        one
17496        TWO
17497        ˇTHREE
17498        FOUR
17499        five
17500    "});
17501    cx.run_until_parked();
17502    cx.update_editor(|editor, window, cx| {
17503        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17504    });
17505    cx.run_until_parked();
17506    cx.assert_index_text(Some(indoc! {"
17507        one
17508        TWO
17509        THREE
17510        FOUR
17511        five
17512    "}));
17513    cx.set_state(indoc! { "
17514        one
17515        TWO
17516        ˇTHREE-HUNDRED
17517        FOUR
17518        five
17519    "});
17520    cx.run_until_parked();
17521    cx.update_editor(|editor, window, cx| {
17522        let snapshot = editor.snapshot(window, cx);
17523        let hunks = editor
17524            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17525            .collect::<Vec<_>>();
17526        assert_eq!(hunks.len(), 1);
17527        assert_eq!(
17528            hunks[0].status(),
17529            DiffHunkStatus {
17530                kind: DiffHunkStatusKind::Modified,
17531                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17532            }
17533        );
17534
17535        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17536    });
17537    cx.run_until_parked();
17538    cx.assert_index_text(Some(indoc! {"
17539        one
17540        TWO
17541        THREE-HUNDRED
17542        FOUR
17543        five
17544    "}));
17545}
17546
17547#[gpui::test]
17548fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17549    init_test(cx, |_| {});
17550
17551    let editor = cx.add_window(|window, cx| {
17552        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17553        build_editor(buffer, window, cx)
17554    });
17555
17556    let render_args = Arc::new(Mutex::new(None));
17557    let snapshot = editor
17558        .update(cx, |editor, window, cx| {
17559            let snapshot = editor.buffer().read(cx).snapshot(cx);
17560            let range =
17561                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17562
17563            struct RenderArgs {
17564                row: MultiBufferRow,
17565                folded: bool,
17566                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17567            }
17568
17569            let crease = Crease::inline(
17570                range,
17571                FoldPlaceholder::test(),
17572                {
17573                    let toggle_callback = render_args.clone();
17574                    move |row, folded, callback, _window, _cx| {
17575                        *toggle_callback.lock() = Some(RenderArgs {
17576                            row,
17577                            folded,
17578                            callback,
17579                        });
17580                        div()
17581                    }
17582                },
17583                |_row, _folded, _window, _cx| div(),
17584            );
17585
17586            editor.insert_creases(Some(crease), cx);
17587            let snapshot = editor.snapshot(window, cx);
17588            let _div = snapshot.render_crease_toggle(
17589                MultiBufferRow(1),
17590                false,
17591                cx.entity().clone(),
17592                window,
17593                cx,
17594            );
17595            snapshot
17596        })
17597        .unwrap();
17598
17599    let render_args = render_args.lock().take().unwrap();
17600    assert_eq!(render_args.row, MultiBufferRow(1));
17601    assert!(!render_args.folded);
17602    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17603
17604    cx.update_window(*editor, |_, window, cx| {
17605        (render_args.callback)(true, window, cx)
17606    })
17607    .unwrap();
17608    let snapshot = editor
17609        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17610        .unwrap();
17611    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17612
17613    cx.update_window(*editor, |_, window, cx| {
17614        (render_args.callback)(false, window, cx)
17615    })
17616    .unwrap();
17617    let snapshot = editor
17618        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17619        .unwrap();
17620    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17621}
17622
17623#[gpui::test]
17624async fn test_input_text(cx: &mut TestAppContext) {
17625    init_test(cx, |_| {});
17626    let mut cx = EditorTestContext::new(cx).await;
17627
17628    cx.set_state(
17629        &r#"ˇone
17630        two
17631
17632        three
17633        fourˇ
17634        five
17635
17636        siˇx"#
17637            .unindent(),
17638    );
17639
17640    cx.dispatch_action(HandleInput(String::new()));
17641    cx.assert_editor_state(
17642        &r#"ˇone
17643        two
17644
17645        three
17646        fourˇ
17647        five
17648
17649        siˇx"#
17650            .unindent(),
17651    );
17652
17653    cx.dispatch_action(HandleInput("AAAA".to_string()));
17654    cx.assert_editor_state(
17655        &r#"AAAAˇone
17656        two
17657
17658        three
17659        fourAAAAˇ
17660        five
17661
17662        siAAAAˇx"#
17663            .unindent(),
17664    );
17665}
17666
17667#[gpui::test]
17668async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17669    init_test(cx, |_| {});
17670
17671    let mut cx = EditorTestContext::new(cx).await;
17672    cx.set_state(
17673        r#"let foo = 1;
17674let foo = 2;
17675let foo = 3;
17676let fooˇ = 4;
17677let foo = 5;
17678let foo = 6;
17679let foo = 7;
17680let foo = 8;
17681let foo = 9;
17682let foo = 10;
17683let foo = 11;
17684let foo = 12;
17685let foo = 13;
17686let foo = 14;
17687let foo = 15;"#,
17688    );
17689
17690    cx.update_editor(|e, window, cx| {
17691        assert_eq!(
17692            e.next_scroll_position,
17693            NextScrollCursorCenterTopBottom::Center,
17694            "Default next scroll direction is center",
17695        );
17696
17697        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17698        assert_eq!(
17699            e.next_scroll_position,
17700            NextScrollCursorCenterTopBottom::Top,
17701            "After center, next scroll direction should be top",
17702        );
17703
17704        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17705        assert_eq!(
17706            e.next_scroll_position,
17707            NextScrollCursorCenterTopBottom::Bottom,
17708            "After top, next scroll direction should be bottom",
17709        );
17710
17711        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17712        assert_eq!(
17713            e.next_scroll_position,
17714            NextScrollCursorCenterTopBottom::Center,
17715            "After bottom, scrolling should start over",
17716        );
17717
17718        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17719        assert_eq!(
17720            e.next_scroll_position,
17721            NextScrollCursorCenterTopBottom::Top,
17722            "Scrolling continues if retriggered fast enough"
17723        );
17724    });
17725
17726    cx.executor()
17727        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17728    cx.executor().run_until_parked();
17729    cx.update_editor(|e, _, _| {
17730        assert_eq!(
17731            e.next_scroll_position,
17732            NextScrollCursorCenterTopBottom::Center,
17733            "If scrolling is not triggered fast enough, it should reset"
17734        );
17735    });
17736}
17737
17738#[gpui::test]
17739async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17740    init_test(cx, |_| {});
17741    let mut cx = EditorLspTestContext::new_rust(
17742        lsp::ServerCapabilities {
17743            definition_provider: Some(lsp::OneOf::Left(true)),
17744            references_provider: Some(lsp::OneOf::Left(true)),
17745            ..lsp::ServerCapabilities::default()
17746        },
17747        cx,
17748    )
17749    .await;
17750
17751    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17752        let go_to_definition = cx
17753            .lsp
17754            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17755                move |params, _| async move {
17756                    if empty_go_to_definition {
17757                        Ok(None)
17758                    } else {
17759                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17760                            uri: params.text_document_position_params.text_document.uri,
17761                            range: lsp::Range::new(
17762                                lsp::Position::new(4, 3),
17763                                lsp::Position::new(4, 6),
17764                            ),
17765                        })))
17766                    }
17767                },
17768            );
17769        let references = cx
17770            .lsp
17771            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17772                Ok(Some(vec![lsp::Location {
17773                    uri: params.text_document_position.text_document.uri,
17774                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17775                }]))
17776            });
17777        (go_to_definition, references)
17778    };
17779
17780    cx.set_state(
17781        &r#"fn one() {
17782            let mut a = ˇtwo();
17783        }
17784
17785        fn two() {}"#
17786            .unindent(),
17787    );
17788    set_up_lsp_handlers(false, &mut cx);
17789    let navigated = cx
17790        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17791        .await
17792        .expect("Failed to navigate to definition");
17793    assert_eq!(
17794        navigated,
17795        Navigated::Yes,
17796        "Should have navigated to definition from the GetDefinition response"
17797    );
17798    cx.assert_editor_state(
17799        &r#"fn one() {
17800            let mut a = two();
17801        }
17802
17803        fn «twoˇ»() {}"#
17804            .unindent(),
17805    );
17806
17807    let editors = cx.update_workspace(|workspace, _, cx| {
17808        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17809    });
17810    cx.update_editor(|_, _, test_editor_cx| {
17811        assert_eq!(
17812            editors.len(),
17813            1,
17814            "Initially, only one, test, editor should be open in the workspace"
17815        );
17816        assert_eq!(
17817            test_editor_cx.entity(),
17818            editors.last().expect("Asserted len is 1").clone()
17819        );
17820    });
17821
17822    set_up_lsp_handlers(true, &mut cx);
17823    let navigated = cx
17824        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17825        .await
17826        .expect("Failed to navigate to lookup references");
17827    assert_eq!(
17828        navigated,
17829        Navigated::Yes,
17830        "Should have navigated to references as a fallback after empty GoToDefinition response"
17831    );
17832    // We should not change the selections in the existing file,
17833    // if opening another milti buffer with the references
17834    cx.assert_editor_state(
17835        &r#"fn one() {
17836            let mut a = two();
17837        }
17838
17839        fn «twoˇ»() {}"#
17840            .unindent(),
17841    );
17842    let editors = cx.update_workspace(|workspace, _, cx| {
17843        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17844    });
17845    cx.update_editor(|_, _, test_editor_cx| {
17846        assert_eq!(
17847            editors.len(),
17848            2,
17849            "After falling back to references search, we open a new editor with the results"
17850        );
17851        let references_fallback_text = editors
17852            .into_iter()
17853            .find(|new_editor| *new_editor != test_editor_cx.entity())
17854            .expect("Should have one non-test editor now")
17855            .read(test_editor_cx)
17856            .text(test_editor_cx);
17857        assert_eq!(
17858            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17859            "Should use the range from the references response and not the GoToDefinition one"
17860        );
17861    });
17862}
17863
17864#[gpui::test]
17865async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17866    init_test(cx, |_| {});
17867    cx.update(|cx| {
17868        let mut editor_settings = EditorSettings::get_global(cx).clone();
17869        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17870        EditorSettings::override_global(editor_settings, cx);
17871    });
17872    let mut cx = EditorLspTestContext::new_rust(
17873        lsp::ServerCapabilities {
17874            definition_provider: Some(lsp::OneOf::Left(true)),
17875            references_provider: Some(lsp::OneOf::Left(true)),
17876            ..lsp::ServerCapabilities::default()
17877        },
17878        cx,
17879    )
17880    .await;
17881    let original_state = r#"fn one() {
17882        let mut a = ˇtwo();
17883    }
17884
17885    fn two() {}"#
17886        .unindent();
17887    cx.set_state(&original_state);
17888
17889    let mut go_to_definition = cx
17890        .lsp
17891        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17892            move |_, _| async move { Ok(None) },
17893        );
17894    let _references = cx
17895        .lsp
17896        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17897            panic!("Should not call for references with no go to definition fallback")
17898        });
17899
17900    let navigated = cx
17901        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17902        .await
17903        .expect("Failed to navigate to lookup references");
17904    go_to_definition
17905        .next()
17906        .await
17907        .expect("Should have called the go_to_definition handler");
17908
17909    assert_eq!(
17910        navigated,
17911        Navigated::No,
17912        "Should have navigated to references as a fallback after empty GoToDefinition response"
17913    );
17914    cx.assert_editor_state(&original_state);
17915    let editors = cx.update_workspace(|workspace, _, cx| {
17916        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17917    });
17918    cx.update_editor(|_, _, _| {
17919        assert_eq!(
17920            editors.len(),
17921            1,
17922            "After unsuccessful fallback, no other editor should have been opened"
17923        );
17924    });
17925}
17926
17927#[gpui::test]
17928async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17929    init_test(cx, |_| {});
17930
17931    let language = Arc::new(Language::new(
17932        LanguageConfig::default(),
17933        Some(tree_sitter_rust::LANGUAGE.into()),
17934    ));
17935
17936    let text = r#"
17937        #[cfg(test)]
17938        mod tests() {
17939            #[test]
17940            fn runnable_1() {
17941                let a = 1;
17942            }
17943
17944            #[test]
17945            fn runnable_2() {
17946                let a = 1;
17947                let b = 2;
17948            }
17949        }
17950    "#
17951    .unindent();
17952
17953    let fs = FakeFs::new(cx.executor());
17954    fs.insert_file("/file.rs", Default::default()).await;
17955
17956    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17957    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17958    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17959    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17960    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17961
17962    let editor = cx.new_window_entity(|window, cx| {
17963        Editor::new(
17964            EditorMode::full(),
17965            multi_buffer,
17966            Some(project.clone()),
17967            window,
17968            cx,
17969        )
17970    });
17971
17972    editor.update_in(cx, |editor, window, cx| {
17973        let snapshot = editor.buffer().read(cx).snapshot(cx);
17974        editor.tasks.insert(
17975            (buffer.read(cx).remote_id(), 3),
17976            RunnableTasks {
17977                templates: vec![],
17978                offset: snapshot.anchor_before(43),
17979                column: 0,
17980                extra_variables: HashMap::default(),
17981                context_range: BufferOffset(43)..BufferOffset(85),
17982            },
17983        );
17984        editor.tasks.insert(
17985            (buffer.read(cx).remote_id(), 8),
17986            RunnableTasks {
17987                templates: vec![],
17988                offset: snapshot.anchor_before(86),
17989                column: 0,
17990                extra_variables: HashMap::default(),
17991                context_range: BufferOffset(86)..BufferOffset(191),
17992            },
17993        );
17994
17995        // Test finding task when cursor is inside function body
17996        editor.change_selections(None, window, cx, |s| {
17997            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17998        });
17999        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18000        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18001
18002        // Test finding task when cursor is on function name
18003        editor.change_selections(None, window, cx, |s| {
18004            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18005        });
18006        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18007        assert_eq!(row, 8, "Should find task when cursor is on function name");
18008    });
18009}
18010
18011#[gpui::test]
18012async fn test_folding_buffers(cx: &mut TestAppContext) {
18013    init_test(cx, |_| {});
18014
18015    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18016    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18017    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18018
18019    let fs = FakeFs::new(cx.executor());
18020    fs.insert_tree(
18021        path!("/a"),
18022        json!({
18023            "first.rs": sample_text_1,
18024            "second.rs": sample_text_2,
18025            "third.rs": sample_text_3,
18026        }),
18027    )
18028    .await;
18029    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18030    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18031    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18032    let worktree = project.update(cx, |project, cx| {
18033        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18034        assert_eq!(worktrees.len(), 1);
18035        worktrees.pop().unwrap()
18036    });
18037    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18038
18039    let buffer_1 = project
18040        .update(cx, |project, cx| {
18041            project.open_buffer((worktree_id, "first.rs"), cx)
18042        })
18043        .await
18044        .unwrap();
18045    let buffer_2 = project
18046        .update(cx, |project, cx| {
18047            project.open_buffer((worktree_id, "second.rs"), cx)
18048        })
18049        .await
18050        .unwrap();
18051    let buffer_3 = project
18052        .update(cx, |project, cx| {
18053            project.open_buffer((worktree_id, "third.rs"), cx)
18054        })
18055        .await
18056        .unwrap();
18057
18058    let multi_buffer = cx.new(|cx| {
18059        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18060        multi_buffer.push_excerpts(
18061            buffer_1.clone(),
18062            [
18063                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18064                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18065                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18066            ],
18067            cx,
18068        );
18069        multi_buffer.push_excerpts(
18070            buffer_2.clone(),
18071            [
18072                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18073                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18074                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18075            ],
18076            cx,
18077        );
18078        multi_buffer.push_excerpts(
18079            buffer_3.clone(),
18080            [
18081                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18082                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18083                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18084            ],
18085            cx,
18086        );
18087        multi_buffer
18088    });
18089    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18090        Editor::new(
18091            EditorMode::full(),
18092            multi_buffer.clone(),
18093            Some(project.clone()),
18094            window,
18095            cx,
18096        )
18097    });
18098
18099    assert_eq!(
18100        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18101        "\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",
18102    );
18103
18104    multi_buffer_editor.update(cx, |editor, cx| {
18105        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18106    });
18107    assert_eq!(
18108        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18109        "\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",
18110        "After folding the first buffer, its text should not be displayed"
18111    );
18112
18113    multi_buffer_editor.update(cx, |editor, cx| {
18114        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18115    });
18116    assert_eq!(
18117        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18118        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18119        "After folding the second buffer, its text should not be displayed"
18120    );
18121
18122    multi_buffer_editor.update(cx, |editor, cx| {
18123        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18124    });
18125    assert_eq!(
18126        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18127        "\n\n\n\n\n",
18128        "After folding the third buffer, its text should not be displayed"
18129    );
18130
18131    // Emulate selection inside the fold logic, that should work
18132    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18133        editor
18134            .snapshot(window, cx)
18135            .next_line_boundary(Point::new(0, 4));
18136    });
18137
18138    multi_buffer_editor.update(cx, |editor, cx| {
18139        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18140    });
18141    assert_eq!(
18142        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18143        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18144        "After unfolding the second buffer, its text should be displayed"
18145    );
18146
18147    // Typing inside of buffer 1 causes that buffer to be unfolded.
18148    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18149        assert_eq!(
18150            multi_buffer
18151                .read(cx)
18152                .snapshot(cx)
18153                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18154                .collect::<String>(),
18155            "bbbb"
18156        );
18157        editor.change_selections(None, window, cx, |selections| {
18158            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18159        });
18160        editor.handle_input("B", window, cx);
18161    });
18162
18163    assert_eq!(
18164        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18165        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18166        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18167    );
18168
18169    multi_buffer_editor.update(cx, |editor, cx| {
18170        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18171    });
18172    assert_eq!(
18173        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18174        "\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",
18175        "After unfolding the all buffers, all original text should be displayed"
18176    );
18177}
18178
18179#[gpui::test]
18180async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18181    init_test(cx, |_| {});
18182
18183    let sample_text_1 = "1111\n2222\n3333".to_string();
18184    let sample_text_2 = "4444\n5555\n6666".to_string();
18185    let sample_text_3 = "7777\n8888\n9999".to_string();
18186
18187    let fs = FakeFs::new(cx.executor());
18188    fs.insert_tree(
18189        path!("/a"),
18190        json!({
18191            "first.rs": sample_text_1,
18192            "second.rs": sample_text_2,
18193            "third.rs": sample_text_3,
18194        }),
18195    )
18196    .await;
18197    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18198    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18199    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18200    let worktree = project.update(cx, |project, cx| {
18201        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18202        assert_eq!(worktrees.len(), 1);
18203        worktrees.pop().unwrap()
18204    });
18205    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18206
18207    let buffer_1 = project
18208        .update(cx, |project, cx| {
18209            project.open_buffer((worktree_id, "first.rs"), cx)
18210        })
18211        .await
18212        .unwrap();
18213    let buffer_2 = project
18214        .update(cx, |project, cx| {
18215            project.open_buffer((worktree_id, "second.rs"), cx)
18216        })
18217        .await
18218        .unwrap();
18219    let buffer_3 = project
18220        .update(cx, |project, cx| {
18221            project.open_buffer((worktree_id, "third.rs"), cx)
18222        })
18223        .await
18224        .unwrap();
18225
18226    let multi_buffer = cx.new(|cx| {
18227        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18228        multi_buffer.push_excerpts(
18229            buffer_1.clone(),
18230            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18231            cx,
18232        );
18233        multi_buffer.push_excerpts(
18234            buffer_2.clone(),
18235            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18236            cx,
18237        );
18238        multi_buffer.push_excerpts(
18239            buffer_3.clone(),
18240            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18241            cx,
18242        );
18243        multi_buffer
18244    });
18245
18246    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18247        Editor::new(
18248            EditorMode::full(),
18249            multi_buffer,
18250            Some(project.clone()),
18251            window,
18252            cx,
18253        )
18254    });
18255
18256    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18257    assert_eq!(
18258        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18259        full_text,
18260    );
18261
18262    multi_buffer_editor.update(cx, |editor, cx| {
18263        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18264    });
18265    assert_eq!(
18266        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18267        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18268        "After folding the first buffer, its text should not be displayed"
18269    );
18270
18271    multi_buffer_editor.update(cx, |editor, cx| {
18272        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18273    });
18274
18275    assert_eq!(
18276        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18277        "\n\n\n\n\n\n7777\n8888\n9999",
18278        "After folding the second buffer, its text should not be displayed"
18279    );
18280
18281    multi_buffer_editor.update(cx, |editor, cx| {
18282        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18283    });
18284    assert_eq!(
18285        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18286        "\n\n\n\n\n",
18287        "After folding the third buffer, its text should not be displayed"
18288    );
18289
18290    multi_buffer_editor.update(cx, |editor, cx| {
18291        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18292    });
18293    assert_eq!(
18294        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18295        "\n\n\n\n4444\n5555\n6666\n\n",
18296        "After unfolding the second buffer, its text should be displayed"
18297    );
18298
18299    multi_buffer_editor.update(cx, |editor, cx| {
18300        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18301    });
18302    assert_eq!(
18303        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18304        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18305        "After unfolding the first buffer, its text should be displayed"
18306    );
18307
18308    multi_buffer_editor.update(cx, |editor, cx| {
18309        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18310    });
18311    assert_eq!(
18312        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18313        full_text,
18314        "After unfolding all buffers, all original text should be displayed"
18315    );
18316}
18317
18318#[gpui::test]
18319async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18320    init_test(cx, |_| {});
18321
18322    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18323
18324    let fs = FakeFs::new(cx.executor());
18325    fs.insert_tree(
18326        path!("/a"),
18327        json!({
18328            "main.rs": sample_text,
18329        }),
18330    )
18331    .await;
18332    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18333    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18334    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18335    let worktree = project.update(cx, |project, cx| {
18336        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18337        assert_eq!(worktrees.len(), 1);
18338        worktrees.pop().unwrap()
18339    });
18340    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18341
18342    let buffer_1 = project
18343        .update(cx, |project, cx| {
18344            project.open_buffer((worktree_id, "main.rs"), cx)
18345        })
18346        .await
18347        .unwrap();
18348
18349    let multi_buffer = cx.new(|cx| {
18350        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18351        multi_buffer.push_excerpts(
18352            buffer_1.clone(),
18353            [ExcerptRange::new(
18354                Point::new(0, 0)
18355                    ..Point::new(
18356                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18357                        0,
18358                    ),
18359            )],
18360            cx,
18361        );
18362        multi_buffer
18363    });
18364    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18365        Editor::new(
18366            EditorMode::full(),
18367            multi_buffer,
18368            Some(project.clone()),
18369            window,
18370            cx,
18371        )
18372    });
18373
18374    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18375    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18376        enum TestHighlight {}
18377        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18378        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18379        editor.highlight_text::<TestHighlight>(
18380            vec![highlight_range.clone()],
18381            HighlightStyle::color(Hsla::green()),
18382            cx,
18383        );
18384        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18385    });
18386
18387    let full_text = format!("\n\n{sample_text}");
18388    assert_eq!(
18389        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18390        full_text,
18391    );
18392}
18393
18394#[gpui::test]
18395async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18396    init_test(cx, |_| {});
18397    cx.update(|cx| {
18398        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18399            "keymaps/default-linux.json",
18400            cx,
18401        )
18402        .unwrap();
18403        cx.bind_keys(default_key_bindings);
18404    });
18405
18406    let (editor, cx) = cx.add_window_view(|window, cx| {
18407        let multi_buffer = MultiBuffer::build_multi(
18408            [
18409                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18410                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18411                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18412                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18413            ],
18414            cx,
18415        );
18416        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18417
18418        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18419        // fold all but the second buffer, so that we test navigating between two
18420        // adjacent folded buffers, as well as folded buffers at the start and
18421        // end the multibuffer
18422        editor.fold_buffer(buffer_ids[0], cx);
18423        editor.fold_buffer(buffer_ids[2], cx);
18424        editor.fold_buffer(buffer_ids[3], cx);
18425
18426        editor
18427    });
18428    cx.simulate_resize(size(px(1000.), px(1000.)));
18429
18430    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18431    cx.assert_excerpts_with_selections(indoc! {"
18432        [EXCERPT]
18433        ˇ[FOLDED]
18434        [EXCERPT]
18435        a1
18436        b1
18437        [EXCERPT]
18438        [FOLDED]
18439        [EXCERPT]
18440        [FOLDED]
18441        "
18442    });
18443    cx.simulate_keystroke("down");
18444    cx.assert_excerpts_with_selections(indoc! {"
18445        [EXCERPT]
18446        [FOLDED]
18447        [EXCERPT]
18448        ˇa1
18449        b1
18450        [EXCERPT]
18451        [FOLDED]
18452        [EXCERPT]
18453        [FOLDED]
18454        "
18455    });
18456    cx.simulate_keystroke("down");
18457    cx.assert_excerpts_with_selections(indoc! {"
18458        [EXCERPT]
18459        [FOLDED]
18460        [EXCERPT]
18461        a1
18462        ˇb1
18463        [EXCERPT]
18464        [FOLDED]
18465        [EXCERPT]
18466        [FOLDED]
18467        "
18468    });
18469    cx.simulate_keystroke("down");
18470    cx.assert_excerpts_with_selections(indoc! {"
18471        [EXCERPT]
18472        [FOLDED]
18473        [EXCERPT]
18474        a1
18475        b1
18476        ˇ[EXCERPT]
18477        [FOLDED]
18478        [EXCERPT]
18479        [FOLDED]
18480        "
18481    });
18482    cx.simulate_keystroke("down");
18483    cx.assert_excerpts_with_selections(indoc! {"
18484        [EXCERPT]
18485        [FOLDED]
18486        [EXCERPT]
18487        a1
18488        b1
18489        [EXCERPT]
18490        ˇ[FOLDED]
18491        [EXCERPT]
18492        [FOLDED]
18493        "
18494    });
18495    for _ in 0..5 {
18496        cx.simulate_keystroke("down");
18497        cx.assert_excerpts_with_selections(indoc! {"
18498            [EXCERPT]
18499            [FOLDED]
18500            [EXCERPT]
18501            a1
18502            b1
18503            [EXCERPT]
18504            [FOLDED]
18505            [EXCERPT]
18506            ˇ[FOLDED]
18507            "
18508        });
18509    }
18510
18511    cx.simulate_keystroke("up");
18512    cx.assert_excerpts_with_selections(indoc! {"
18513        [EXCERPT]
18514        [FOLDED]
18515        [EXCERPT]
18516        a1
18517        b1
18518        [EXCERPT]
18519        ˇ[FOLDED]
18520        [EXCERPT]
18521        [FOLDED]
18522        "
18523    });
18524    cx.simulate_keystroke("up");
18525    cx.assert_excerpts_with_selections(indoc! {"
18526        [EXCERPT]
18527        [FOLDED]
18528        [EXCERPT]
18529        a1
18530        b1
18531        ˇ[EXCERPT]
18532        [FOLDED]
18533        [EXCERPT]
18534        [FOLDED]
18535        "
18536    });
18537    cx.simulate_keystroke("up");
18538    cx.assert_excerpts_with_selections(indoc! {"
18539        [EXCERPT]
18540        [FOLDED]
18541        [EXCERPT]
18542        a1
18543        ˇb1
18544        [EXCERPT]
18545        [FOLDED]
18546        [EXCERPT]
18547        [FOLDED]
18548        "
18549    });
18550    cx.simulate_keystroke("up");
18551    cx.assert_excerpts_with_selections(indoc! {"
18552        [EXCERPT]
18553        [FOLDED]
18554        [EXCERPT]
18555        ˇa1
18556        b1
18557        [EXCERPT]
18558        [FOLDED]
18559        [EXCERPT]
18560        [FOLDED]
18561        "
18562    });
18563    for _ in 0..5 {
18564        cx.simulate_keystroke("up");
18565        cx.assert_excerpts_with_selections(indoc! {"
18566            [EXCERPT]
18567            ˇ[FOLDED]
18568            [EXCERPT]
18569            a1
18570            b1
18571            [EXCERPT]
18572            [FOLDED]
18573            [EXCERPT]
18574            [FOLDED]
18575            "
18576        });
18577    }
18578}
18579
18580#[gpui::test]
18581async fn test_inline_completion_text(cx: &mut TestAppContext) {
18582    init_test(cx, |_| {});
18583
18584    // Simple insertion
18585    assert_highlighted_edits(
18586        "Hello, world!",
18587        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18588        true,
18589        cx,
18590        |highlighted_edits, cx| {
18591            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18592            assert_eq!(highlighted_edits.highlights.len(), 1);
18593            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18594            assert_eq!(
18595                highlighted_edits.highlights[0].1.background_color,
18596                Some(cx.theme().status().created_background)
18597            );
18598        },
18599    )
18600    .await;
18601
18602    // Replacement
18603    assert_highlighted_edits(
18604        "This is a test.",
18605        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18606        false,
18607        cx,
18608        |highlighted_edits, cx| {
18609            assert_eq!(highlighted_edits.text, "That is a test.");
18610            assert_eq!(highlighted_edits.highlights.len(), 1);
18611            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18612            assert_eq!(
18613                highlighted_edits.highlights[0].1.background_color,
18614                Some(cx.theme().status().created_background)
18615            );
18616        },
18617    )
18618    .await;
18619
18620    // Multiple edits
18621    assert_highlighted_edits(
18622        "Hello, world!",
18623        vec![
18624            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18625            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18626        ],
18627        false,
18628        cx,
18629        |highlighted_edits, cx| {
18630            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18631            assert_eq!(highlighted_edits.highlights.len(), 2);
18632            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18633            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18634            assert_eq!(
18635                highlighted_edits.highlights[0].1.background_color,
18636                Some(cx.theme().status().created_background)
18637            );
18638            assert_eq!(
18639                highlighted_edits.highlights[1].1.background_color,
18640                Some(cx.theme().status().created_background)
18641            );
18642        },
18643    )
18644    .await;
18645
18646    // Multiple lines with edits
18647    assert_highlighted_edits(
18648        "First line\nSecond line\nThird line\nFourth line",
18649        vec![
18650            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18651            (
18652                Point::new(2, 0)..Point::new(2, 10),
18653                "New third line".to_string(),
18654            ),
18655            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18656        ],
18657        false,
18658        cx,
18659        |highlighted_edits, cx| {
18660            assert_eq!(
18661                highlighted_edits.text,
18662                "Second modified\nNew third line\nFourth updated line"
18663            );
18664            assert_eq!(highlighted_edits.highlights.len(), 3);
18665            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18666            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18667            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18668            for highlight in &highlighted_edits.highlights {
18669                assert_eq!(
18670                    highlight.1.background_color,
18671                    Some(cx.theme().status().created_background)
18672                );
18673            }
18674        },
18675    )
18676    .await;
18677}
18678
18679#[gpui::test]
18680async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18681    init_test(cx, |_| {});
18682
18683    // Deletion
18684    assert_highlighted_edits(
18685        "Hello, world!",
18686        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18687        true,
18688        cx,
18689        |highlighted_edits, cx| {
18690            assert_eq!(highlighted_edits.text, "Hello, world!");
18691            assert_eq!(highlighted_edits.highlights.len(), 1);
18692            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18693            assert_eq!(
18694                highlighted_edits.highlights[0].1.background_color,
18695                Some(cx.theme().status().deleted_background)
18696            );
18697        },
18698    )
18699    .await;
18700
18701    // Insertion
18702    assert_highlighted_edits(
18703        "Hello, world!",
18704        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18705        true,
18706        cx,
18707        |highlighted_edits, cx| {
18708            assert_eq!(highlighted_edits.highlights.len(), 1);
18709            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18710            assert_eq!(
18711                highlighted_edits.highlights[0].1.background_color,
18712                Some(cx.theme().status().created_background)
18713            );
18714        },
18715    )
18716    .await;
18717}
18718
18719async fn assert_highlighted_edits(
18720    text: &str,
18721    edits: Vec<(Range<Point>, String)>,
18722    include_deletions: bool,
18723    cx: &mut TestAppContext,
18724    assertion_fn: impl Fn(HighlightedText, &App),
18725) {
18726    let window = cx.add_window(|window, cx| {
18727        let buffer = MultiBuffer::build_simple(text, cx);
18728        Editor::new(EditorMode::full(), buffer, None, window, cx)
18729    });
18730    let cx = &mut VisualTestContext::from_window(*window, cx);
18731
18732    let (buffer, snapshot) = window
18733        .update(cx, |editor, _window, cx| {
18734            (
18735                editor.buffer().clone(),
18736                editor.buffer().read(cx).snapshot(cx),
18737            )
18738        })
18739        .unwrap();
18740
18741    let edits = edits
18742        .into_iter()
18743        .map(|(range, edit)| {
18744            (
18745                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18746                edit,
18747            )
18748        })
18749        .collect::<Vec<_>>();
18750
18751    let text_anchor_edits = edits
18752        .clone()
18753        .into_iter()
18754        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18755        .collect::<Vec<_>>();
18756
18757    let edit_preview = window
18758        .update(cx, |_, _window, cx| {
18759            buffer
18760                .read(cx)
18761                .as_singleton()
18762                .unwrap()
18763                .read(cx)
18764                .preview_edits(text_anchor_edits.into(), cx)
18765        })
18766        .unwrap()
18767        .await;
18768
18769    cx.update(|_window, cx| {
18770        let highlighted_edits = inline_completion_edit_text(
18771            &snapshot.as_singleton().unwrap().2,
18772            &edits,
18773            &edit_preview,
18774            include_deletions,
18775            cx,
18776        );
18777        assertion_fn(highlighted_edits, cx)
18778    });
18779}
18780
18781#[track_caller]
18782fn assert_breakpoint(
18783    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18784    path: &Arc<Path>,
18785    expected: Vec<(u32, Breakpoint)>,
18786) {
18787    if expected.len() == 0usize {
18788        assert!(!breakpoints.contains_key(path), "{}", path.display());
18789    } else {
18790        let mut breakpoint = breakpoints
18791            .get(path)
18792            .unwrap()
18793            .into_iter()
18794            .map(|breakpoint| {
18795                (
18796                    breakpoint.row,
18797                    Breakpoint {
18798                        message: breakpoint.message.clone(),
18799                        state: breakpoint.state,
18800                        condition: breakpoint.condition.clone(),
18801                        hit_condition: breakpoint.hit_condition.clone(),
18802                    },
18803                )
18804            })
18805            .collect::<Vec<_>>();
18806
18807        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18808
18809        assert_eq!(expected, breakpoint);
18810    }
18811}
18812
18813fn add_log_breakpoint_at_cursor(
18814    editor: &mut Editor,
18815    log_message: &str,
18816    window: &mut Window,
18817    cx: &mut Context<Editor>,
18818) {
18819    let (anchor, bp) = editor
18820        .breakpoints_at_cursors(window, cx)
18821        .first()
18822        .and_then(|(anchor, bp)| {
18823            if let Some(bp) = bp {
18824                Some((*anchor, bp.clone()))
18825            } else {
18826                None
18827            }
18828        })
18829        .unwrap_or_else(|| {
18830            let cursor_position: Point = editor.selections.newest(cx).head();
18831
18832            let breakpoint_position = editor
18833                .snapshot(window, cx)
18834                .display_snapshot
18835                .buffer_snapshot
18836                .anchor_before(Point::new(cursor_position.row, 0));
18837
18838            (breakpoint_position, Breakpoint::new_log(&log_message))
18839        });
18840
18841    editor.edit_breakpoint_at_anchor(
18842        anchor,
18843        bp,
18844        BreakpointEditAction::EditLogMessage(log_message.into()),
18845        cx,
18846    );
18847}
18848
18849#[gpui::test]
18850async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18851    init_test(cx, |_| {});
18852
18853    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18854    let fs = FakeFs::new(cx.executor());
18855    fs.insert_tree(
18856        path!("/a"),
18857        json!({
18858            "main.rs": sample_text,
18859        }),
18860    )
18861    .await;
18862    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18863    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18864    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18865
18866    let fs = FakeFs::new(cx.executor());
18867    fs.insert_tree(
18868        path!("/a"),
18869        json!({
18870            "main.rs": sample_text,
18871        }),
18872    )
18873    .await;
18874    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18877    let worktree_id = workspace
18878        .update(cx, |workspace, _window, cx| {
18879            workspace.project().update(cx, |project, cx| {
18880                project.worktrees(cx).next().unwrap().read(cx).id()
18881            })
18882        })
18883        .unwrap();
18884
18885    let buffer = project
18886        .update(cx, |project, cx| {
18887            project.open_buffer((worktree_id, "main.rs"), cx)
18888        })
18889        .await
18890        .unwrap();
18891
18892    let (editor, cx) = cx.add_window_view(|window, cx| {
18893        Editor::new(
18894            EditorMode::full(),
18895            MultiBuffer::build_from_buffer(buffer, cx),
18896            Some(project.clone()),
18897            window,
18898            cx,
18899        )
18900    });
18901
18902    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18903    let abs_path = project.read_with(cx, |project, cx| {
18904        project
18905            .absolute_path(&project_path, cx)
18906            .map(|path_buf| Arc::from(path_buf.to_owned()))
18907            .unwrap()
18908    });
18909
18910    // assert we can add breakpoint on the first line
18911    editor.update_in(cx, |editor, window, cx| {
18912        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18913        editor.move_to_end(&MoveToEnd, window, cx);
18914        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18915    });
18916
18917    let breakpoints = editor.update(cx, |editor, cx| {
18918        editor
18919            .breakpoint_store()
18920            .as_ref()
18921            .unwrap()
18922            .read(cx)
18923            .all_source_breakpoints(cx)
18924            .clone()
18925    });
18926
18927    assert_eq!(1, breakpoints.len());
18928    assert_breakpoint(
18929        &breakpoints,
18930        &abs_path,
18931        vec![
18932            (0, Breakpoint::new_standard()),
18933            (3, Breakpoint::new_standard()),
18934        ],
18935    );
18936
18937    editor.update_in(cx, |editor, window, cx| {
18938        editor.move_to_beginning(&MoveToBeginning, window, cx);
18939        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18940    });
18941
18942    let breakpoints = editor.update(cx, |editor, cx| {
18943        editor
18944            .breakpoint_store()
18945            .as_ref()
18946            .unwrap()
18947            .read(cx)
18948            .all_source_breakpoints(cx)
18949            .clone()
18950    });
18951
18952    assert_eq!(1, breakpoints.len());
18953    assert_breakpoint(
18954        &breakpoints,
18955        &abs_path,
18956        vec![(3, Breakpoint::new_standard())],
18957    );
18958
18959    editor.update_in(cx, |editor, window, cx| {
18960        editor.move_to_end(&MoveToEnd, window, cx);
18961        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18962    });
18963
18964    let breakpoints = editor.update(cx, |editor, cx| {
18965        editor
18966            .breakpoint_store()
18967            .as_ref()
18968            .unwrap()
18969            .read(cx)
18970            .all_source_breakpoints(cx)
18971            .clone()
18972    });
18973
18974    assert_eq!(0, breakpoints.len());
18975    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18976}
18977
18978#[gpui::test]
18979async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18980    init_test(cx, |_| {});
18981
18982    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18983
18984    let fs = FakeFs::new(cx.executor());
18985    fs.insert_tree(
18986        path!("/a"),
18987        json!({
18988            "main.rs": sample_text,
18989        }),
18990    )
18991    .await;
18992    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18993    let (workspace, cx) =
18994        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18995
18996    let worktree_id = workspace.update(cx, |workspace, cx| {
18997        workspace.project().update(cx, |project, cx| {
18998            project.worktrees(cx).next().unwrap().read(cx).id()
18999        })
19000    });
19001
19002    let buffer = project
19003        .update(cx, |project, cx| {
19004            project.open_buffer((worktree_id, "main.rs"), cx)
19005        })
19006        .await
19007        .unwrap();
19008
19009    let (editor, cx) = cx.add_window_view(|window, cx| {
19010        Editor::new(
19011            EditorMode::full(),
19012            MultiBuffer::build_from_buffer(buffer, cx),
19013            Some(project.clone()),
19014            window,
19015            cx,
19016        )
19017    });
19018
19019    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19020    let abs_path = project.read_with(cx, |project, cx| {
19021        project
19022            .absolute_path(&project_path, cx)
19023            .map(|path_buf| Arc::from(path_buf.to_owned()))
19024            .unwrap()
19025    });
19026
19027    editor.update_in(cx, |editor, window, cx| {
19028        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19029    });
19030
19031    let breakpoints = editor.update(cx, |editor, cx| {
19032        editor
19033            .breakpoint_store()
19034            .as_ref()
19035            .unwrap()
19036            .read(cx)
19037            .all_source_breakpoints(cx)
19038            .clone()
19039    });
19040
19041    assert_breakpoint(
19042        &breakpoints,
19043        &abs_path,
19044        vec![(0, Breakpoint::new_log("hello world"))],
19045    );
19046
19047    // Removing a log message from a log breakpoint should remove it
19048    editor.update_in(cx, |editor, window, cx| {
19049        add_log_breakpoint_at_cursor(editor, "", window, cx);
19050    });
19051
19052    let breakpoints = editor.update(cx, |editor, cx| {
19053        editor
19054            .breakpoint_store()
19055            .as_ref()
19056            .unwrap()
19057            .read(cx)
19058            .all_source_breakpoints(cx)
19059            .clone()
19060    });
19061
19062    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19063
19064    editor.update_in(cx, |editor, window, cx| {
19065        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19066        editor.move_to_end(&MoveToEnd, window, cx);
19067        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19068        // Not adding a log message to a standard breakpoint shouldn't remove it
19069        add_log_breakpoint_at_cursor(editor, "", window, cx);
19070    });
19071
19072    let breakpoints = editor.update(cx, |editor, cx| {
19073        editor
19074            .breakpoint_store()
19075            .as_ref()
19076            .unwrap()
19077            .read(cx)
19078            .all_source_breakpoints(cx)
19079            .clone()
19080    });
19081
19082    assert_breakpoint(
19083        &breakpoints,
19084        &abs_path,
19085        vec![
19086            (0, Breakpoint::new_standard()),
19087            (3, Breakpoint::new_standard()),
19088        ],
19089    );
19090
19091    editor.update_in(cx, |editor, window, cx| {
19092        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19093    });
19094
19095    let breakpoints = editor.update(cx, |editor, cx| {
19096        editor
19097            .breakpoint_store()
19098            .as_ref()
19099            .unwrap()
19100            .read(cx)
19101            .all_source_breakpoints(cx)
19102            .clone()
19103    });
19104
19105    assert_breakpoint(
19106        &breakpoints,
19107        &abs_path,
19108        vec![
19109            (0, Breakpoint::new_standard()),
19110            (3, Breakpoint::new_log("hello world")),
19111        ],
19112    );
19113
19114    editor.update_in(cx, |editor, window, cx| {
19115        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19116    });
19117
19118    let breakpoints = editor.update(cx, |editor, cx| {
19119        editor
19120            .breakpoint_store()
19121            .as_ref()
19122            .unwrap()
19123            .read(cx)
19124            .all_source_breakpoints(cx)
19125            .clone()
19126    });
19127
19128    assert_breakpoint(
19129        &breakpoints,
19130        &abs_path,
19131        vec![
19132            (0, Breakpoint::new_standard()),
19133            (3, Breakpoint::new_log("hello Earth!!")),
19134        ],
19135    );
19136}
19137
19138/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19139/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19140/// or when breakpoints were placed out of order. This tests for a regression too
19141#[gpui::test]
19142async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19143    init_test(cx, |_| {});
19144
19145    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19146    let fs = FakeFs::new(cx.executor());
19147    fs.insert_tree(
19148        path!("/a"),
19149        json!({
19150            "main.rs": sample_text,
19151        }),
19152    )
19153    .await;
19154    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19155    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19156    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19157
19158    let fs = FakeFs::new(cx.executor());
19159    fs.insert_tree(
19160        path!("/a"),
19161        json!({
19162            "main.rs": sample_text,
19163        }),
19164    )
19165    .await;
19166    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19167    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19168    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19169    let worktree_id = workspace
19170        .update(cx, |workspace, _window, cx| {
19171            workspace.project().update(cx, |project, cx| {
19172                project.worktrees(cx).next().unwrap().read(cx).id()
19173            })
19174        })
19175        .unwrap();
19176
19177    let buffer = project
19178        .update(cx, |project, cx| {
19179            project.open_buffer((worktree_id, "main.rs"), cx)
19180        })
19181        .await
19182        .unwrap();
19183
19184    let (editor, cx) = cx.add_window_view(|window, cx| {
19185        Editor::new(
19186            EditorMode::full(),
19187            MultiBuffer::build_from_buffer(buffer, cx),
19188            Some(project.clone()),
19189            window,
19190            cx,
19191        )
19192    });
19193
19194    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19195    let abs_path = project.read_with(cx, |project, cx| {
19196        project
19197            .absolute_path(&project_path, cx)
19198            .map(|path_buf| Arc::from(path_buf.to_owned()))
19199            .unwrap()
19200    });
19201
19202    // assert we can add breakpoint on the first line
19203    editor.update_in(cx, |editor, window, cx| {
19204        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19205        editor.move_to_end(&MoveToEnd, window, cx);
19206        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19207        editor.move_up(&MoveUp, window, cx);
19208        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19209    });
19210
19211    let breakpoints = editor.update(cx, |editor, cx| {
19212        editor
19213            .breakpoint_store()
19214            .as_ref()
19215            .unwrap()
19216            .read(cx)
19217            .all_source_breakpoints(cx)
19218            .clone()
19219    });
19220
19221    assert_eq!(1, breakpoints.len());
19222    assert_breakpoint(
19223        &breakpoints,
19224        &abs_path,
19225        vec![
19226            (0, Breakpoint::new_standard()),
19227            (2, Breakpoint::new_standard()),
19228            (3, Breakpoint::new_standard()),
19229        ],
19230    );
19231
19232    editor.update_in(cx, |editor, window, cx| {
19233        editor.move_to_beginning(&MoveToBeginning, window, cx);
19234        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19235        editor.move_to_end(&MoveToEnd, window, cx);
19236        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19237        // Disabling a breakpoint that doesn't exist should do nothing
19238        editor.move_up(&MoveUp, window, cx);
19239        editor.move_up(&MoveUp, window, cx);
19240        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19241    });
19242
19243    let breakpoints = editor.update(cx, |editor, cx| {
19244        editor
19245            .breakpoint_store()
19246            .as_ref()
19247            .unwrap()
19248            .read(cx)
19249            .all_source_breakpoints(cx)
19250            .clone()
19251    });
19252
19253    let disable_breakpoint = {
19254        let mut bp = Breakpoint::new_standard();
19255        bp.state = BreakpointState::Disabled;
19256        bp
19257    };
19258
19259    assert_eq!(1, breakpoints.len());
19260    assert_breakpoint(
19261        &breakpoints,
19262        &abs_path,
19263        vec![
19264            (0, disable_breakpoint.clone()),
19265            (2, Breakpoint::new_standard()),
19266            (3, disable_breakpoint.clone()),
19267        ],
19268    );
19269
19270    editor.update_in(cx, |editor, window, cx| {
19271        editor.move_to_beginning(&MoveToBeginning, window, cx);
19272        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19273        editor.move_to_end(&MoveToEnd, window, cx);
19274        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19275        editor.move_up(&MoveUp, window, cx);
19276        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19277    });
19278
19279    let breakpoints = editor.update(cx, |editor, cx| {
19280        editor
19281            .breakpoint_store()
19282            .as_ref()
19283            .unwrap()
19284            .read(cx)
19285            .all_source_breakpoints(cx)
19286            .clone()
19287    });
19288
19289    assert_eq!(1, breakpoints.len());
19290    assert_breakpoint(
19291        &breakpoints,
19292        &abs_path,
19293        vec![
19294            (0, Breakpoint::new_standard()),
19295            (2, disable_breakpoint),
19296            (3, Breakpoint::new_standard()),
19297        ],
19298    );
19299}
19300
19301#[gpui::test]
19302async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19303    init_test(cx, |_| {});
19304    let capabilities = lsp::ServerCapabilities {
19305        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19306            prepare_provider: Some(true),
19307            work_done_progress_options: Default::default(),
19308        })),
19309        ..Default::default()
19310    };
19311    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19312
19313    cx.set_state(indoc! {"
19314        struct Fˇoo {}
19315    "});
19316
19317    cx.update_editor(|editor, _, cx| {
19318        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19319        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19320        editor.highlight_background::<DocumentHighlightRead>(
19321            &[highlight_range],
19322            |c| c.editor_document_highlight_read_background,
19323            cx,
19324        );
19325    });
19326
19327    let mut prepare_rename_handler = cx
19328        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19329            move |_, _, _| async move {
19330                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19331                    start: lsp::Position {
19332                        line: 0,
19333                        character: 7,
19334                    },
19335                    end: lsp::Position {
19336                        line: 0,
19337                        character: 10,
19338                    },
19339                })))
19340            },
19341        );
19342    let prepare_rename_task = cx
19343        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19344        .expect("Prepare rename was not started");
19345    prepare_rename_handler.next().await.unwrap();
19346    prepare_rename_task.await.expect("Prepare rename failed");
19347
19348    let mut rename_handler =
19349        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19350            let edit = lsp::TextEdit {
19351                range: lsp::Range {
19352                    start: lsp::Position {
19353                        line: 0,
19354                        character: 7,
19355                    },
19356                    end: lsp::Position {
19357                        line: 0,
19358                        character: 10,
19359                    },
19360                },
19361                new_text: "FooRenamed".to_string(),
19362            };
19363            Ok(Some(lsp::WorkspaceEdit::new(
19364                // Specify the same edit twice
19365                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19366            )))
19367        });
19368    let rename_task = cx
19369        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19370        .expect("Confirm rename was not started");
19371    rename_handler.next().await.unwrap();
19372    rename_task.await.expect("Confirm rename failed");
19373    cx.run_until_parked();
19374
19375    // Despite two edits, only one is actually applied as those are identical
19376    cx.assert_editor_state(indoc! {"
19377        struct FooRenamedˇ {}
19378    "});
19379}
19380
19381#[gpui::test]
19382async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19383    init_test(cx, |_| {});
19384    // These capabilities indicate that the server does not support prepare rename.
19385    let capabilities = lsp::ServerCapabilities {
19386        rename_provider: Some(lsp::OneOf::Left(true)),
19387        ..Default::default()
19388    };
19389    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19390
19391    cx.set_state(indoc! {"
19392        struct Fˇoo {}
19393    "});
19394
19395    cx.update_editor(|editor, _window, cx| {
19396        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19397        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19398        editor.highlight_background::<DocumentHighlightRead>(
19399            &[highlight_range],
19400            |c| c.editor_document_highlight_read_background,
19401            cx,
19402        );
19403    });
19404
19405    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19406        .expect("Prepare rename was not started")
19407        .await
19408        .expect("Prepare rename failed");
19409
19410    let mut rename_handler =
19411        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19412            let edit = lsp::TextEdit {
19413                range: lsp::Range {
19414                    start: lsp::Position {
19415                        line: 0,
19416                        character: 7,
19417                    },
19418                    end: lsp::Position {
19419                        line: 0,
19420                        character: 10,
19421                    },
19422                },
19423                new_text: "FooRenamed".to_string(),
19424            };
19425            Ok(Some(lsp::WorkspaceEdit::new(
19426                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19427            )))
19428        });
19429    let rename_task = cx
19430        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19431        .expect("Confirm rename was not started");
19432    rename_handler.next().await.unwrap();
19433    rename_task.await.expect("Confirm rename failed");
19434    cx.run_until_parked();
19435
19436    // Correct range is renamed, as `surrounding_word` is used to find it.
19437    cx.assert_editor_state(indoc! {"
19438        struct FooRenamedˇ {}
19439    "});
19440}
19441
19442#[gpui::test]
19443async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19444    init_test(cx, |_| {});
19445    let mut cx = EditorTestContext::new(cx).await;
19446
19447    let language = Arc::new(
19448        Language::new(
19449            LanguageConfig::default(),
19450            Some(tree_sitter_html::LANGUAGE.into()),
19451        )
19452        .with_brackets_query(
19453            r#"
19454            ("<" @open "/>" @close)
19455            ("</" @open ">" @close)
19456            ("<" @open ">" @close)
19457            ("\"" @open "\"" @close)
19458            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19459        "#,
19460        )
19461        .unwrap(),
19462    );
19463    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19464
19465    cx.set_state(indoc! {"
19466        <span>ˇ</span>
19467    "});
19468    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19469    cx.assert_editor_state(indoc! {"
19470        <span>
19471        ˇ
19472        </span>
19473    "});
19474
19475    cx.set_state(indoc! {"
19476        <span><span></span>ˇ</span>
19477    "});
19478    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19479    cx.assert_editor_state(indoc! {"
19480        <span><span></span>
19481        ˇ</span>
19482    "});
19483
19484    cx.set_state(indoc! {"
19485        <span>ˇ
19486        </span>
19487    "});
19488    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19489    cx.assert_editor_state(indoc! {"
19490        <span>
19491        ˇ
19492        </span>
19493    "});
19494}
19495
19496#[gpui::test(iterations = 10)]
19497async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19498    init_test(cx, |_| {});
19499
19500    let fs = FakeFs::new(cx.executor());
19501    fs.insert_tree(
19502        path!("/dir"),
19503        json!({
19504            "a.ts": "a",
19505        }),
19506    )
19507    .await;
19508
19509    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19510    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19511    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19512
19513    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19514    language_registry.add(Arc::new(Language::new(
19515        LanguageConfig {
19516            name: "TypeScript".into(),
19517            matcher: LanguageMatcher {
19518                path_suffixes: vec!["ts".to_string()],
19519                ..Default::default()
19520            },
19521            ..Default::default()
19522        },
19523        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19524    )));
19525    let mut fake_language_servers = language_registry.register_fake_lsp(
19526        "TypeScript",
19527        FakeLspAdapter {
19528            capabilities: lsp::ServerCapabilities {
19529                code_lens_provider: Some(lsp::CodeLensOptions {
19530                    resolve_provider: Some(true),
19531                }),
19532                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19533                    commands: vec!["_the/command".to_string()],
19534                    ..lsp::ExecuteCommandOptions::default()
19535                }),
19536                ..lsp::ServerCapabilities::default()
19537            },
19538            ..FakeLspAdapter::default()
19539        },
19540    );
19541
19542    let (buffer, _handle) = project
19543        .update(cx, |p, cx| {
19544            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19545        })
19546        .await
19547        .unwrap();
19548    cx.executor().run_until_parked();
19549
19550    let fake_server = fake_language_servers.next().await.unwrap();
19551
19552    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19553    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19554    drop(buffer_snapshot);
19555    let actions = cx
19556        .update_window(*workspace, |_, window, cx| {
19557            project.code_actions(&buffer, anchor..anchor, window, cx)
19558        })
19559        .unwrap();
19560
19561    fake_server
19562        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19563            Ok(Some(vec![
19564                lsp::CodeLens {
19565                    range: lsp::Range::default(),
19566                    command: Some(lsp::Command {
19567                        title: "Code lens command".to_owned(),
19568                        command: "_the/command".to_owned(),
19569                        arguments: None,
19570                    }),
19571                    data: None,
19572                },
19573                lsp::CodeLens {
19574                    range: lsp::Range::default(),
19575                    command: Some(lsp::Command {
19576                        title: "Command not in capabilities".to_owned(),
19577                        command: "not in capabilities".to_owned(),
19578                        arguments: None,
19579                    }),
19580                    data: None,
19581                },
19582                lsp::CodeLens {
19583                    range: lsp::Range {
19584                        start: lsp::Position {
19585                            line: 1,
19586                            character: 1,
19587                        },
19588                        end: lsp::Position {
19589                            line: 1,
19590                            character: 1,
19591                        },
19592                    },
19593                    command: Some(lsp::Command {
19594                        title: "Command not in range".to_owned(),
19595                        command: "_the/command".to_owned(),
19596                        arguments: None,
19597                    }),
19598                    data: None,
19599                },
19600            ]))
19601        })
19602        .next()
19603        .await;
19604
19605    let actions = actions.await.unwrap();
19606    assert_eq!(
19607        actions.len(),
19608        1,
19609        "Should have only one valid action for the 0..0 range"
19610    );
19611    let action = actions[0].clone();
19612    let apply = project.update(cx, |project, cx| {
19613        project.apply_code_action(buffer.clone(), action, true, cx)
19614    });
19615
19616    // Resolving the code action does not populate its edits. In absence of
19617    // edits, we must execute the given command.
19618    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19619        |mut lens, _| async move {
19620            let lens_command = lens.command.as_mut().expect("should have a command");
19621            assert_eq!(lens_command.title, "Code lens command");
19622            lens_command.arguments = Some(vec![json!("the-argument")]);
19623            Ok(lens)
19624        },
19625    );
19626
19627    // While executing the command, the language server sends the editor
19628    // a `workspaceEdit` request.
19629    fake_server
19630        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19631            let fake = fake_server.clone();
19632            move |params, _| {
19633                assert_eq!(params.command, "_the/command");
19634                let fake = fake.clone();
19635                async move {
19636                    fake.server
19637                        .request::<lsp::request::ApplyWorkspaceEdit>(
19638                            lsp::ApplyWorkspaceEditParams {
19639                                label: None,
19640                                edit: lsp::WorkspaceEdit {
19641                                    changes: Some(
19642                                        [(
19643                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19644                                            vec![lsp::TextEdit {
19645                                                range: lsp::Range::new(
19646                                                    lsp::Position::new(0, 0),
19647                                                    lsp::Position::new(0, 0),
19648                                                ),
19649                                                new_text: "X".into(),
19650                                            }],
19651                                        )]
19652                                        .into_iter()
19653                                        .collect(),
19654                                    ),
19655                                    ..Default::default()
19656                                },
19657                            },
19658                        )
19659                        .await
19660                        .into_response()
19661                        .unwrap();
19662                    Ok(Some(json!(null)))
19663                }
19664            }
19665        })
19666        .next()
19667        .await;
19668
19669    // Applying the code lens command returns a project transaction containing the edits
19670    // sent by the language server in its `workspaceEdit` request.
19671    let transaction = apply.await.unwrap();
19672    assert!(transaction.0.contains_key(&buffer));
19673    buffer.update(cx, |buffer, cx| {
19674        assert_eq!(buffer.text(), "Xa");
19675        buffer.undo(cx);
19676        assert_eq!(buffer.text(), "a");
19677    });
19678}
19679
19680#[gpui::test]
19681async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19682    init_test(cx, |_| {});
19683
19684    let fs = FakeFs::new(cx.executor());
19685    let main_text = r#"fn main() {
19686println!("1");
19687println!("2");
19688println!("3");
19689println!("4");
19690println!("5");
19691}"#;
19692    let lib_text = "mod foo {}";
19693    fs.insert_tree(
19694        path!("/a"),
19695        json!({
19696            "lib.rs": lib_text,
19697            "main.rs": main_text,
19698        }),
19699    )
19700    .await;
19701
19702    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19703    let (workspace, cx) =
19704        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19705    let worktree_id = workspace.update(cx, |workspace, cx| {
19706        workspace.project().update(cx, |project, cx| {
19707            project.worktrees(cx).next().unwrap().read(cx).id()
19708        })
19709    });
19710
19711    let expected_ranges = vec![
19712        Point::new(0, 0)..Point::new(0, 0),
19713        Point::new(1, 0)..Point::new(1, 1),
19714        Point::new(2, 0)..Point::new(2, 2),
19715        Point::new(3, 0)..Point::new(3, 3),
19716    ];
19717
19718    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19719    let editor_1 = workspace
19720        .update_in(cx, |workspace, window, cx| {
19721            workspace.open_path(
19722                (worktree_id, "main.rs"),
19723                Some(pane_1.downgrade()),
19724                true,
19725                window,
19726                cx,
19727            )
19728        })
19729        .unwrap()
19730        .await
19731        .downcast::<Editor>()
19732        .unwrap();
19733    pane_1.update(cx, |pane, cx| {
19734        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19735        open_editor.update(cx, |editor, cx| {
19736            assert_eq!(
19737                editor.display_text(cx),
19738                main_text,
19739                "Original main.rs text on initial open",
19740            );
19741            assert_eq!(
19742                editor
19743                    .selections
19744                    .all::<Point>(cx)
19745                    .into_iter()
19746                    .map(|s| s.range())
19747                    .collect::<Vec<_>>(),
19748                vec![Point::zero()..Point::zero()],
19749                "Default selections on initial open",
19750            );
19751        })
19752    });
19753    editor_1.update_in(cx, |editor, window, cx| {
19754        editor.change_selections(None, window, cx, |s| {
19755            s.select_ranges(expected_ranges.clone());
19756        });
19757    });
19758
19759    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19760        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19761    });
19762    let editor_2 = workspace
19763        .update_in(cx, |workspace, window, cx| {
19764            workspace.open_path(
19765                (worktree_id, "main.rs"),
19766                Some(pane_2.downgrade()),
19767                true,
19768                window,
19769                cx,
19770            )
19771        })
19772        .unwrap()
19773        .await
19774        .downcast::<Editor>()
19775        .unwrap();
19776    pane_2.update(cx, |pane, cx| {
19777        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19778        open_editor.update(cx, |editor, cx| {
19779            assert_eq!(
19780                editor.display_text(cx),
19781                main_text,
19782                "Original main.rs text on initial open in another panel",
19783            );
19784            assert_eq!(
19785                editor
19786                    .selections
19787                    .all::<Point>(cx)
19788                    .into_iter()
19789                    .map(|s| s.range())
19790                    .collect::<Vec<_>>(),
19791                vec![Point::zero()..Point::zero()],
19792                "Default selections on initial open in another panel",
19793            );
19794        })
19795    });
19796
19797    editor_2.update_in(cx, |editor, window, cx| {
19798        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19799    });
19800
19801    let _other_editor_1 = workspace
19802        .update_in(cx, |workspace, window, cx| {
19803            workspace.open_path(
19804                (worktree_id, "lib.rs"),
19805                Some(pane_1.downgrade()),
19806                true,
19807                window,
19808                cx,
19809            )
19810        })
19811        .unwrap()
19812        .await
19813        .downcast::<Editor>()
19814        .unwrap();
19815    pane_1
19816        .update_in(cx, |pane, window, cx| {
19817            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19818                .unwrap()
19819        })
19820        .await
19821        .unwrap();
19822    drop(editor_1);
19823    pane_1.update(cx, |pane, cx| {
19824        pane.active_item()
19825            .unwrap()
19826            .downcast::<Editor>()
19827            .unwrap()
19828            .update(cx, |editor, cx| {
19829                assert_eq!(
19830                    editor.display_text(cx),
19831                    lib_text,
19832                    "Other file should be open and active",
19833                );
19834            });
19835        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19836    });
19837
19838    let _other_editor_2 = workspace
19839        .update_in(cx, |workspace, window, cx| {
19840            workspace.open_path(
19841                (worktree_id, "lib.rs"),
19842                Some(pane_2.downgrade()),
19843                true,
19844                window,
19845                cx,
19846            )
19847        })
19848        .unwrap()
19849        .await
19850        .downcast::<Editor>()
19851        .unwrap();
19852    pane_2
19853        .update_in(cx, |pane, window, cx| {
19854            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19855                .unwrap()
19856        })
19857        .await
19858        .unwrap();
19859    drop(editor_2);
19860    pane_2.update(cx, |pane, cx| {
19861        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19862        open_editor.update(cx, |editor, cx| {
19863            assert_eq!(
19864                editor.display_text(cx),
19865                lib_text,
19866                "Other file should be open and active in another panel too",
19867            );
19868        });
19869        assert_eq!(
19870            pane.items().count(),
19871            1,
19872            "No other editors should be open in another pane",
19873        );
19874    });
19875
19876    let _editor_1_reopened = workspace
19877        .update_in(cx, |workspace, window, cx| {
19878            workspace.open_path(
19879                (worktree_id, "main.rs"),
19880                Some(pane_1.downgrade()),
19881                true,
19882                window,
19883                cx,
19884            )
19885        })
19886        .unwrap()
19887        .await
19888        .downcast::<Editor>()
19889        .unwrap();
19890    let _editor_2_reopened = workspace
19891        .update_in(cx, |workspace, window, cx| {
19892            workspace.open_path(
19893                (worktree_id, "main.rs"),
19894                Some(pane_2.downgrade()),
19895                true,
19896                window,
19897                cx,
19898            )
19899        })
19900        .unwrap()
19901        .await
19902        .downcast::<Editor>()
19903        .unwrap();
19904    pane_1.update(cx, |pane, cx| {
19905        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19906        open_editor.update(cx, |editor, cx| {
19907            assert_eq!(
19908                editor.display_text(cx),
19909                main_text,
19910                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19911            );
19912            assert_eq!(
19913                editor
19914                    .selections
19915                    .all::<Point>(cx)
19916                    .into_iter()
19917                    .map(|s| s.range())
19918                    .collect::<Vec<_>>(),
19919                expected_ranges,
19920                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19921            );
19922        })
19923    });
19924    pane_2.update(cx, |pane, cx| {
19925        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19926        open_editor.update(cx, |editor, cx| {
19927            assert_eq!(
19928                editor.display_text(cx),
19929                r#"fn main() {
19930⋯rintln!("1");
19931⋯intln!("2");
19932⋯ntln!("3");
19933println!("4");
19934println!("5");
19935}"#,
19936                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19937            );
19938            assert_eq!(
19939                editor
19940                    .selections
19941                    .all::<Point>(cx)
19942                    .into_iter()
19943                    .map(|s| s.range())
19944                    .collect::<Vec<_>>(),
19945                vec![Point::zero()..Point::zero()],
19946                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19947            );
19948        })
19949    });
19950}
19951
19952#[gpui::test]
19953async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19954    init_test(cx, |_| {});
19955
19956    let fs = FakeFs::new(cx.executor());
19957    let main_text = r#"fn main() {
19958println!("1");
19959println!("2");
19960println!("3");
19961println!("4");
19962println!("5");
19963}"#;
19964    let lib_text = "mod foo {}";
19965    fs.insert_tree(
19966        path!("/a"),
19967        json!({
19968            "lib.rs": lib_text,
19969            "main.rs": main_text,
19970        }),
19971    )
19972    .await;
19973
19974    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19975    let (workspace, cx) =
19976        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19977    let worktree_id = workspace.update(cx, |workspace, cx| {
19978        workspace.project().update(cx, |project, cx| {
19979            project.worktrees(cx).next().unwrap().read(cx).id()
19980        })
19981    });
19982
19983    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19984    let editor = workspace
19985        .update_in(cx, |workspace, window, cx| {
19986            workspace.open_path(
19987                (worktree_id, "main.rs"),
19988                Some(pane.downgrade()),
19989                true,
19990                window,
19991                cx,
19992            )
19993        })
19994        .unwrap()
19995        .await
19996        .downcast::<Editor>()
19997        .unwrap();
19998    pane.update(cx, |pane, cx| {
19999        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20000        open_editor.update(cx, |editor, cx| {
20001            assert_eq!(
20002                editor.display_text(cx),
20003                main_text,
20004                "Original main.rs text on initial open",
20005            );
20006        })
20007    });
20008    editor.update_in(cx, |editor, window, cx| {
20009        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20010    });
20011
20012    cx.update_global(|store: &mut SettingsStore, cx| {
20013        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20014            s.restore_on_file_reopen = Some(false);
20015        });
20016    });
20017    editor.update_in(cx, |editor, window, cx| {
20018        editor.fold_ranges(
20019            vec![
20020                Point::new(1, 0)..Point::new(1, 1),
20021                Point::new(2, 0)..Point::new(2, 2),
20022                Point::new(3, 0)..Point::new(3, 3),
20023            ],
20024            false,
20025            window,
20026            cx,
20027        );
20028    });
20029    pane.update_in(cx, |pane, window, cx| {
20030        pane.close_all_items(&CloseAllItems::default(), window, cx)
20031            .unwrap()
20032    })
20033    .await
20034    .unwrap();
20035    pane.update(cx, |pane, _| {
20036        assert!(pane.active_item().is_none());
20037    });
20038    cx.update_global(|store: &mut SettingsStore, cx| {
20039        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20040            s.restore_on_file_reopen = Some(true);
20041        });
20042    });
20043
20044    let _editor_reopened = workspace
20045        .update_in(cx, |workspace, window, cx| {
20046            workspace.open_path(
20047                (worktree_id, "main.rs"),
20048                Some(pane.downgrade()),
20049                true,
20050                window,
20051                cx,
20052            )
20053        })
20054        .unwrap()
20055        .await
20056        .downcast::<Editor>()
20057        .unwrap();
20058    pane.update(cx, |pane, cx| {
20059        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20060        open_editor.update(cx, |editor, cx| {
20061            assert_eq!(
20062                editor.display_text(cx),
20063                main_text,
20064                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20065            );
20066        })
20067    });
20068}
20069
20070#[gpui::test]
20071async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20072    struct EmptyModalView {
20073        focus_handle: gpui::FocusHandle,
20074    }
20075    impl EventEmitter<DismissEvent> for EmptyModalView {}
20076    impl Render for EmptyModalView {
20077        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20078            div()
20079        }
20080    }
20081    impl Focusable for EmptyModalView {
20082        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20083            self.focus_handle.clone()
20084        }
20085    }
20086    impl workspace::ModalView for EmptyModalView {}
20087    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20088        EmptyModalView {
20089            focus_handle: cx.focus_handle(),
20090        }
20091    }
20092
20093    init_test(cx, |_| {});
20094
20095    let fs = FakeFs::new(cx.executor());
20096    let project = Project::test(fs, [], cx).await;
20097    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20098    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20099    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20100    let editor = cx.new_window_entity(|window, cx| {
20101        Editor::new(
20102            EditorMode::full(),
20103            buffer,
20104            Some(project.clone()),
20105            window,
20106            cx,
20107        )
20108    });
20109    workspace
20110        .update(cx, |workspace, window, cx| {
20111            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20112        })
20113        .unwrap();
20114    editor.update_in(cx, |editor, window, cx| {
20115        editor.open_context_menu(&OpenContextMenu, window, cx);
20116        assert!(editor.mouse_context_menu.is_some());
20117    });
20118    workspace
20119        .update(cx, |workspace, window, cx| {
20120            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20121        })
20122        .unwrap();
20123    cx.read(|cx| {
20124        assert!(editor.read(cx).mouse_context_menu.is_none());
20125    });
20126}
20127
20128#[gpui::test]
20129async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20130    init_test(cx, |_| {});
20131
20132    let fs = FakeFs::new(cx.executor());
20133    fs.insert_file(path!("/file.html"), Default::default())
20134        .await;
20135
20136    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20137
20138    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20139    let html_language = Arc::new(Language::new(
20140        LanguageConfig {
20141            name: "HTML".into(),
20142            matcher: LanguageMatcher {
20143                path_suffixes: vec!["html".to_string()],
20144                ..LanguageMatcher::default()
20145            },
20146            brackets: BracketPairConfig {
20147                pairs: vec![BracketPair {
20148                    start: "<".into(),
20149                    end: ">".into(),
20150                    close: true,
20151                    ..Default::default()
20152                }],
20153                ..Default::default()
20154            },
20155            ..Default::default()
20156        },
20157        Some(tree_sitter_html::LANGUAGE.into()),
20158    ));
20159    language_registry.add(html_language);
20160    let mut fake_servers = language_registry.register_fake_lsp(
20161        "HTML",
20162        FakeLspAdapter {
20163            capabilities: lsp::ServerCapabilities {
20164                completion_provider: Some(lsp::CompletionOptions {
20165                    resolve_provider: Some(true),
20166                    ..Default::default()
20167                }),
20168                ..Default::default()
20169            },
20170            ..Default::default()
20171        },
20172    );
20173
20174    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20175    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20176
20177    let worktree_id = workspace
20178        .update(cx, |workspace, _window, cx| {
20179            workspace.project().update(cx, |project, cx| {
20180                project.worktrees(cx).next().unwrap().read(cx).id()
20181            })
20182        })
20183        .unwrap();
20184    project
20185        .update(cx, |project, cx| {
20186            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20187        })
20188        .await
20189        .unwrap();
20190    let editor = workspace
20191        .update(cx, |workspace, window, cx| {
20192            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20193        })
20194        .unwrap()
20195        .await
20196        .unwrap()
20197        .downcast::<Editor>()
20198        .unwrap();
20199
20200    let fake_server = fake_servers.next().await.unwrap();
20201    editor.update_in(cx, |editor, window, cx| {
20202        editor.set_text("<ad></ad>", window, cx);
20203        editor.change_selections(None, window, cx, |selections| {
20204            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20205        });
20206        let Some((buffer, _)) = editor
20207            .buffer
20208            .read(cx)
20209            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20210        else {
20211            panic!("Failed to get buffer for selection position");
20212        };
20213        let buffer = buffer.read(cx);
20214        let buffer_id = buffer.remote_id();
20215        let opening_range =
20216            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20217        let closing_range =
20218            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20219        let mut linked_ranges = HashMap::default();
20220        linked_ranges.insert(
20221            buffer_id,
20222            vec![(opening_range.clone(), vec![closing_range.clone()])],
20223        );
20224        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20225    });
20226    let mut completion_handle =
20227        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20228            Ok(Some(lsp::CompletionResponse::Array(vec![
20229                lsp::CompletionItem {
20230                    label: "head".to_string(),
20231                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20232                        lsp::InsertReplaceEdit {
20233                            new_text: "head".to_string(),
20234                            insert: lsp::Range::new(
20235                                lsp::Position::new(0, 1),
20236                                lsp::Position::new(0, 3),
20237                            ),
20238                            replace: lsp::Range::new(
20239                                lsp::Position::new(0, 1),
20240                                lsp::Position::new(0, 3),
20241                            ),
20242                        },
20243                    )),
20244                    ..Default::default()
20245                },
20246            ])))
20247        });
20248    editor.update_in(cx, |editor, window, cx| {
20249        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20250    });
20251    cx.run_until_parked();
20252    completion_handle.next().await.unwrap();
20253    editor.update(cx, |editor, _| {
20254        assert!(
20255            editor.context_menu_visible(),
20256            "Completion menu should be visible"
20257        );
20258    });
20259    editor.update_in(cx, |editor, window, cx| {
20260        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20261    });
20262    cx.executor().run_until_parked();
20263    editor.update(cx, |editor, cx| {
20264        assert_eq!(editor.text(cx), "<head></head>");
20265    });
20266}
20267
20268#[gpui::test]
20269async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20270    init_test(cx, |_| {});
20271
20272    let fs = FakeFs::new(cx.executor());
20273    fs.insert_tree(
20274        path!("/root"),
20275        json!({
20276            "a": {
20277                "main.rs": "fn main() {}",
20278            },
20279            "foo": {
20280                "bar": {
20281                    "external_file.rs": "pub mod external {}",
20282                }
20283            }
20284        }),
20285    )
20286    .await;
20287
20288    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20289    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20290    language_registry.add(rust_lang());
20291    let _fake_servers = language_registry.register_fake_lsp(
20292        "Rust",
20293        FakeLspAdapter {
20294            ..FakeLspAdapter::default()
20295        },
20296    );
20297    let (workspace, cx) =
20298        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20299    let worktree_id = workspace.update(cx, |workspace, cx| {
20300        workspace.project().update(cx, |project, cx| {
20301            project.worktrees(cx).next().unwrap().read(cx).id()
20302        })
20303    });
20304
20305    let assert_language_servers_count =
20306        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20307            project.update(cx, |project, cx| {
20308                let current = project
20309                    .lsp_store()
20310                    .read(cx)
20311                    .as_local()
20312                    .unwrap()
20313                    .language_servers
20314                    .len();
20315                assert_eq!(expected, current, "{context}");
20316            });
20317        };
20318
20319    assert_language_servers_count(
20320        0,
20321        "No servers should be running before any file is open",
20322        cx,
20323    );
20324    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20325    let main_editor = workspace
20326        .update_in(cx, |workspace, window, cx| {
20327            workspace.open_path(
20328                (worktree_id, "main.rs"),
20329                Some(pane.downgrade()),
20330                true,
20331                window,
20332                cx,
20333            )
20334        })
20335        .unwrap()
20336        .await
20337        .downcast::<Editor>()
20338        .unwrap();
20339    pane.update(cx, |pane, cx| {
20340        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20341        open_editor.update(cx, |editor, cx| {
20342            assert_eq!(
20343                editor.display_text(cx),
20344                "fn main() {}",
20345                "Original main.rs text on initial open",
20346            );
20347        });
20348        assert_eq!(open_editor, main_editor);
20349    });
20350    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20351
20352    let external_editor = workspace
20353        .update_in(cx, |workspace, window, cx| {
20354            workspace.open_abs_path(
20355                PathBuf::from("/root/foo/bar/external_file.rs"),
20356                OpenOptions::default(),
20357                window,
20358                cx,
20359            )
20360        })
20361        .await
20362        .expect("opening external file")
20363        .downcast::<Editor>()
20364        .expect("downcasted external file's open element to editor");
20365    pane.update(cx, |pane, cx| {
20366        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20367        open_editor.update(cx, |editor, cx| {
20368            assert_eq!(
20369                editor.display_text(cx),
20370                "pub mod external {}",
20371                "External file is open now",
20372            );
20373        });
20374        assert_eq!(open_editor, external_editor);
20375    });
20376    assert_language_servers_count(
20377        1,
20378        "Second, external, *.rs file should join the existing server",
20379        cx,
20380    );
20381
20382    pane.update_in(cx, |pane, window, cx| {
20383        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20384    })
20385    .unwrap()
20386    .await
20387    .unwrap();
20388    pane.update_in(cx, |pane, window, cx| {
20389        pane.navigate_backward(window, cx);
20390    });
20391    cx.run_until_parked();
20392    pane.update(cx, |pane, cx| {
20393        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20394        open_editor.update(cx, |editor, cx| {
20395            assert_eq!(
20396                editor.display_text(cx),
20397                "pub mod external {}",
20398                "External file is open now",
20399            );
20400        });
20401    });
20402    assert_language_servers_count(
20403        1,
20404        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20405        cx,
20406    );
20407
20408    cx.update(|_, cx| {
20409        workspace::reload(&workspace::Reload::default(), cx);
20410    });
20411    assert_language_servers_count(
20412        1,
20413        "After reloading the worktree with local and external files opened, only one project should be started",
20414        cx,
20415    );
20416}
20417
20418#[gpui::test]
20419async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20420    init_test(cx, |_| {});
20421
20422    let mut cx = EditorTestContext::new(cx).await;
20423    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20424    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20425
20426    // test cursor move to start of each line on tab
20427    // for `if`, `elif`, `else`, `while`, `with` and `for`
20428    cx.set_state(indoc! {"
20429        def main():
20430        ˇ    for item in items:
20431        ˇ        while item.active:
20432        ˇ            if item.value > 10:
20433        ˇ                continue
20434        ˇ            elif item.value < 0:
20435        ˇ                break
20436        ˇ            else:
20437        ˇ                with item.context() as ctx:
20438        ˇ                    yield count
20439        ˇ        else:
20440        ˇ            log('while else')
20441        ˇ    else:
20442        ˇ        log('for else')
20443    "});
20444    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20445    cx.assert_editor_state(indoc! {"
20446        def main():
20447            ˇfor item in items:
20448                ˇwhile item.active:
20449                    ˇif item.value > 10:
20450                        ˇcontinue
20451                    ˇelif item.value < 0:
20452                        ˇbreak
20453                    ˇelse:
20454                        ˇwith item.context() as ctx:
20455                            ˇyield count
20456                ˇelse:
20457                    ˇlog('while else')
20458            ˇelse:
20459                ˇlog('for else')
20460    "});
20461    // test relative indent is preserved when tab
20462    // for `if`, `elif`, `else`, `while`, `with` and `for`
20463    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20464    cx.assert_editor_state(indoc! {"
20465        def main():
20466                ˇfor item in items:
20467                    ˇwhile item.active:
20468                        ˇif item.value > 10:
20469                            ˇcontinue
20470                        ˇelif item.value < 0:
20471                            ˇbreak
20472                        ˇelse:
20473                            ˇwith item.context() as ctx:
20474                                ˇyield count
20475                    ˇelse:
20476                        ˇlog('while else')
20477                ˇelse:
20478                    ˇlog('for else')
20479    "});
20480
20481    // test cursor move to start of each line on tab
20482    // for `try`, `except`, `else`, `finally`, `match` and `def`
20483    cx.set_state(indoc! {"
20484        def main():
20485        ˇ    try:
20486        ˇ       fetch()
20487        ˇ    except ValueError:
20488        ˇ       handle_error()
20489        ˇ    else:
20490        ˇ        match value:
20491        ˇ            case _:
20492        ˇ    finally:
20493        ˇ        def status():
20494        ˇ            return 0
20495    "});
20496    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20497    cx.assert_editor_state(indoc! {"
20498        def main():
20499            ˇtry:
20500                ˇfetch()
20501            ˇexcept ValueError:
20502                ˇhandle_error()
20503            ˇelse:
20504                ˇmatch value:
20505                    ˇcase _:
20506            ˇfinally:
20507                ˇdef status():
20508                    ˇreturn 0
20509    "});
20510    // test relative indent is preserved when tab
20511    // for `try`, `except`, `else`, `finally`, `match` and `def`
20512    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20513    cx.assert_editor_state(indoc! {"
20514        def main():
20515                ˇtry:
20516                    ˇfetch()
20517                ˇexcept ValueError:
20518                    ˇhandle_error()
20519                ˇelse:
20520                    ˇmatch value:
20521                        ˇcase _:
20522                ˇfinally:
20523                    ˇdef status():
20524                        ˇreturn 0
20525    "});
20526}
20527
20528#[gpui::test]
20529async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
20530    init_test(cx, |_| {});
20531
20532    let mut cx = EditorTestContext::new(cx).await;
20533    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20534    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20535
20536    // test `else` auto outdents when typed inside `if` block
20537    cx.set_state(indoc! {"
20538        def main():
20539            if i == 2:
20540                return
20541                ˇ
20542    "});
20543    cx.update_editor(|editor, window, cx| {
20544        editor.handle_input("else:", window, cx);
20545    });
20546    cx.assert_editor_state(indoc! {"
20547        def main():
20548            if i == 2:
20549                return
20550            else:ˇ
20551    "});
20552
20553    // test `except` auto outdents when typed inside `try` block
20554    cx.set_state(indoc! {"
20555        def main():
20556            try:
20557                i = 2
20558                ˇ
20559    "});
20560    cx.update_editor(|editor, window, cx| {
20561        editor.handle_input("except:", window, cx);
20562    });
20563    cx.assert_editor_state(indoc! {"
20564        def main():
20565            try:
20566                i = 2
20567            except:ˇ
20568    "});
20569
20570    // test `else` auto outdents when typed inside `except` block
20571    cx.set_state(indoc! {"
20572        def main():
20573            try:
20574                i = 2
20575            except:
20576                j = 2
20577                ˇ
20578    "});
20579    cx.update_editor(|editor, window, cx| {
20580        editor.handle_input("else:", window, cx);
20581    });
20582    cx.assert_editor_state(indoc! {"
20583        def main():
20584            try:
20585                i = 2
20586            except:
20587                j = 2
20588            else:ˇ
20589    "});
20590
20591    // test `finally` auto outdents when typed inside `else` block
20592    cx.set_state(indoc! {"
20593        def main():
20594            try:
20595                i = 2
20596            except:
20597                j = 2
20598            else:
20599                k = 2
20600                ˇ
20601    "});
20602    cx.update_editor(|editor, window, cx| {
20603        editor.handle_input("finally:", window, cx);
20604    });
20605    cx.assert_editor_state(indoc! {"
20606        def main():
20607            try:
20608                i = 2
20609            except:
20610                j = 2
20611            else:
20612                k = 2
20613            finally:ˇ
20614    "});
20615
20616    // TODO: test `except` auto outdents when typed inside `try` block right after for block
20617    // cx.set_state(indoc! {"
20618    //     def main():
20619    //         try:
20620    //             for i in range(n):
20621    //                 pass
20622    //             ˇ
20623    // "});
20624    // cx.update_editor(|editor, window, cx| {
20625    //     editor.handle_input("except:", window, cx);
20626    // });
20627    // cx.assert_editor_state(indoc! {"
20628    //     def main():
20629    //         try:
20630    //             for i in range(n):
20631    //                 pass
20632    //         except:ˇ
20633    // "});
20634
20635    // TODO: test `else` auto outdents when typed inside `except` block right after for block
20636    // cx.set_state(indoc! {"
20637    //     def main():
20638    //         try:
20639    //             i = 2
20640    //         except:
20641    //             for i in range(n):
20642    //                 pass
20643    //             ˇ
20644    // "});
20645    // cx.update_editor(|editor, window, cx| {
20646    //     editor.handle_input("else:", window, cx);
20647    // });
20648    // cx.assert_editor_state(indoc! {"
20649    //     def main():
20650    //         try:
20651    //             i = 2
20652    //         except:
20653    //             for i in range(n):
20654    //                 pass
20655    //         else:ˇ
20656    // "});
20657
20658    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
20659    // cx.set_state(indoc! {"
20660    //     def main():
20661    //         try:
20662    //             i = 2
20663    //         except:
20664    //             j = 2
20665    //         else:
20666    //             for i in range(n):
20667    //                 pass
20668    //             ˇ
20669    // "});
20670    // cx.update_editor(|editor, window, cx| {
20671    //     editor.handle_input("finally:", window, cx);
20672    // });
20673    // cx.assert_editor_state(indoc! {"
20674    //     def main():
20675    //         try:
20676    //             i = 2
20677    //         except:
20678    //             j = 2
20679    //         else:
20680    //             for i in range(n):
20681    //                 pass
20682    //         finally:ˇ
20683    // "});
20684
20685    // test `else` stays at correct indent when typed after `for` block
20686    cx.set_state(indoc! {"
20687        def main():
20688            for i in range(10):
20689                if i == 3:
20690                    break
20691            ˇ
20692    "});
20693    cx.update_editor(|editor, window, cx| {
20694        editor.handle_input("else:", window, cx);
20695    });
20696    cx.assert_editor_state(indoc! {"
20697        def main():
20698            for i in range(10):
20699                if i == 3:
20700                    break
20701            else:ˇ
20702    "});
20703}
20704
20705#[gpui::test]
20706async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
20707    init_test(cx, |_| {});
20708    update_test_language_settings(cx, |settings| {
20709        settings.defaults.extend_comment_on_newline = Some(false);
20710    });
20711    let mut cx = EditorTestContext::new(cx).await;
20712    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20713    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20714
20715    // test correct indent after newline on comment
20716    cx.set_state(indoc! {"
20717        # COMMENT:ˇ
20718    "});
20719    cx.update_editor(|editor, window, cx| {
20720        editor.newline(&Newline, window, cx);
20721    });
20722    cx.assert_editor_state(indoc! {"
20723        # COMMENT:
20724        ˇ
20725    "});
20726
20727    // test correct indent after newline in curly brackets
20728    cx.set_state(indoc! {"
20729        {ˇ}
20730    "});
20731    cx.update_editor(|editor, window, cx| {
20732        editor.newline(&Newline, window, cx);
20733    });
20734    cx.run_until_parked();
20735    cx.assert_editor_state(indoc! {"
20736        {
20737            ˇ
20738        }
20739    "});
20740}
20741
20742fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
20743    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
20744    point..point
20745}
20746
20747fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
20748    let (text, ranges) = marked_text_ranges(marked_text, true);
20749    assert_eq!(editor.text(cx), text);
20750    assert_eq!(
20751        editor.selections.ranges(cx),
20752        ranges,
20753        "Assert selections are {}",
20754        marked_text
20755    );
20756}
20757
20758pub fn handle_signature_help_request(
20759    cx: &mut EditorLspTestContext,
20760    mocked_response: lsp::SignatureHelp,
20761) -> impl Future<Output = ()> + use<> {
20762    let mut request =
20763        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
20764            let mocked_response = mocked_response.clone();
20765            async move { Ok(Some(mocked_response)) }
20766        });
20767
20768    async move {
20769        request.next().await;
20770    }
20771}
20772
20773/// Handle completion request passing a marked string specifying where the completion
20774/// should be triggered from using '|' character, what range should be replaced, and what completions
20775/// should be returned using '<' and '>' to delimit the range.
20776///
20777/// Also see `handle_completion_request_with_insert_and_replace`.
20778#[track_caller]
20779pub fn handle_completion_request(
20780    cx: &mut EditorLspTestContext,
20781    marked_string: &str,
20782    completions: Vec<&'static str>,
20783    counter: Arc<AtomicUsize>,
20784) -> impl Future<Output = ()> {
20785    let complete_from_marker: TextRangeMarker = '|'.into();
20786    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20787    let (_, mut marked_ranges) = marked_text_ranges_by(
20788        marked_string,
20789        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20790    );
20791
20792    let complete_from_position =
20793        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20794    let replace_range =
20795        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20796
20797    let mut request =
20798        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20799            let completions = completions.clone();
20800            counter.fetch_add(1, atomic::Ordering::Release);
20801            async move {
20802                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20803                assert_eq!(
20804                    params.text_document_position.position,
20805                    complete_from_position
20806                );
20807                Ok(Some(lsp::CompletionResponse::Array(
20808                    completions
20809                        .iter()
20810                        .map(|completion_text| lsp::CompletionItem {
20811                            label: completion_text.to_string(),
20812                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20813                                range: replace_range,
20814                                new_text: completion_text.to_string(),
20815                            })),
20816                            ..Default::default()
20817                        })
20818                        .collect(),
20819                )))
20820            }
20821        });
20822
20823    async move {
20824        request.next().await;
20825    }
20826}
20827
20828/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
20829/// given instead, which also contains an `insert` range.
20830///
20831/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
20832/// that is, `replace_range.start..cursor_pos`.
20833pub fn handle_completion_request_with_insert_and_replace(
20834    cx: &mut EditorLspTestContext,
20835    marked_string: &str,
20836    completions: Vec<&'static str>,
20837    counter: Arc<AtomicUsize>,
20838) -> impl Future<Output = ()> {
20839    let complete_from_marker: TextRangeMarker = '|'.into();
20840    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20841    let (_, mut marked_ranges) = marked_text_ranges_by(
20842        marked_string,
20843        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20844    );
20845
20846    let complete_from_position =
20847        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20848    let replace_range =
20849        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20850
20851    let mut request =
20852        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20853            let completions = completions.clone();
20854            counter.fetch_add(1, atomic::Ordering::Release);
20855            async move {
20856                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20857                assert_eq!(
20858                    params.text_document_position.position, complete_from_position,
20859                    "marker `|` position doesn't match",
20860                );
20861                Ok(Some(lsp::CompletionResponse::Array(
20862                    completions
20863                        .iter()
20864                        .map(|completion_text| lsp::CompletionItem {
20865                            label: completion_text.to_string(),
20866                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20867                                lsp::InsertReplaceEdit {
20868                                    insert: lsp::Range {
20869                                        start: replace_range.start,
20870                                        end: complete_from_position,
20871                                    },
20872                                    replace: replace_range,
20873                                    new_text: completion_text.to_string(),
20874                                },
20875                            )),
20876                            ..Default::default()
20877                        })
20878                        .collect(),
20879                )))
20880            }
20881        });
20882
20883    async move {
20884        request.next().await;
20885    }
20886}
20887
20888fn handle_resolve_completion_request(
20889    cx: &mut EditorLspTestContext,
20890    edits: Option<Vec<(&'static str, &'static str)>>,
20891) -> impl Future<Output = ()> {
20892    let edits = edits.map(|edits| {
20893        edits
20894            .iter()
20895            .map(|(marked_string, new_text)| {
20896                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
20897                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
20898                lsp::TextEdit::new(replace_range, new_text.to_string())
20899            })
20900            .collect::<Vec<_>>()
20901    });
20902
20903    let mut request =
20904        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20905            let edits = edits.clone();
20906            async move {
20907                Ok(lsp::CompletionItem {
20908                    additional_text_edits: edits,
20909                    ..Default::default()
20910                })
20911            }
20912        });
20913
20914    async move {
20915        request.next().await;
20916    }
20917}
20918
20919pub(crate) fn update_test_language_settings(
20920    cx: &mut TestAppContext,
20921    f: impl Fn(&mut AllLanguageSettingsContent),
20922) {
20923    cx.update(|cx| {
20924        SettingsStore::update_global(cx, |store, cx| {
20925            store.update_user_settings::<AllLanguageSettings>(cx, f);
20926        });
20927    });
20928}
20929
20930pub(crate) fn update_test_project_settings(
20931    cx: &mut TestAppContext,
20932    f: impl Fn(&mut ProjectSettings),
20933) {
20934    cx.update(|cx| {
20935        SettingsStore::update_global(cx, |store, cx| {
20936            store.update_user_settings::<ProjectSettings>(cx, f);
20937        });
20938    });
20939}
20940
20941pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
20942    cx.update(|cx| {
20943        assets::Assets.load_test_fonts(cx);
20944        let store = SettingsStore::test(cx);
20945        cx.set_global(store);
20946        theme::init(theme::LoadThemes::JustBase, cx);
20947        release_channel::init(SemanticVersion::default(), cx);
20948        client::init_settings(cx);
20949        language::init(cx);
20950        Project::init_settings(cx);
20951        workspace::init_settings(cx);
20952        crate::init(cx);
20953    });
20954
20955    update_test_language_settings(cx, f);
20956}
20957
20958#[track_caller]
20959fn assert_hunk_revert(
20960    not_reverted_text_with_selections: &str,
20961    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
20962    expected_reverted_text_with_selections: &str,
20963    base_text: &str,
20964    cx: &mut EditorLspTestContext,
20965) {
20966    cx.set_state(not_reverted_text_with_selections);
20967    cx.set_head_text(base_text);
20968    cx.executor().run_until_parked();
20969
20970    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
20971        let snapshot = editor.snapshot(window, cx);
20972        let reverted_hunk_statuses = snapshot
20973            .buffer_snapshot
20974            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
20975            .map(|hunk| hunk.status().kind)
20976            .collect::<Vec<_>>();
20977
20978        editor.git_restore(&Default::default(), window, cx);
20979        reverted_hunk_statuses
20980    });
20981    cx.executor().run_until_parked();
20982    cx.assert_editor_state(expected_reverted_text_with_selections);
20983    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
20984}